Android & ProGuard

参考链接首选官方文档>>

  • 开启方式
  • 配置方式
  • 调整方法

开启

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
  • proguardFiles:
    <SDK>/tools/proguard/proguard-android.txt 是默认的配置;
    app/proguard-rules.pro 是我们自定义规则的文件;

  • app/build.gradle:为不同的 build type 设置不同的配置;

  • minifyEnabled: 移除无效的类、类成员、方法、属性等;把类名、属性名、方法名替换为简短且无意义的名称

  • shrinkResources: 删除 / 合并资源,将 drawable/layout 中没有被引用的文件的内容清空而保留文件;而名称相同的资源被视为重复资源会被合并;
    注意:删除资源很容易出现问题,这时候要找到被误删的资源并在 res/raw/keep.xml 中标注:

<!--tools:keep 属性中指定要保留的资源-->
<!--tools:discard 属性中指定要舍弃的资源-->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

构建成功后可以在 <module-name>/build/outputs/mapping/release/ 中可以找到相关文件:
dump.txt: 说明 APK 中所有类文件的内部结构。
mapping.txt: 提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt: 列出未进行混淆的类和成员。
usage.txt: 列出从 APK 移除的代码。

配置

自定义的混淆规则的格式如下:

[保持命令] <类> {
    [成员] 
}
  • [保存命令]
命令 说明
-dontwarn 不提示指定包名的混淆打包 warning
-keep 防止类和成员被移除或者被重命名
-keepnames 防止类和成员被重命名
-keepclassmembers 防止成员被移除或者被重命名
-keepnames 防止成员被重命名
-keepclasseswithmembers 防止拥有该成员的类和成员被移除或者被重命名
-keepclasseswithmembernames 防止拥有该成员的类和成员被重命名

<类> 指定了作用文件,可以有如下匹配写法:

  • 具体的类(完整包名)
    -dontnote retrofit2.Platform
  • 访问修饰符(public、protected、private)
    -keep public class * extends android.app.Fragment
  • 通配符 *,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符 **,匹配任意长度字符,并且包含包名分隔符(.)
    -dontwarn com.tencent.bugly.**
  • extends,即可以指定类的基类
    -keep public class * extends android.app.Fragment
  • implement,匹配实现了某接口的类
    -keep public class * implements com.bumptech.glide.module.GlideModule
  • $,内部类

[成员] 指定了类成员相关的限定条件,可以使用:

  • 匹配所有构造器
    public <init>();
  • 匹配所有字段
    <fields>
  • 匹配所有方法
# 保留所有类中的有 @org.greenrobot.eventbus.Subscribe 注解的方法
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
  • 通配符 *,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符 **,匹配任意长度字符,并且包含包名分隔符(.)
  • 通配符 ***,匹配任意参数类型
  • …,匹配任意长度的任意类型参数。
    void test(…)
  • 访问修饰符(public、protected、private)

而一些常用的写法:

# 不混淆某个类
-keep public class name.huihui.example.Test { *; }

# 不混淆某个包所有的类
-keep class name.huihui.test.** { *; }

#不混淆某个类的子类
-keep public class * extends name.huihui.example.Test { *; }

#不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** {*;}

#不混淆某个接口的实现
-keep class * implements name.huihui.example.TestInterface { *; }

#不混淆某个类的构造方法
-keepclassmembers class name.huihui.example.Test { 
  public <init>(); 
}

#不混淆某个类的特定的方法
-keepclassmembers class name.huihui.example.Test { 
  public void test(java.lang.String); 
}

调整

混淆的规则大部分是固定的,然后重点在于我们要根据项目引用的框架、使用的技术去调整规则:

  • 第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。

  • 在运行时动态改变的代码,例如反射。
    比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在 Model 包下的话,可以添加类似这样的代码把所有实体类都保持住:
    -keep public class **.*Model*.** {*;}

  • JNI 中调用的类。

  • WebViewJavaScript 调用的方法

#保留 annotation, 例如 @JavascriptInterface 
-keepattributes *Annotation*

#保留跟 javascript 相关的属性 
-keepattributes JavascriptInterface

# #package# 为实际的包名
#-keepclassmembers #package#.JSInterface {
#    <methods>;
#}
  • Layout 布局使用的 View 构造函数、android:onClick 等。
# 保持自定义 View 的 get 和 set 相关方法
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

  • 编译时问题
Note: there were 8 references to unknown classes.
You should check your configuration for typos.
([http://proguard.sourceforge.net/manual/troubleshooting.html#unknownclass](http://proguard.sourceforge.net/manual/troubleshooting.html#unknownclass))
Note: there were 272 unkept descriptor classes in kept class members.
You should consider explicitly keeping the mentioned classes
(using '-keep').
([http://proguard.sourceforge.net/manual/troubleshooting.html#descriptorclass](http://proguard.sourceforge.net/manual/troubleshooting.html#descriptorclass))
Note: there were 75 unresolved dynamic references to classes or interfaces.
You should check if you need to specify additional program jars.
([http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass](http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass))
Warning: there were 11 unresolved references to classes or interfaces.
You may need to add missing library jars or update their versions.
If your code works fine without the missing classes, you can suppress
the warnings with '-dontwarn' options.
([http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass](http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass))
Warning: Exception while processing task java.io.IOException: Please correct the above warnings first.

Warning 中提到有 11 个未解析的类或引用,并且给出两个解决方案;
方案一:你可能需要添加丢失的库或更新它们的版本。
方案二:如果你现在代码运行得好好的,也就是没有它们也没关系,那你可以使用 -dontwarn 来禁止这样的警告。

具体的类或引用会出现在日志中:

Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.OpenSSLProvider
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.OpenSSLProvider
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt

上述例子中类 okhttp3.internal.platform.ConscryptPlatform 引用了 org.conscrypt.OpenSSLProvider 但混淆压缩后找不到。如果项目中实际并没有用上类 org.conscrypt.OpenSSLProvider 就可以通过以下规则抑制警告:

-dontwarn org.conscrypt.*

-dontwarn okhttp3.internal.platform.ConscryptPlatform
  • 运行时问题
    这一块最麻烦的是,这种问题往往可能藏的比较深,所以除了彻底的回归测试外只有理解这其中的原理才能有效的减少问题。
    配置混淆规则时提到过,动态改变的代码、反射、js 调用等方法 / 类不能被混淆,否则在运行时会报错。
    具体比如 Retrofit 等网络框架或者用 Gson 解析 json 至实体类时,如果没有使用 -keep 标注实体类将会导致实体类的字段名被混淆而无法解析到预想的字段中。

部分参考自 写给 Android 开发者的混淆使用手册

转载于:https://www.jianshu.com/p/992ae30aaa35

猜你喜欢

转载自blog.csdn.net/weixin_33963189/article/details/91059723