抖音包大小优化-资源优化

发布时间: 2023-02-01 14:41 阅读: 文章来源:转载
),已开源。

8.ARSC 瘦身8.1 背景

resources.arsc 这个文件在很多项目中都占用了相当多的空间。常见的优化方法是使用 AndResGuard 混淆减少文件名及目录长度,7z 压缩,如果有海外产品的话可以动态下发语言。 我们在做完这些优化后,由于公司内部有很多海外产品,涉及到多语言的关系,ARSC 依然很大,我们决定尝试进一步优化。经过调研,最终我们对 3 个方面做了优化,分别是删除无用 Name、合并字符串池中重复字符串、删除无用文案,最终带来的收益是 1.6MB。 在此之前,我们还在 AndResGuard 的基础上完成了重复 MD5 文件图片合并,原理是一样的。

8.2 原理

先贴一张 arsc 结构的图,这个二进制文件的数据结构相当复杂,AndResGuard 其实只修改了这个文件的一小部分,至于更多的修改就无能为力了,于是我们自己解析了这个文件进行分析。 网上也有不少关于这个文件格式的说明,这里就不赘述了。推荐老罗和尼古拉斯的博客以及 aapt2 源码。google 提供的 android-arscblamer 和 apktool 的代码也值得一看。

下面用一张图简单描述一下修改过程:

如图,字符串其实是通过索引的方式来获取的,所有字符串都保存在两个字符串池中(单个 package),一个是全局字符串池,一个是 package 下的字符串池,我们只需要修改指向全局字符串的偏移值就行了。name 和 value 所在二进制位置如下图。

8.3 方案8.3.1 删除无用 Name

AndResGuard 在今年的 7 月也增加了这个功能,我们来看一下实现原理。 Name 对应的字符串池是 package 字符串池,由于这个字符串池中只包含所有 Name,我们操作可以稍微暴力一点,先做一份备份,然后清空字符串池,添加一个用于替换的字符串,赋值为 [name_removed]。

首先要确定哪些 name 是通过 getIdentifier 调用,配置成白名单。 遍历 name 项,如果不在白名单,那么把这一个 name 的偏移替换成 0,使其指向[name_removed]。 如果 name 在白名单,那么不应该删除,我们通过备份的字符串池找到这个 name 对应的字符串,添加到字符串池中,把偏移指向对应下标即可。

抖音通过这个优化减少了包大小 70k。

8.3.2 合并重复字符串

value 所对应的是全局字符串池,虽然名字听起来不会有重复值,但在我们扫描排序后发现其实有很多重复字符串(用 AppBundle 打包就不会存在这个问题) 在抖音项目中,这个字符串池里有 1k+个重复字符串,合并这些字符串是非常必要的。

我们先遍历所有数据,然后把字符串池的重复字符串合并,记录偏移的修改,最后把需要修改的 value 的引用指向新的偏移。这个过程需要操作 arsc 数据结构的 ResValuel 和 ResTableMap,以保证所有 string 类型的值都能得到替换。

抖音通过这个优化减少了包大小 30k。

8.3.3 删除无用文案

在打包过程中,其实所有 strings.xml 中保存的字符串都是不会被优化的,随着项目逐渐变大,一些废弃文案或者下个版本才有用的文案被引入了 apk 中,我们在 Proguard 后再次扫描,发现了 3000+个无用字符串。在公司内部的一些海外项目中,有的文案被翻译成 100 多个国家的语言,占用了极大的空间。

删除的方法和上面类似,都是指向替换的字符串所在偏移。 如图可能会存在两个不同 name 指向同一个字符串,需要判断待删除的字符串是否还有其他引用。

不同项目收益可能不太一样,公司内部海外项目对这些无用文案进行了替换,减少了 1.5M 包大小左右。

8.4 实现

如果是普通的 assemble 打包,直接在 ProcessResources 过程中获取 ap_文件中的 arsc 文件,利用我们的工具修改即可。

如果是 AppBundle 方式打包,修改 ap_是没有用的,因为最后产物是用 aapt 以 proto 格式生成的 resources.pb 文件,要修改只能 hook aapt 过程。这个文件和 arsc 文件结构不太一样,好在我们可以使用官方提供的 Resources 类解析、生成 pb 文件,使用相似的方法修改即可。

修改效果如图:

8.5 进一步优化

arsc 中的偏移数组是有优化空间的,我们会在未来尝试进行优化。 用二进制编辑器打开 arsc 文件可以发现,这样的 FF 值在文件中大量存在。

是什么导致了这样的空间浪费? 我们可以看到下图中框选的空白,每一个都代表了其字符串所在的偏移值,这里并没有值,赋值 FF FF FF FF 作为默认偏移值,浪费了 4 字节空间。 某些列(configuration)可能就只有几个格子有值,如图抖音中 drawable 有 4k+张图片,有 24 列,大多数 configuration 只有几张图片,因此浪费了 4k*23*4380k。大致估算,抖音可以减少 1M 体积。(压缩前)

如下图 facebook 针对 arsc 文件的处理,我们可以把一行只有一个值的 id 抽出来,单独放到一个 Resource Type 中,每一个 id 只有一个值,避免了上述空间浪费情况。 但这样做修改了 ID,因此对应的代码中的 ID 也要修改,涉及了逆向 xml 以及 dex,提高了修改成本。还有一种思路是修改 aapt 源码,没有直接改 arsc 灵活。

9.总结

上述就是我们抖音 Android 端在包大小优化方面针对资源做的一些尝试和积累,力求追求极致。

我们针对包大小优化,在其他方面还做了很多优化措施:针对 so 优化,做了 so 合并、stl 版本统一、精简导出符号表和 so 压缩等措施;针对代码优化,细化混淆规则,开发 bytex 插件进行无用代码扫描、acess 方法内联、getter/setter 方法内联、删除行号等优化措施。

除了优化措施,良好的包大小监控系统是防止包大小劣化最重要的工具,否则包大小优化措施取得的收益抵不过业务快速迭代带来的包大小增长。抖音 Android 端结合 CI、Cony 平台,开发出了一套代码合入前置检查系统,每个分支增量超过阈值不准合入;还开发了分业务线监控包大小的工具,便于监控每个业务线包大小增长和给各个业务线定包大小指标。

最后,抖音 Android 诚招对技术有无限热情的小伙伴。感兴趣的小伙伴都可以通过 字节跳动招聘官网查询抖音 Android 相关职位() 或简历发送至 shipeiqing@bytedance.com。

更多分享

欢迎关注字节跳动技术团队

•••展开全文
相关文章