关于 ProGuard 的一些踩坑总结

前言

在开发中,混淆是相当重要的一个环节,任何一个 app 都应该开启代码混淆、资源压缩、移除无用资源。Android 的 SDK 提供了 ProGuard 来实现这一过程。ProGuard 会检测和移除封装应用中未使用的类、字段、方法和属性,包括自带代码库中的未使用项(这使其成为以变通方式解决 64k 引用限制的有用工具)。ProGuard 还可优化字节码,移除未使用的代码指令,以及用短名称混淆其余的类、字段和方法。混淆过的代码可令您的 APK 难以被逆向工程,这在应用使用许可验证等安全敏感性功能时特别有用。

ProGuard 简介

ProGuard 一共包括四个功能:

  • 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性
  • 优化(Optimize):对字节码进行优化,移除无用的指令
  • 混淆(Obfuscate):使用 a、b、c、d 这样的名称,对类、字段和方法进行重命名
  • 预检(Preveirfy):在 Java 平台上对处理后的代码进行预检
    混淆由这个四个步骤构成,其中每个步骤也都是可选的,可通过配置脚本决定执行其中哪儿步骤。

混淆命令

  • -keep {Modifier} {class_specification} 防止类和成员被移除或者被重命名
  • -keepclassmembers {modifier} {class_specification} 防止成员被移除或者被重命名
  • -keepclasseswithmembers {class_specification} 防止拥有该成员的类和成员被移除或者被重命名
  • -keepnames {class_specification} 防止成员被重命名
  • -keepclasseswithmembernames {class_specification} 防止拥有该成员的类和成员被重命名
  • -keepclasseswithmembers
  • optimizationpasses 指定压缩级别
  • dontoptimize 不优化输入的类文件
  • dontusemixedcaseclassnames 包名不混合大小写
  • dontskipnonpubliclibraryclasses 不跳过非公共的库的类成员
  • dontpreverify 混淆时不做预校验
  • dontwarn 如果有警告也不终止
  • verbose 混淆时记录日志
  • optimizations 混淆时采用的算法

元素不参与混淆的规则

形如:

[保持命令] [类] {
    [成员]
}

“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

  • 具体的类
  • 访问修饰符(public、protected、private)
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • extends,即可以指定类的基类
  • implement,匹配实现了某接口的类
  • $,内部类

“成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

  • 匹配所有构造器
  • 匹配所有域
  • 匹配所有方法
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • 通配符*,匹配任意参数类型
  • …,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。
  • 访问修饰符(public、protected、private)

常用自定义混淆规则

  • -keep public class com.proguard.example.Test { *; } 不混淆某个类
  • -keep class com.bumptech.glide.* { ; } 不混淆某个包下面的所有类
  • -keep public class * extends com.proguard.example.Test { *; } 不混淆某个类的子类
  • -keep public class .model. {*;} 不混淆所有类名包含 model 的类及其成员
  • -keep public class * extends android.os.IInterface 不混淆某个接口的实现
  • -keepclassmembers class com.proguard.example.Test {public ();} 不混淆某个类的构造方法
  • -keepclassmembers class * {void *(**On*Event);} 不混淆带有回调函数 OnXXEvent的方法

混淆配置

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            //Zipalign优化
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

一般混淆的时间比较长,所以只在 release 模式下开启。shrinkResources 是打开资源压缩。

通用混淆代码

#指定压缩级别
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法 后面的参数是一个过滤器,这个过滤器是谷歌推挤的算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification

#包明不混合大小写
-dontusemixedcaseclassnames

#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses

 #混淆时是否记录日志
-verbose

# 混淆时不做预校验 Android 不需要做 preverify,去掉可加快混淆速度
-dontpreverify

#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile

#保留行号
-keepattributes SourceFile,LineNumberTable,Deprecated

#忽略警告
-ignorewarning


#生成日志数据,gradle build时在本项目根目录输出
-dump class_files.txt            #apk包内所有class的内部结构
-printseeds seeds.txt            #未混淆的类和成员
-printusage unused.txt           #打印未被使用的代码
-printmapping mapping.txt        #混淆前后的映射

#保护注解
-keepattributes *Annotation*,InnerClasses,Deprecated

#避免混淆泛型 如果混淆报错建议关掉
-keepattributes Signature

-keepattributes EnclosingMethod

#保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native ;
}

#不提示兼容库的错误警告
-dontwarn android.support.**

#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}


# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
-keep public class * extends android.os.IInterface

-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}


#不混淆资源类及其方法
-keep class **.R$* {
 *;
}

# 对于带有回调函数onXXEvent的,不能混淆
-keepclassmembers class * {
    void *(**On*Event);
}

# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
 public (android.content.Context, android.util.AttributeSet);
}

# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
 public (android.content.Context, android.util.AttributeSet, int);
}

# 保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
 public void *(android.view.View);
}

-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
 public static **[] values();
 public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
 public static final android.os.Parcelable$Creator *;
}

混淆检查

混淆过的包必须进行检查,避免因混淆引入的bug。一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:

  • dump.txt 描述APK文件中所有类的内部结构
  • mapping.txt 提供混淆前后类、方法、类成员等的对照表
  • seeds.txt 列出没有被混淆的类和成员
  • usage.txt 列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

参考
proguard 使用手册
proguard 介绍
压缩代码和资源
写给Android开发者的混淆使用手册

你可能感兴趣的:(Android知识总结)