去除 Apk 中的重复资源文件
在 Android Apk 包体积治理的项目中,对于资源文件的治理是其中很重要的一部分,其中,做好前期的压缩删除等工作是前提条件,但在后续的开发迭代过程中,如何进行长期的治理、预警和自动化,是值得深入探讨的问题
在包体积治理的过程中,我们知道对于 apk 的组成,其中资源文件占了很大的一部分,需要对其进行处理
首当其冲就是压缩图片资源,png 转为 webp,将没有 alpha 通道的 png 转为 jpg,将剩余的 png 文件进行压缩,但不在本文的讨论范围内,略过下次再表
在我们的项目中,由于项目组件化的架构,各个业务 module 不存在依赖关系因此无法相互访问,由于一些历史原因,部分资源文件也不在 UI 库中,导致开发过程中各业务同学常见的做法就是「从 A Module中的资源文件夹拷贝资源文件到 B Module 中,并加上前缀」导致最终的 apk 中会存在多个文件一样,但仅仅是名称不同的资源文件,导致 apk 中存在多份冗余文件
于是这个插件应运而生,用来在编译期去除 apk 中的重复资源文件,当然也会输出对应的日志文件,可以用来作为预警日志提醒开发同学引入了重复的文件,检查是否必要
我们知道 resources.arsc 文件类似于一个 table,存储了资源文件的 id 及其对应的 dpi 的文件路径,如下图所示
于是我们的想法就是将相同的资源文件删除,并修改其在 resources.arsc 中相对应的映射关系
实现
既然有了想法,当然是避免重复造轮子,先从 Google 搜索了一番解决方案,找到美团的一份技术文档 Android App包瘦身优化实践 以及 Github 上的 Optimizeapp 项目,借鉴这两个项目,编写了自己的去重 Gradle 插件,现已开源,详见 ApkMonitorPlugin
方法
Android 的打包流程中,会将 res 中的资源文件等记录到 resources.arsc
文件中,我们需要在这个产物生成后,对齐进行处理
而 process${variantName}Resource
Task 会在 build/intermediates/processed_res/${variantName}/out
目录下生成 resources-${variantName}.ap_
压缩文件,其中包含了 AndroidManifest.xml
、resources.arsc
、drawable
等等文件。
我们需要在打包流程中在该 Task 后增加一个 Task,对 resources-${variantName}.ap_
文件进行处理即可
1 | val processResource = project.tasks.getByName("process${variantName}Resources") |
问题
无法在 Android 11 设备上安装
当使用上述的方法对 resource.arsc
中的映射关系进行替换后,在 targetSdk < 30 的项目中测试正常,但当 targetSdk ≥ 30 并在 Android 11 的设备上运行,却会安装失败,提示以下的错误:
-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary
在 Android R+(即 sdkVersion ≥ 30) 以后,Android 不允许压缩 resource.arsc
文件,原因在于
如果以 Android 11(API 级别 30)或更高版本为目标平台的应用包含压缩的 resources.arsc 文件或者如果此文件未按 4 字节边界对齐,应用将无法安装。如果存在其中任意一种情况,系统将无法对此文件进行内存映射。无法进行内存映射的资源表必须读入 RAM 中的缓冲区,从而给系统造成不必要的内存压力,并大大增加设备的 RAM 使用量。
检查
我们可以通过 zipalign 工具检查 apk 中的文件是否被压缩
zipalign 可执行文件可以在 Android Sdk 的 buildTools 中找到,例如 /Users/XXX/Library/Android/sdk/build-tools/31.0.0/zipalign
我们将打包出来的 apk 使用 zipalign 进行对齐检查,可以看到 resources.arsc
的确是被压缩了
1 | zipalign -c -v 4 app-debug.apk | grep arsc |
解决方案
那是不是就没有办法了?
那倒也不是,我们在删除重复的资源文件和修改 resource.arsc
后,需要对文件重新压缩为 .ap_
文件,在这个时候对于 resources.arsc
文件的压缩方式设置为 STORED
即可
如下:
1 | val entry = ZipEntry(subPath).apply { |
结果
通过以上方式修改后,可见 resource.arsc
文件没有被压缩了,也能顺利在 Android R 以上的设备上正常安装了