前介
一个 Apk 主要分为 res 丶 dex 丶 META-INF 丶 AndroidManifest.xml 丶 resources.arsc 组成(对应文件作用看下方表格)。
从图片可以看到一个项目的 res 占很大一部分内存,其次就是 dex 文件。所以优化 Apk 的体积就应该从 res 丶 dex 出发。
| 昵称 | 介绍 |
|---|---|
| res | Apk 资源,代码中对应的 res 目录下的所有通过 AAPT 编译的产物。 |
| dex | 所有的 Class 文件的归宿 |
| META-INF | 报错通过 V1 签名的签名信息 |
| AndroidManifest.xml | 通过 AAPT 优化编译的清单文件 |
| resources.arsc | 通过 AAPT 编译资源生成的产物,主要存放资源 ID (R 类生成的 ID )和 res 目录下的资源文件对应关系 |
定位目标
前面都是一堆准备工作,我们来明确目标。最终要完成在 APK 构建过程中,将 res 目录下所有的图片进行 去重丶 压缩处理 丶 混淆。
寻找合适的Task
确定了目标后,我们就知道优化的目标在 Apk 的 res 目录下所有的资源文件,由于存在对 res 目录的文件进行 改(混淆) 丶 删(去重) 的情况,在修改的同时还需要修改 resources.arsc 文件。因为前面讲了,这个文件中存着一份 ID 和 资源的映射关系。
这里大家是不是就能衍生出换肤的难点在哪了吧?要保证新生成的皮肤
resources.arsc的ID要和宿主的ID保持一致。这样代码中才能正确的通过ID找资源,其实R也是AAPT插件生成的哦。学完这篇我相信大家也能做一款属于自己的换肤插件。
接下来思路就清晰了。我的第一想法是在生成 resources.arsc 前,优化 res 目录下面所有的图片。这样就规避掉了
修改 resources.arsc 的难点。
通过第一章代码我还真找到了一个 mergeDebugResources 的 Task 它的作用就是合并资源,包含依赖的资源(如何找呢?通过我上一篇分享的文章,看每个 Task 的输入路径)。但是回头一想,这样就不能做去重复的功能了,因为我如果删除重复的资源,代码引用并不知道啊!(因为我的删除重复资源是相同文件,并不是简单昵称相同)。
到这里,我们分析结束,只能在生成 resources.arsc 之后做优化操作,并修改 resources.arsc 文件了。哪 resources.arsc 是多久生成的呢?我们来看下面的一张图。
AAPT 来打包 res 资源文件,生成 R.java 、resources.arsc 和 res文件。我们只需有找到一个执行 AAPT 的 Task 在这个 Task 之后做操作。
其实我当时中间有另外一种想法,能不能自己改造一个
aapt呢?查阅资料思路是可行的,但是过于复杂和兼容性不强(主要需要改C代码,我不会),有兴趣可以参考
接下来我们应该找一个执行 AAPT 的 TASK,接着用我前面讲的输出所有 Task 的输入和输出路径,寻找产物有 resources.arsc 的 Task。
我在
build目录中找了半天resources.arsc文件,但是死活找不到。
最后发现了一个 processDebugResources 的 Task。
- 调用
aapt生成项目和所有aar依赖的R.java, 输出到app/build/generated/source/r/debug目录 - 生成资源索引文件
app/build/intermediates/res/resources-debug.ap_ - 把符号表输出到
app/build/intermediates/symbols/debug/R.txt
这个 Task 的产物是 resources-debug.ap_ (就是个 zip 文件)解压下就可以所有的数据了。
终于看到 resources.arsc 并且还附带了 res 中所有的资源了。
没事干你整个压缩包干嘛呀,害的我找了好久。
最后只需要在 processDebugResources Task 之后,解压 resources-debug.ap_ 文件,遍历 res 下所有的图片进行去重丶压缩丶混淆丶修改 resources.arsc ,从新压缩改造内容,替换 resources-debug.ap_ 文件即可。
源代码
QOptimize 插件是一款,在 Apk 编译期间对 Apk 进行体积优化的一款插件。