Android 混淆,新引入的D8、R8改变了什么?
本文作者
作者:CoderPig
链接:
https://juejin.cn/post/6966526844552085512
本文由作者授权发布。
引言
1、日常开发Debug包时不用混淆,正式发布Release包前开启代码混淆;
2、混淆好处① → 类、方法、变量名变成短且无意义的名字,提高反编译后代码的阅读成本;
3、混淆好处② → 删除无用的类、方法与属性,缩减了APK包的大小;
4、混淆好处③ → 对字节码进行优化,移除无用指令,应用运行更快;
5、怎么混淆 → 主项目的build.gradle设置minifyEnabled true,proguard-rules.pro加入混淆规则;
6、混淆规则哪里来 → 网上搜索通用混淆模板复制粘贴,项目依赖到的第三方库官方文档复制粘贴;
混淆具体做了啥?有看过混淆源码吗?说下底层原理...
Tips:照惯例,写下简单例子演示日常使用,走走过场,只对混淆原理感兴趣的可以跳过这Part~
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
}
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
et_url.setText("https://www.baidu.com")
bt_load.setOnClickListener {
launch {
tv_content.text = "开始加载请求..."
tv_content.text = "加载完毕,网页内容如下:\n\n\n ${loadUrl(et_url.text.toString())}"
}
}
}
private suspend fun loadUrl(url: String) = withContext(Dispatchers.IO) {
var content = ""
(URL(url).openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
content = dealResponse(inputStream)
disconnect()
}
return@withContext content
}
private fun dealResponse(inputStream: InputStream): String {
val reader = BufferedReader(InputStreamReader(inputStream))
return StringBuffer().apply {
var str = reader.readLine()
while (null != str) {
append(str)
str = reader.readLine()
}
}.toString()
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
signingConfigs {
release {
storeFile file('test.jks')
storePassword '123456'
keyAlias 'test'
keyPassword '123456'
}
}
buildTypes {
release {
// 启用代码压缩、优化及混淆
minifyEnabled true
// 启用资源压缩,需配合 minifyEnabled=true 使用
shrinkResources true
// 指定混淆保留规则
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 包签名
signingConfig signingConfigs.release
}
}
2. 混淆后App Crash日志定位问题
对崩溃堆栈轨迹进行去混淆处理或符号化解析
https://support.google.com/googleplay/android-developer/answer/6295281?hl=zh-cn
R8-Retrace.7z
https://pan.baidu.com/s/1XHhAVfCK72aGJ6dd7D52xA
Tips:没必要死记,点赞mark下,要用的时候再回来查就好,遗漏的欢迎在评论区提出~
1. 混淆设置参数
-optimizationpasses 5 # 代码混淆的压缩比例,值介于0-7,默认5
-verbose # 混淆时记录日志
-dontoptimize # 不优化输入的类文件
-dontshrink # 关闭压缩
-dontpreverify # 关闭预校验(作用于Java平台,Android不需要,去掉可加快混淆)
-dontoptimize # 关闭代码优化
-dontobfuscate # 关闭混淆
-ignorewarnings # 忽略警告
-dontwarn com.squareup.okhttp.** # 指定类不输出警告信息
-dontusemixedcaseclassnames # 混淆后类型都为小写
-dontskipnonpubliclibraryclasses # 不跳过非公共的库的类
-printmapping mapping.txt # 生成原类名与混淆后类名的映射文件mapping.txt
-useuniqueclassmembernames # 把混淆类中的方法名也混淆
-allowaccessmodification # 优化时允许访问并修改有修饰符的类及类的成员
-renamesourcefileattribute SourceFile # 将源码中有意义的类名转换成SourceFile,用于混淆具体崩溃代码
-keepattributes SourceFile,LineNumberTable # 保留行号
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod # 避免混淆注解、内部类、泛型、匿名类
-optimizations !code/simplification/cast,!field/ ,!class/merging/ # 指定混淆时采用的算法
2. 保持不被混淆的设置
[保持命令] [类] {
[成员]
}
-keep # 防止类和类成员被移除或被混淆;
-keepnames # 防止类和类成员被混淆;
-keepclassmembers # 防止类成员被移除或被混淆;
-keepclassmembernames # 防止类成员被混淆;
-keepclasseswithmembers # 防止拥有该成员的类和类成员被移除或被混淆;
-keepclasseswithmembernames # 防止拥有该成员的类和类成员被混淆;
具体的类。
访问修饰符→ public、private、protected。
通配符(*) → 匹配任意长度字符,但不包含包名分隔符(.)。
通配符(**) → 匹配任意长度字符,且包含包名分隔符(.)。
extends→ 匹配实现了某个父类的子类。
implements→ 匹配实现了某接口的类。
$→ 内部类。
匹配所有构造器→ <init>。
匹配所有域→ <field>。
匹配所有方法→ <methods>。
访问修饰符→ public、private、protected。
除了*和**通配符外,还支持***通配符,匹配任意参数类型。
...→ 匹配任意长度的任意类型参数,如void test(...)可以匹配不同参数个数的test方法。
# 不混淆某个类的类名,及类中的内容
-keep class cn.coderpig.myapp.example.Test { *; }
# 不混淆指定包名下的类名,不包括子包下的类名
-keep class cn.coderpig.myapp*
# 不混淆指定包名下的类名,及类里的内容
-keep class cn.coderpig.myapp* {*;}
# 不混淆指定包名下的类名,包括子包下的类名
-keep class cn.coderpig.myapp**
# 不混淆某个类的子类
-keep public class * extends cn.coderpig.myapp.base.BaseFragment
# 不混淆实现了某个接口的类
-keep class * implements cn.coderpig.myapp.dao.DaoImp
# 不混淆类名中包含了"entity"的类,及类中内容
-keep class **.*entity*.** {*;}
# 不混淆内部类中的所有public内容
-keep class cn.coderpig.myapp.widget.CustomView$OnClickInterface {
public *;
}
# 不混淆指定类的所有方法
-keep cn.coderpig.myapp.example.Test {
public <methods>;
}
# 不混淆指定类的所有字段
-keep cn.coderpig.myapp.example.Test {
public <fields>;
}
# 不混淆指定类的所有构造方法
-keep cn.coderpig.myapp.example.Test {
public <init>;
}
# 不混淆指定参数作为形参的方法
-keep cn.coderpig.myapp.example.Test {
public <methods>(java.lang.String);
}
# 不混淆类的特定方法
-keep cn.coderpig.myapp.example.Test {
public test(java.lang.String);
}
# 不混淆native方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 不混淆枚举类
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#不混淆资源类
-keepclassmembers class **.R$* {
public static <fields>;
}
# 不混淆自定义控件
-keep public class * entends android.view.View {
*** get*();
void set*(***);
public <init>;
}
# 不混淆实现了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();
}
# 不混淆实现了parcelable接口的类成员
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 注意事项:
#
# ① jni方法不可混淆,方法名需与native方法保持一致;
# ② 反射用到的类不混淆,否则反射可能出问题;
# ③ 四大组件、Application子类、Framework层下的类、自定义的View默认不会被混淆,无需另外配置;
# ④ WebView的JS调用接口方法不可混淆;
# ⑤ 注解相关的类不混淆;
# ⑥ GSON、Fastjson等解析的Bean数据类不可混淆;
# ⑦ 枚举enum类中的values和valuesof这两个方法不可混淆(反射调用);
# ⑧ 继承Parceable和Serializable等可序列化的类不可混淆;
# ⑨ 第三方库或SDK,请参考第三方提供的混淆规则,没提供的话,建议第三方包全部不混淆;
3. 混淆规则的叠加
①
/ proguard-rules.pro
②proguard-android-optimize.txt
/build/intermediates/proguard-rules/debug/aapt_rules.txt
④ AAR库 →
/proguard.txt
⑤ Jar库 →
/META-INF/proguard/
# 输出所有规则叠加后的混淆规则
-printconfiguration ./build/outputs/mapping/full-config.txt
4. 资源压缩
依赖项 → 主资源 → 渠道 → 构建类型
<?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"
<!-- 开启严苟模式,可选值strict,safe,前者严格按照keep和discard指定的资源保留 -->
<!-- 后者保守删除未引用资源,如代码中使用Resources.getIdentifier()引用的资源会保留 -->
tools:shrinkMode="strict"/>
android {
...
defaultConfig {
resConfigs "zh-rCN" // 不用支持国际化只需打包中文资源
}
}
Tips:此结点内容部分抽取自《Android CPU, Compilers, D8 & R8》,阅读原文可能更加容易理解~ https://juejin.cn/post/6844903936227278861
类加载器:加载编译后的
.class ,链接、检测损坏的字节码,定位并初始化静态变量及静态代码;运行时数据:栈、堆、方法变量等;
执行引擎:执行已经加载的代码、清理生成的所有垃圾(gc);
当使用当前Android版本不支持的高版本jdk语法时,在编译期转换为其支持的低版本jdk语法。
1、ProGuard→ 压缩、优化和混淆Java字节码文件的免费工具,开源仓库地址:proguard
https://github.com/Guardsquare/proguard
2、R8→ ProGuard的替代工具,支持现有ProGuard规则,更快更强,AGP 3.4.0或更高版本,默认使用R8混淆编译器。
android.enableR8=false
android.enableR8.libraries=false
mapping.txt → 原始与混淆过的类、方法、字段名称间的转换;seeds.txt → 未进行混淆的类与成员;usage.txt → APK中移除的代码;resources.txt → 资源优化记录文件,哪些资源引用了其他资源,哪些资源在使用,哪些资源被移除;
# 输出mapping.txt文件
-printmapping ./build/outputs/mapping/mapping.txt
# 输出seeds.txt文件
-printseeds ./build/outputs/mapping/seeds.txt
# 输出usage.txt文件
-printusage ./build/outputs/mapping/usage.txt
1、
2、
3、
https://developer.android.google.cn/studio/command-line/d8?hl=zh-cn
1. R8 - 执行前
2. R8 - Java代码压缩
1、
2、
3、
// 前面获取dex的文件列表、混淆列表等,初始化R8Transform实例时传入
R8Transform transform =
new R8Transform(
variantScope,
userMainDexListFiles,
userMainDexListProguardRules,
inputProguardMapping,
variantScope.getOutputProguardMappingFile());
// 处理混淆规则,callback用于在混淆后执行后续操作
return applyProguardRules(
variantScope,
inputProguardMapping,
variantScope.getOutputProguardMappingFile(),
testedVariantData,
transform,
callback);
// This is a "normal" variant in an app/library.
applyProguardConfigForNonTest(transform, variantScope);
// 初始化了一个r8CommandBuilder实例
val r8CommandBuilder = CompatProguardCommandBuilder(!useFullR8, D8DiagnosticsHandler(messageReceiver))
// 然后调用一系列方法,如混淆相关
addMainDexRules()
setMainDexListConsumer()
addProguardConfigurationFiles()
addProguardConfiguration()
setProguardMapOutputPath()
// 配置相关:是否禁用缩小、摇树、脱糖、编译模式
setDisableMinification(toolConfig.disableMinification)
setDisableTreeShaking(toolConfig.disableTreeShaking)
setDisableDesugaring(toolConfig.disableDesugaring)
setMode(compilationMode)
setProgramConsumer(programConsumer)
...
// 初始化r8ProgramResourceProvider实例,用来给R8提供所有资源
val r8ProgramResourceProvider = R8ProgramResourceProvider()
// 各种传参设置
// 最后调用R8.run()
ClassFileProviderFactory(libraries).use { libClasspath ->
r8CommandBuilder.addLibraryResourceProvider(libClasspath.orderedProvider)
R8.run(r8CommandBuilder.build())
}
代码删除:通过语法树静态分析技术、发现并删除未使用的代码,如未实例化的Class等;
代码优化:对运行时代码进行优化,删除死代码、未使用的参数,选择性内联、类合并等;
代码混淆:优化标识符名字,减少代码量,会判断混淆规则中是否允许修改标识符名字;
行号重新映射 等。
3. R8 - 资源压缩
// ① 创建资源优化记录文件
File reportFile = null;
if (params.mappingFile != null) {
File logDir = params.mappingFile.getParentFile();
if (logDir != null) {
reportFile = new File(logDir, "resources.txt");
}
}
// ② 分析资源及使用情况
analyzer.analyze();
// ③ 重写.ap_文件(上面AAPT2生成的),去掉没有用到的资源,实际上是没有删除本地资源的!
analyzer.rewriteResourceZip(params.uncompressedResourceFile, params.compressedResourceFile);
// ③ 导出统计数据
4. R8 - 调D8拆Dex
﹢
﹣
×
÷
...太长省略
-obfuscationdictionary ./dictionary
-classobfuscationdictionary ./dictionary
-packageobfuscationdictionary ./dictionary
# Project类型,配置方法同app模块
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
# AAR类型
android {
defaultConfig {
consumerProguardFiles 'lib-proguard-rules.txt'
}
...
}
写给Android开发者的混淆使用手册
https://juejin.cn/post/6844903503177990158
Android CPU, Compilers, D8 & R8
https://juejin.cn/post/6844903936227278861
关于D8/R8那些事:Desugaring脱糖、APK包体积优化等
https://blog.csdn.net/vitaviva/article/details/109422961
Android 兼容 Java 8 语法特性的原理分析
https://tech.meituan.com/2019/10/17/android-java-8.html
库模块可以包含自己的 ProGuard 配置文件
https://developer.android.com/studio/projects/android-library?hl=zh-cn#Considerations
缩减、混淆处理和优化应用
https://developer.android.com/studio/build/shrink-code?hl=zh-cn
Android | 代码混淆到底做了什么?
https://juejin.cn/post/6930648501311242248
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!