之前对公司的项目做了一些列APK体积的优化,其中涉及到一个小点,相同资源因命名方式的不同,在构建时都打入到APK中,导致体积增大。
一、背景
目前大部分中大型App的研发是由各个业务团队共同协作完成,这种组件化的开发模式就会存一个常见的问题, 不同的业务团队在使用各资源文件(如:drawable、layout等)时,为了避免引发资源覆盖的问题,每
个业务团队都会为自己的资源文件名添加前缀。这样就会导致有些资源文件内容其实相同,但因为名称的不同而重复存在,最终在构建时都加入到APK包中,因而导致包体积增大。为了解决这类“重复资源”的问题,
但又不想增加各团队在协同相同资源开发的成本,因而采用在App构建时自动删除重复的资源文件,只保留其中一份, 而 其它相同资源都指向这个文件,这样对开发人员变得相对透明,不必关心资源是否重复,
在构建时也避免了APK体积不必要的增大。从收益的角度来看,减少apk大小因业务而定,但构建时间会有所增加。
二、原理
在介绍完背景后,接下来介绍一下插件所涉及到的内容以及主要逻辑。如果简单点说整个流程:就是通过侵入APK 的构建流程,修改中间产物达到想要的目的,而且不影响最终APK的稳定性。
在APK构建的过程中通过Package Task把dex、resources、assets、so等文件合并成一个APK。而在APK产生之 前,其实有个中间产物——*.ap_文件,这也是与资源最早相关的内容,它是processResource
Task的产物。 可以 在terminal中手动输入命令: ./gradlew process${variant}Resources ,或者使用Android Studio
中提供的Gradle面板,找到相应的模块点击process${variant}Resources需要执行的命令,当命令执行完以后, 会在build/intermediates/processed_res生成*.ap_产物:
这里的resources-chinaDebug.ap_ 文件其实就是个普通的 ZIP 文件,可以通过unzip命令解压出来。
$ unzip -lv resources-chinaDebug.ap_
从中看出resources-chinaDebug.ap_ 里面包含内容是:
AndroidManifest.xml
res文件夹
resources.arsc
Hook的原则是尽可能早的介入到构建流程中,因为如果能在最开始task处删除重复资源,这样就可以减少后续task 的执行时间。那么processResource Task
就是我们需要Hook的task,通过操作processResource Task的 产物*.ap_文件,删除res文件夹内重复资源,然后修改resources.arsc映射表,就可以达到想要的目的。
起始入口代码如下:
1 | variant.processResTask?.doLast { |
在确定插件Hook的task后,第二步需要对重复资源的定义,这里采用比较保守的方案:资源包中每个zipEntry.crc 和资源目录相同,只有同时满足这两个条件才会被定义为重复资源。
PS:起初判断资源是否相同,第一反应想到的就是文件的MD5值是否相同,而这里本身就是从ZIP文件解压出来,那完全可以通过获取未压缩数据条目的CRC-32进行校验,这样也可避免计算文件MD5值的耗时。
明确了Hook的Task,以及对重复资源的定义。接下来就是对文件的解压缩以及IO操作,这些都没什么可讲的。可能比较 不
好理解的点是如何操作resources.arsc文件,但没关系可通过android-chunk-util,这是一个用java编写的 resources.arsc文件解析工具,通过该工具可以帮助理解resources.arsc文件的结构,
同时通过该工具也可以更 改resources.arsc文件内容。至于resources.arsc文件的结构,这里由于篇幅的原因这就不做介绍, 感兴趣的同
学可查看:安卓resources.arsc解析教程
经过重复资源优化处理后,resources.arsc文件的结构如下图所示,每个资源代表一个chunk,假如以下3个chunk中的 资源相同,则处理后它们会指向相同的路径。
主要代码如下:
1 | //移除不同命名的重复资源 |
- 从*.ap_文件中解压出res文件夹和resources.arsc文件;
- 收集资源放入到map中,map中key对象由zipEntry.crc和资源目录组成;
- 通过2中的map再结合*.ap_文件,重新得到没有重复资源的*.ap_文件;
- 同样在此通过2中map和android-chunk-utils库操作resources.arsc中ResourceTableChunk,把重复的资源重定向到一个文件上。得到一个新的resources.arsc表;
- 把3中新得到的*.ap_文件内的resources.arsc文件删除掉;
- 再把4中新的resources.arsc放入到*.ap_中,最终生成一个没有重复资源且resources.arsc做了重定向的*.ap_文件。
为了能够更加直观的展示插件的运行过程,做了个小动画动态的把每个步骤串行起来。如下所示,从开头到结束,其实就是从 原始的resource.ap_文件得到一个全新的resource.ap_文件。 
四、最后
从收益角度来说,重复资源优化可能对APK体积的贡献并不那么突出。但如果你的APP本身已经做了一轮体积优化,而又恰好 采用组件化的形式开发(目前大部分app都是这种模式),那么重复资源的优化还是很有必要的。而且从技术的角度带来增
长是不错的,它涉及到的内容从插件的开发、APK的构建流程、资源的加载以及映射表的结构等。