0x0、引言
Android代码混淆,老生常谈了,不过大部分Android仔对它的认识可能还处于这样的阶段(比如:写这篇文章前的我):
- 1、日常开发Debug包时不用混淆,正式发布Release包前开启代码混淆;
- 2、混淆好处① → 类、方法、变量名变成短且无意义的名字,提高反编译后代码的阅读成本;
- 3、混淆好处② → 删除无用的类、方法与属性,缩减了APK包的大小;
- 4、混淆好处③ → 对字节码进行优化,移除无用指令,应用运行更快;
- 5、怎么混淆 → 主项目的
build.gradle
设置minifyEnabled true
,proguard-rules.pro
加入混淆规则;
- 6、混淆规则哪里来 → 网上搜索通用混淆模板复制粘贴,项目依赖到的第三方库官方文档复制粘贴;
大都止步于此,好一点的还知道下 ProGuard
听过 R8
,了解 混淆配置语法
,会 自定义混淆规则
。
会上面这些,日常开发已经 很够用了,但是现在IT行业这么 "卷",面试时,面试官问下:
混淆具体做了啥?有看过混淆源码吗?说下底层原理...
所以本节稍微深入点探索下Android中的代码混淆~
0x1、日常使用
Tips:照惯例,写下简单例子演示日常使用,走走过场,只对混淆原理感兴趣的可以跳过这Part~
1. 混淆前后的APK对比
新建项目,引下Kotlin相关依赖,协程等 (app层级的build.gradle):
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' }
新建MainActivity.kt,请求URL,加载内容:
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() } }
运行下康康:
app层级的build.gradle加下release的签名和编译配置:
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 } }
执行 gradle assemble
打下包,静待打包完毕,先康康Debug包:
裸奔,把APK直接拖到反编译工具 jadx-gui
里,看代码无压力:
再康康Release包:
体积着实少了一些,而且变量名都变成了abcd,顺带提下这个**Load Proguard mappings
**,点击加载混淆文件(mapping.txt)后可以去掉代码混淆:
同样拖到jadx-gui里康康:
可读性明显降低~
2. 混淆后App Crash日志定位问题
不知道你有没有想过:混淆后的APK如果报错,日志信息会是怎样的呢?
改下代码验证下,直接在点击处抛出一个空指针异常试试康:
单凭这里的b.b.a.a.onClick(Unknow Srouce:2),似乎很难直接定位到错误代码的真实位置。
一种低效的解决方法:自行对照混淆后生成的 mapping.txt
文件,比如直接搜上面的b.b.a.a:
顺着往下看不难发现问题所在,但日常开发不建议用此法,这里好找只是因为示例代码简单,推荐另一种方法:
去混淆
,如果你的应用有发布到Google Play的话,可以照着官方文档走:
没有上传到Google Play也没关系,直接用 android-
sdk/tools/proguard/bin/proguardgui.bat
:
ProGuard和R8的混淆都可以去,老版本的脚本可能不支持R8,更新下sdk即可,不想更新的也可以直接用我提供的脚本包去R8混淆,下载地址(提取码: 1234):R8-Retrace.7z,使用流程如下图所示:
去混淆前后对比:
可以的,日常使用就讲到这里,接着过一下混淆的详细规则。