Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

滴滴Booster移动APP质量优化框架 学习之旅Study

Notifications You must be signed in to change notification settings

WanAndroid/BoosterDemo

Repository files navigation

滴滴Booster 内置的资源压缩 task booster-task-compression实现了如下功能:

1.删除冗余资源,保留尺寸最大的图片

2.有损压缩图片资源,内置两种压缩方案:

 1.pngquant 有损压缩(需要自行安装 pngquant 命令行工具)
 
 2.cwebp 有损压缩(已内置)

3.修改资源索引文件resources.arsc、webp图片等的zipEntry method,置为DEFLATED,减少压缩包大小

运行demo的时候发现删除冗余资源的时候,console提示删除图片失败,如下图:

test

win7系统 booster_version = '0.8.0' ,该问题已提issue#19

Booster对资源索引文件resources.arsc的压缩,只是单一设置ZipEntry.method,这是否运行时性能有影响,有大佬 这样讨论过: resources.arsc压缩会影响性能吗?Google I/O 2016 笔记:APK 瘦身的正确姿势,尚未定论。

针对resources.arsc的优化,美团还提出如下手段:

1.开启资源混淆

2.对重复的资源优化

3.无用资源优化

资源混淆见微信开源的资源混淆库AndResGuard

对重复的资源优化和对被shrinkResources优化掉的资源进行处理的原理见:美团博客 Android App包瘦身优化实践

这里根据美团讲述的原理在Booster定制task实现对重复的资源优化和对无用资源优化,详见工程module TaskCompression

一、对重复的资源优化

重复资源的筛选条件为 资源的zipEntry.crc相等,最先出现的资源压缩包产物ap_文件是在processResTask中,尽可能早的删除重复资源, 可以减少后续task的执行时间,hook在processResTask之后,如下:

variant.processResTask?.doLast{
 variant.removeRepeatResources(it.logger,results)
}

这里我按照同zipEntry.crc和同资源目录(不同资源目录可能有相同的crc资源,造成误删,不过可能性较小)去分类收集重复资源:

private fun File.findDuplicatedResources():Map<Key,ArrayList<DuplicatedOrUnusedEntry>>{
 var duplicatedResources = HashMap<Key,ArrayList<DuplicatedOrUnusedEntry>>(100)
 ZipFile(this).use { zip ->
 zip.entries().asSequence().forEach { entry ->
 val lastIndex : Int = entry.name.lastIndexOf('/')
 val key = Key(entry.crc.toString(),if(lastIndex == -1) "/" else entry.name.substring(0,lastIndex))
 if(!duplicatedResources.containsKey(key)){
 val list : ArrayList<DuplicatedOrUnusedEntry> = ArrayList(20)
 duplicatedResources[key] = list
 }
 val list = duplicatedResources[key]
 list?.add(DuplicatedOrUnusedEntry(entry.name,entry.size,entry.compressedSize,DuplicatedOrUnusedEntryType.duplicated))
 }
 }
 duplicatedResources.filter {
 it.value.size >= 2
 }.apply{
 duplicatedResources = this as HashMap<Key, ArrayList<DuplicatedOrUnusedEntry>>
 }
 return duplicatedResources
}

重复的资源优化的实现整体思路:

1.从ap_文件中解压出resources.arsc条目,并收集该条目的ZipEntry.method,为后续按照同ZipEntry.method 把改动后的resources.arsc添加到ap_文件中

2.收集重复资源

3.根据收集的重复资源,保留重复资源的第一个,从删除ap_文件中删除其他重复资源的zipEntry

4.使用通过[android-chunk-utils]修改resources.arsc全局StringChunk,把这些重复的资源都重定向到没有被删除的第一个资源

5.按照同ZipEntry.method把改动后的resources.arsc添加到ap_文件中

源码见:doRemoveRepeatResources方法

验证: 分别在App/lib module显示三张图片,重复资源如下:

text

查看没集成重复的资源优化的apk,如图:

text

使用工具查看集成重复的资源优化的apk,如图: text

集成重复的资源优化打包,控制和输出报告都可以看到如下输出:

text

可以知道删除哪些重复资源,压缩包减少了多少kb。

二、无用资源优化

通过shrinkResources true来开启资源压缩,资源压缩工具会把无用的资源替换成预定义的版本而不是移除, 那么google出于什么原因这样做了? ResourceUsageAnalyzer 注释是这样说的的:

/**
 * Whether we should create small/empty dummy files instead of actually
 * removing file resources. This is to work around crashes on some devices
 * where the device is traversing resources. See http://b.android.com/79325 for more.
 */

注释上说了适配解决某些设备crash问题,查看issue,发现发生crash的设备基本上都是三星手机,如果删除无用资源,需要考虑该issue问题。

如果采用人工移除的方式会带来后期的维护成本,在Android构建工具执行package${flavorName}Task之前通过修改 Compiled Resources来实现自动去除无用资源。

具体流程如下: * 收集资源包(Compiled Resources的简称)中被替换的预定义版本的资源名称,通过查看资源包 (Zip格式)中每个ZipEntry的CRC-32 checksum来寻找被替换的预定义资源,预定义资源的CRC-32定义在 ResourceUsageAnalyzer, 下面是它们的定义:

	// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY
 public static final long TINY_PNG_CRC = 0x88b2a3b0L;
 // A 3x3 pixel PNG of type BufferedImage.TYPE_INT_ARGB with 9-patch markers
 public static final long TINY_9PNG_CRC = 0x1148f987L;
 // The XML document <x/> as binary-packed with AAPT
 public static final long TINY_XML_CRC = 0xd7e65643L;
 
 // The XML document <x/> as a proto packed with AAPT2
 public static final long TINY_PROTO_XML_CRC = 3204905971L;

从定义中没有看到webp、jpg、jpeg相关的crc,那么这些没有定义crc-32的资源在ZipEntry中crc为多少了,用预定义资源替换未使用的地方的实现如下:

private void replaceWithDummyEntry(JarOutputStream zos, ZipEntry entry, String name)throws IOException {
 // Create a new entry so that the compressed len is recomputed.
 byte[] bytes;
 long crc;
 if (name.endsWith(DOT_9PNG)) {
 bytes = TINY_9PNG;
 crc = TINY_9PNG_CRC;
 } else if (name.endsWith(DOT_PNG)) {
 bytes = TINY_PNG;
 crc = TINY_PNG_CRC;
 } else if (name.endsWith(DOT_XML)) {
 switch (format) {
 case BINARY:
 bytes = TINY_BINARY_XML;
 crc = TINY_BINARY_XML_CRC;
 break;
 case PROTO:
 bytes = TINY_PROTO_XML;
 crc = TINY_PROTO_XML_CRC;
 break;
 default:
 throw new IllegalStateException("");
 }
 } else {
 //没有预定资源格式,crc =0,数据为空
 bytes = new byte[0];
 crc = 0L;
 }
 JarEntry outEntry = new JarEntry(name);
 if (entry.getTime() != -1L) {
 outEntry.setTime(entry.getTime());
 }
 if (entry.getMethod() == JarEntry.STORED) {
 outEntry.setMethod(JarEntry.STORED);
 outEntry.setSize(bytes.length);
 outEntry.setCrc(crc);
 }
 zos.putNextEntry(outEntry);
 zos.write(bytes);
 zos.closeEntry();
 ...
 }

可以得出筛选无使用资源的条件为crc in如下集合中:

val unusedResourceCrcs = longArrayOf(
 ResourceUsageAnalyzer.TINY_PNG_CRC,
 ResourceUsageAnalyzer.TINY_9PNG_CRC,
 ResourceUsageAnalyzer.TINY_BINARY_XML_CRC,
 ResourceUsageAnalyzer.TINY_PROTO_XML_CRC,
 0 //jpg、jpeg、webp等
)

如果集成了

打印packageAndroidTask的inputFiles,如下:

test

分别查看箭头目录下的文件,有*.ap_文件,

test

而从上面两图中可以了解到shrinkResources 影响到packageAndroidTask的inputFiles,没有开启shrinkResources, packageAndroidTask从processedResTask产物中读取ap_文件,开启shrinkResources,从res_stripped目录下读取ap_文件, 根据其stripped名,也猜测出ap_文件中已经进行了预定资源替换未使用资源了,可以压缩软件查看未使用资源的zipEntry.crc 进行验证,如下图:

test

可以看到没有使用的webp、jpg资源的ZipEntry.crc为0;如果集成了Booster内置的booster-task-compression, 会把png格式转换成webp格式,没使用的png最后的crc会变为0.

删除无用资源方案想到两种:

方案一:删除所有无用资源文件,以及删除资源索引文件resources.arsc中global StringChunk有关无用资源的数据项。

缺点:删除了global StringChunk中的数据项,改变了后续数据项的索引值,好比删除List中的元素,后续的元素索引值减一一样,牵一发动全身,需要同步其他chunk索引到global StringChunk数据项的索引值。否则会出现资源显示混乱,甚至crash;同时需要考虑上述issue问题。对resources.arsc越大出现问题的概率越大

方案二:无用资源根据crc分类,再按照重复资源优化,没有删除global StringChunk数据项,没有改变数据项的索引值,不需要改动其他chunk,同时不会出现上述issue问题。

下面对方案二具体实现,方案一就不做讨论了。

无用资源优化的代码实现整体思路:

1.从ap_文件中解压出resources.arsc条目,并收集该条目的ZipEntry.method,为后续按照同ZipEntry.method 把改动后的resources.arsc添加到ap_文件中

2.收集无用资源

3.把收集的无用资源根据crc进行分类,在按照重复资源优化处理

源码见:doRemoveUnusedResources方法

集成无用资源优化打包,控制和输出报告都可以看到如下输出:

text

可以知道删除哪些无用资源,压缩包减少了多少kb,无用资源优化减少的size没多少。

以上重复资源优化和无用资源优化,没有经过大量设备测试,仅供参考学习。

推荐阅读:

滴滴Booster移动App质量优化框架-学习之旅 一

Android 模块Api化演练

不一样视角的Glide剖析(一)

关注公众号:

test

About

滴滴Booster移动APP质量优化框架 学习之旅Study

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Kotlin 89.3%
  • Java 10.7%

AltStyle によって変換されたページ (->オリジナル) /