Android编译优化之混淆配置
背景
为了使用java8及后续java新版本的特性,Google增加了一步编译过程—脱糖(desugaring),但这一步会导致更长的编译时间,这也是为什么Google会推出D8和R8编译器来优化编译速度。
什么是脱糖?
脱糖 即在编译阶段将在语法层面一些底层字节码不支持的特性转换为基础的字节码结构,(比如 List 上的泛型脱糖后在字节码层面实际为 Object); Android 工具链对 Java8 语法特性脱糖的过程可谓丰富多彩,当然他们的最终目的是一致的:使新的语法可以在所有的设备上运行。
D8
D8的功能是将Java字节码转化成dex代码,D8作为DX的一个替代方案。编译流程如下图所示:
D8
Android Studio 3.1版本开始,将D8作为默认的Dex编译器。如果想关闭D8,可以在gradle.properties里添加如下配置:
android.enableD8=false android.enableD8.desugaring=false
开启D8的好处
• 编译更快、时间更短
• DEX编译时占用内容更小
• .dex文件更小
• D8编译的.dex文件拥有相同或者更好的运行性能
如果你的工程已经使用Java 8尽可能开启D8编译,不然可能会出现编译错误。
R8
R8作为原本Proguard 压缩与优化(minification、shrinking、optimization)部分的替代品,依然使用与Proguard一样的keep规则,是新一代的代码压缩工具。 R8之前采用D8+Proguard的形式构建,R8则将混淆和D8工具进行整合,目的是加速构建时间和减少输出apk的大小。
R8
Gradle插件版本达到3.4.0及以上,默认会开始R8进行代码优化。如果你不想开启R8,可以在gradle.properties里添加如下配置:
android.enableR8=false
开启R8的好处
• 代码缩减:规避64引用限制
• 资源缩减:移除不使用的资源
• 混淆代码:减小DEX文件大小
• 优化代码:进一步减小DEX文件大小
相关评测报告
D8R8评测报告
AS多模块混淆配置
AS多模块下,我们可以采用各个模块单独配置方式,即proguard文件配置在各个模块下,在各自的build.gradle文件中引入; 也可以采用下方集中配置方式,将各模块的proguard文件集中配置到一个文件夹下,然后在app模块中集中引入所依赖的模块的proguard文件。 下面以cmcc_service这个app模块为例加以说明,
buildTypes { release { minifyEnabled true //开启混淆 shrinkResources true //无用资源去除 zipAlignEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro', '../proguardDefine/common-rules.pro', '../proguardDefine/live_lib-rules.pro', '../proguardDefine/gs_api_adapter-rules.pro', '../proguardDefine/cmcc_service.pro', '../proguardDefine/lib_voiceassistant-rules.pro','../proguardDefine/cmcc_softprobe-rules.pro' if (propertyHaveSigningConfigs) signingConfig signingConfigs.release } }
其中,proguard-android-optimize.txt是android默认的一些通用混淆规则,proguard-rules.pro是app代码的混淆规则,proguardDefine文件夹下所依赖模块统一放置proguard规则的地方。
注意:引入的三方libs,一般都配有响应的proguard文件,所以不需要再重复配置,gradle编译时会将相关文件 proguard文件合并到一个configuration.txt文件中
configuration位置
proguard-android-optimize.txt
# 禁用一些代码简化和优化,以及字段和类合并 -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* # 运行优化 passes 的数量为 5,数值越高,混淆效果越好,但耗时也更长 -optimizationpasses 5 # 允许访问和修改保护代码 -allowaccessmodification # 不允许使用大小写混合的类名 -dontusemixedcaseclassnames # 不跳过非公共库类 -dontskipnonpubliclibraryclasses # 详细输出 -verbose # 保留一些反射所需的属性 -keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod # 保留以下三个类及其公共成员 -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService -keep public class com.google.android.vending.licensing.ILicensingService # 忽略以下类,不输出 note 信息 -dontnote com.android.vending.licensing.ILicensingService -dontnote com.google.vending.licensing.ILicensingService -dontnote com.google.android.vending.licensing.ILicensingService # 对于本地方法,参见 http://proguard.sourceforge.net/manual/examples.html#native # 保留包含 native 方法的类和方法 -keepclasseswithmembernames,includedescriptorclasses class * { native; } # 保留公共的 View 子类的 set 和 get 方法 # 以便于使用属性动画 -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } # 保留 Activity 中可用于 XML 属性 onClick 中的方法 -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # 对于枚举类,参见 http://proguard.sourceforge.net/manual/examples.html#enumerations # 保留枚举类成员 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # 保留实现 Parcelable 接口的类的 CREATOR 静态成员 -keepclassmembers class * implements android.os.Parcelable { public static final ** CREATOR; } # 保留 JavaScript 接口方法上的注解 -keepclassmembers class * { @android.webkit.JavascriptInterface ; } # 支持库包含对新平台版本的引用 # 在应用链接旧版平台版本时,不要发出警告,因为它们是安全的 -dontnote android.support.** -dontnote androidx.** -dontwarn android.support.** -dontwarn androidx.** # 该类已弃用,但仍然保留用于向后兼容 -dontwarn android.util.FloatMath #这段混淆规则用于保护使用了@Keep注解的类和成员不被混淆,以及忽略特定的冗余类。 -keep class android.support.annotation.Keep -keep class androidx.annotation.Keep #保留使用了@Keep注解的类和接口的所有成员 -keep @android.support.annotation.Keep class * {;} -keep @androidx.annotation.Keep class * {;} #保留使用了@Keep注解的类的方法 -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @androidx.annotation.Keep ; } #保留使用了@Keep注解的类的字段 -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @androidx.annotation.Keep ; } #保留使用了@Keep注解的类的构造方法 -keepclasseswithmembers class * { @android.support.annotation.Keep (...); } -keepclasseswithmembers class * { @androidx.annotation.Keep (...); } #忽略特定的冗余类 #android.jar和org.apache.http.legacy.jar中的类重复 -dontnote org.apache.http.** -dontnote android.net.http.** #android.jar和core-lambda-stubs.jar中的类重复 -dontnote java.lang.invoke.**
一些三方自带混淆规则
这些规则同样合并到了configuration.txt文件中 retrofit2混淆规则
# The proguard configuration file for the following section is /home/cl/.gradle/caches/transforms-3/29b6aa006718d6829551a18646bf70bb/transformed/rules/lib/META-INF/proguard/retrofit2.pro # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and # EnclosingMethod is required to use InnerClasses. -keepattributes Signature, InnerClasses, EnclosingMethod # Retain service method parameters when optimizing. -keepclassmembers,allowshrinking,allowobfuscation interface * { @retrofit2.http.*; } # Ignore annotation used for build tooling. -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement # Ignore JSR 305 annotations for embedding nullability information. -dontwarn javax.annotation.** # Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. -dontwarn kotlin.Unit # Top-level functions that can only be used by Kotlin. -dontwarn retrofit2.-KotlinExtensions # End of content from /home/cl/.gradle/caches/transforms-3/29b6aa006718d6829551a18646bf70bb/transformed/rules/lib/META-INF/proguard/retrofit2.pro
RxJava2RxAndroid混淆规则
-dontwarn java.util.concurrent.Flow*
Okhttp3混淆规则
# The proguard configuration file for the following section is /home/cl/.gradle/caches/transforms-3/af3ecb4c3ae4accf6423845d738f047d/transformed/rules/lib/META-INF/proguard/okhttp3.pro # JSR 305 annotations are for embedding nullability information. -dontwarn javax.annotation.** # A resource is loaded with a relative path so the package of this class must be preserved. -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* # OkHttp platform used only on JVM and when Conscrypt dependency is available. -dontwarn okhttp3.internal.platform.ConscryptPlatform # End of content from /home/cl/.gradle/caches/transforms-3/af3ecb4c3ae4accf6423845d738f047d/transformed/rules/lib/META-INF/proguard/okhttp3.pro
更多可能用到的混淆配置
# 指定不去忽略非公共库的类 -dontskipnonpubliclibraryclasses # 指定不去忽略非公共库的成员 -dontskipnonpubliclibraryclassmembers # 混淆时不做预校验 -dontpreverify # 忽略警告 -ignorewarnings # 保留代码行号,方便异常信息的追踪 -keepattributes SourceFile,LineNumberTable # appcompat库不做混淆 -keep class androidx.appcompat.** #保留 AndroidManifest.xml 文件:防止删除 AndroidManifest.xml 文件中定义的组件 -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.preference.Preference -keep public class * extends android.view.View #保留序列化,例如 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(); } #带有Context、View、AttributeSet类型参数的初始化方法 -keepclasseswithmembers class * { public(android.content.Context); } -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); } #保留资源R类 -keep class **.R$* {*;} #避免回调函数 onXXEvent 混淆 -keepclassmembers class * { void *(**On*Event); void *(**On*Listener); void *(**on*Changed); } #业务实体不做混淆,避免gson解析错误 -dontwarn com.grandstream.convergentconference.entity.** -keep class com.grandstream.convergentconference.entity.** { *;} #Rxjava、RxAndroid,官方ReadMe文档中说明无需特殊配置 -dontwarn java.util.concurrent.Flow* #okhttp3、okio、retrofit,jar包中已包含相关proguard规则,无需配置 #其他一些配置
Android.bp混淆配置
android_app { name: "MyApp", package_name: "com.example.myapp", srcs: ["java/**/*.java"], manifest: "AndroidManifest.xml", dex_preopt: { enabled: true, }, apk_cert_permissions: [ "android.permission.ACCESS_FINE_LOCATION", "android.permission.RECORD_AUDIO", "android.permission.READ_CONTACTS", ], certificates: ["platform"], resource_files: ["res/**/*"], enable_proguard: true, proguard_flags: ["proguard-android.txt"], dex_preopt_image_dir: "target/product/${TARGET_PRODUCT}/obj/dex_preopt_image", }
其中,enable_proguard 设置为 true 表示启用代码和资源混淆,proguard_flags 指定了 Proguard 混淆规则文件,可以在文件中指定代码和资源混淆规则。 resource_files 指定了应用程序的资源文件,包括 AndroidManifest.xml 文件。
android.mk混淆配置
LOCAL_PROGUARD_ENABLED := obfuscation optimization LOCAL_PROGUARD_FLAG_FILES := proguard.flags ifeq (eng,$(TARGET_BUILD_VARIANT)) LOCAL_PROGUARD_FLAG_FILES += proguard-test.flags else LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags endif
LOCAL_PROGUARD_ENABLED 有以下几种配置:
1. LOCAL_PROGUARD_ENABLED := disabled:禁用混淆。
2. LOCAL_PROGUARD_ENABLED := obfuscate:只开启代码混淆,不进行优化。
3. LOCAL_PROGUARD_ENABLED := optimize:只开启优化,不进行代码混淆。
4. LOCAL_PROGUARD_ENABLED := obfuscate optimize:同时开启代码混淆和优化。
编译系统默认应该会加载一个android通用的混淆文件,LOCAL_PROGUARD_FLAG_FILES用于指定app特定的proguard文件。 如果我们不太会配置这些选项,可以去查看原生应用的混淆配置,这对你会有很大启发。
混淆堆栈还原
使用 android 自带的混淆堆栈还原工具 proguardgui 解决
Android/Sdk/tools/proguard/bin$ ls proguard.sh proguardgui.sh retrace.sh /Android/Sdk/tools/proguard/bin$ ./proguardgui.sh
脚本运行后会打开 ProGuard 调试界面
proguard gui tools
填写混淆前后的映射文件 mapping.txt 和需要还原的 logcat 异常日志,点击 Retrace 按钮就能还原混淆前的异常信息
结论
总之,一些通用混淆规则Android已经帮我写好了,我们关心的可能就是我们自己开发的那部分代码的混淆规则,混淆工作做的好有很多好处: 代码更安全、包体积更小、运行速度更快,但是混淆也会带来意想不到的运行异常,希望你做好测试,希望你尽快用起来。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !