如上图所示,优化前 caller 方法的 invoke 指令使用的是子类引用,其伪指令如下所示,需要用到 2 个引用
1 new-instance v0, Sub1
2 invoke-virtual v0, Sub1.a()
3 new-instance v1, Sub2
4 invoke-virtual v1, Sub2.a()
优化后,invoke 指令都指向其父类应用,2 个引用可以合并为 1 个,减少了 DEX 中的引用数量
1 new-instance v0, Sub1
2 invoke-virtual v0, Base.a()
3 new-instance v1, Sub2
4 invoke-virtual v1, Base.a()
针对编程语言的优化KotlinDataClassPass
该 Pass 是对 Kotlin data class 的优化,基本思路是对 data class 的生成代码进行精简。
- 解构声明优化
Kotlin 中存在解构声明这种语法,可以更方便的创建多个变量,基本用法如下
1 data class Person(val name: String,val age: Int)
2 val (name,age) = person("John",20)
kotlinc 会为Person类生成 get 方法和 componentN 方法,如下是伪代码表示
1 Person {
2 String name;
3 Int age;
4
5 getName(): String { return name; }
6 getAge(): Int { return age; }
7 component1(): String { return name; }
8 component2(): Int { return age; }
9 }
10 // 解构声明编译为
11 val name = person.component12 1()
13 val age = person.component2()
可以看到,get 和 component 的逻辑是一样的,所以在编译期,可以进行全局的匹配,用 get 替换掉 component,然后再删除 component。
- toString 等生成方法优化
kotlin compiler 为 data class 生成的 toString 具有相似的代码结构,因此可以生成一个辅助方法,然后在所有 data class 的 toString 方法中调用这个辅助方法,即外联,从而减少指令数量。
equals 和 hashCode 也可以进行类似优化,但是风险相对较高,因此单独为这些优化配置了开关,业务方可以视情况开启。
提升压缩率的优化RegAllocPassDEX 及其他文件经过压缩打成 APK,如果能通过改变 DEX 的内容来提升压缩率,那么也会减小最终的包体积。RegAllocPass 就是通过重新分配寄存器来提升压缩率的。
dx 生成 DEX 时使用的是线性寄存器分配算法,其基本步骤是进行存活变量分析,然后计算出每个变量的活跃区间,再根据活跃区间依次为变量分配寄存器,超出活跃区间的寄存器可以进行再分配,其优点是运行速度快,但结果往往不是最优的。
比如下面的代码,dx 分配了 6 个寄存器,v0 ~ v5
1 public static double calculateLuminance(@ColorInt int color) {
2 final double[] result = getTempDouble3Array();
3 colorToXYZ(color,result);
4 return result[1] / 100;
5 }
相对的,ReDex 使用了图着色算法进行寄存器分配,基本步骤是进行存活变量分析,并构建冲突图,冲突图的每个节点是一个变量,如果 2 个变量可以同时存活,就在两个节点之间建立边,最后为冲突图着色,每个颜色代表一个寄存器,着色完成即寄存器分配完成。着色法相对更慢,结果一般更优。对上面同样的代码,着色法使用了 4 个寄存器,v0 ~ v3。
DEX 中的方法使用的寄存器越少,其内容重复率就越高,压缩率也会更大,从而减小了包体积。
抖音落地抖音是字节跳动规模最大、运行环境复杂度最高的应用之一。在 ReDex 落地初期,由于对复杂度估计不足,在独立灰度和全量灰度期间引起了一些问题,在解决问题的过程中,我们也逐步形成了一套迭代流程以保证优化的稳定性。下面介绍一下我们遇到过的典型问题及当前的迭代流程。
遇到的问题兼容性问题一般来说,只要按照字节码规范进行优化,就不会有兼容性问题,因为 dalvik/art 也是按照规范去校验和运行字节码的,即使进行了错误的优化,引起的问题也应该是共性问题。但很多事都有例外,ReDex 就在某品牌手机的部分 Android 5.x 的机型上遇到了问题。
从 log 和一些 hook 来看,某品牌手机对 5.x 的 art 做了大量的魔改,可以推断其魔改存在一些问题,导致对正确的字节码的校验和运行也可能出现问题。一个可能的原因是:在 ReDex 进行优化时,会对一些方法体的指令顺序进行重排,这种重排是不影响方法的逻辑的,但是可能会改变一部分指令,魔改后的 art 在校验这样的方法时可能会报 verify error,引起 crash。
最终通过黑名单配置跳过了这些方法的优化规避了问题,在后续的优化过程中,没有再遇到类似的问题。
复杂场景优化问题抖音业务复杂,代码写法多样,给静态分析和优化增加了一些难度,也更容易遇到问题。下面是 2 个典型问题:
- 空方法优化问题 代码中可能存在一些空方法,排除掉反射和 natvie 调用等场景后,剩下的空方法应该是可以删除的。但是在做优化时,却遇到了 crash,如以下代码
1 object XXXSDKHelper {
2 init {
3 initXXXSDK()
4 }
5 fun fakeInit() {
6 }
7 }
8
9 // 初始化任务
10 public class XXInitTask implements Runnable {
11 @Override
12 public void run() {
13 XXXSDKHelper.INSTANCE.fakeInit();
14 }
15 }
在初始化代码中调用fakeInit,它是一个空方法,调用它的目的是触发XXSDKHelper类加载从而执行init语句块,如果删除了这个空方法,就会导致初始化未执行,在后续的流程中抛空指针。
- 复杂反射问题
对于 Class.forname(...)等简单的反射用法,静态分析是可以分析出来的,但是对一些经过字符串拼接或者嵌套之后的反射,静态分析很难分析到。因此,对可能会被反射的代码进行优化需要非常小心,通常来说,匿名内部类是不会通过反射调用的,基于此前提,我们进行了匿名内部类的重命名优化,但是在灰度后,发现某些第三方 SDK 会通过复杂的运行时逻辑对匿名内部类进行了反射调用,最终导致了 ClassNotFoundError。
复杂场景的优化问题有些是业务代码不规范造成的,但更多的是优化前提(空方法可以删除/匿名内部类不会被反射)不成立所导致,所以在进行优化时首先需要对假设进行谨慎的验证。
迭代流程为了减少稳定性问题,我们总结了 ReDex Pass 的迭代流程。
在对一项 Pass 有了初步构思后,组内会进行可行性讨论,如果理论上可行就进入开发和验证阶段,之后同步进行至少 2 轮的独立灰度验证和业务方 Pass 评审,最后进行全量灰度验证。其中任意一个环节发现问题,都会重新进行整个流程。
通过这个流程,我们大大减少了稳定性问题遗留到灰度阶段的可能,在不断完善迭代流程的同时我们也在探索通过加强单元测试、自动化测试等方式来提升质量。
后续规划ReDex 仍然在持续迭代中,未来我们会在以下几个方向继续进行深入探索:
- 更多包体积优化的探索和迭代,同时探索字节码优化在性能提升方面的可能性
- 提升字节码质量
- 更加严格的合法性校验;ReDex 之前已经检测出若干自定义插件和 proguard 的问题,将问题拦截在了编译期,后续会继续提升该能力
- 建立更加完善的质量验证体系;ReDex 作为编译期的全局字节码优化方案,如果保证优化后的字节码质量一直是个痛点,我们会继续在单元测试、自动化测试等方向探索质量提升的手段
- 增加编译期监控,更加快速便捷的解决编译期字节码问题,提升接入体验
- 其他应用方向探索;如方法插桩、某些条件下的死代码扫描等。
字节跳动终端技术团队(Client Infrastructure) 是大前端基础技术的全球化研发团队(分别在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率;支持的产品包括但不限于抖音、今日头条、西瓜视频、飞书、瓜瓜龙等,在移动端、Web、Desktop 等各终端都有深入研究。
就是现在!客户端/前端/服务端/端智能算法/测试开发 面向全球范围招聘!一起来用技术改变世界,感兴趣请联系 fengrui.0@bytedance.com,邮件主题 简历-姓名-求职意向-期望城市-电话。
抖音 Android 基础技术团队是一个深度追求极致的团队,我们专注于性能、架构、包大小、稳定性、基础库、编译构建等方向的深耕,保障超大规模团队的研发效率和数亿用户的使用体验。目前北京、上海、杭州、深圳都有大量人才需要,欢迎有志之士与我们共同建设亿级用户 APP!
可以进入字节跳动招聘官网查询「抖音基础技术 Android」相关职位,或者联系邮件:xiaolin.gan@bytedance.com ,直接发送简历内推或者咨询相关信息!