去除 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 中相对应的映射关系

apk_monitor_plugin_doc_resource_arsc.png

实现

既然有了想法,当然是避免重复造轮子,先从 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.xmlresources.arscdrawable 等等文件。

我们需要在打包流程中在该 Task 后增加一个 Task,对 resources-${variantName}.ap_文件进行处理即可

1
2
3
4
 val processResource = project.tasks.getByName("process${variantName}Resources")
processResource.doLast {
//xxxxx
}

问题

无法在 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 为目标平台的应用

如果以 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

apk_monitor_plugin_doc_zipalign.png

解决方案

那是不是就没有办法了?

那倒也不是,我们在删除重复的资源文件和修改 resource.arsc 后,需要对文件重新压缩为 .ap_ 文件,在这个时候对于 resources.arsc 文件的压缩方式设置为 STORED 即可

如下:

1
2
3
4
5
6
7
8
9
10
val entry = ZipEntry(subPath).apply {
setLevel(ZipOutputStream.STORED)
setMethod(ZipEntry.STORED);
setCompressedSize(file.length());
setSize(file.length());
val crc = CRC32();
crc.update(file.readBytes())
setCrc(crc.getValue());
}
putNextEntry(entry)

结果

通过以上方式修改后,可见 resource.arsc 文件没有被压缩了,也能顺利在 Android R 以上的设备上正常安装了

apk_monitor_plugin_doc_zipalign_after_stored.png

作者

PPTing

发布于

2022-03-28

更新于

2022-03-29

许可协议

评论