你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解

本篇是来自 Android Developers 的播客 What's so great about R8?》 的整合,核心是讨论了 Android R8 编译器以及它对性能的影响,参与讨论的嘉宾包括来自 Android 工具团队、R8 团队和平台性能团队的专家(Tor Norby, Romain Guy, Sean, Chris, Shai)。

这是一篇让你对 R8 不再误解的内容

D8 与 R8 编译器的区别

首先可能不少人还不理解 D8 与 R8 的区别,在 Android 开发里,我们都知道代码从 Java/Kotlin 编译为字节码后,还需要经过另一层编译才能在设备上运行:

  • D8 编译器 :这是开发者在日常开发中使用的调试编译器,它速度快、支持增量编译,目的是让开发者能够以最快的速度在设备或模拟器上编辑、运行和调试代码,但它不会对代码进行深度的性能优化
  • R8 编译器 :这是用于发布 (Release build) 的全局优化编译器,它会将整个应用作为一个整体进行静态分析,花费可能高达数十分钟的时间来应用各种复杂的优化,从而生成体积最小、运行最快的最终代码

所以,D8主要处理 dexing(将Java字节码转换为Dalvik字节码)和基本的代码缩小,而 R8 的功能更全面,包括高级优化如跨模块分析 。

R8 的核心优化机制

所以 R8 不仅仅是混淆代码,它在底层做了大量提升性能的工作:

  • Tree Shaking :正常应用里通常会引入大量第三方库,但往往只使用其中的一小部分功能,而 R8 能够通过全局静态分析,找出哪些代码在运行时永远不会被调用,并将其完全移除当然,这也是很多时候大家讨厌 R8 的原因,因为 R8 在高版本默认开启后,应用升级就崩溃
  • 方法与参数内联 :R8 会分析代码的调用情况,将总是接收相同参数的方法,或调用频繁但非常简短的方法直接内联到调用处,从而减少虚拟方法调用的开销
  • 合并接口:如果 R8 发现一个接口在实际生产代码中只有一个实现类(例如仅仅为了单元测试而提取的接口),它可以将该接口直接合并到实现类中,减少一层指针查找的性能损耗
  • 与 ART 运行时的协同优化:R8 的工作会为设备上的 ART (Android Runtime) 编译器"铺平道路",结合 Baseline Profiles(基准配置文件),R8 知道哪些代码是启动时的"热代码",并会在 DEX 文件中留下提示,帮助 ART 在用户设备上进一步生成高效的机器码
  • 移除 Kotlin 空安全检查 :Kotlin 会在底层插入大量的非空检查 (如checkNotNullParameter 等),在发布版本中,R8 可以基于全局分析证明某些值永远不为空从而移除检查,或者将其替换为不带冗长字符串的精简指令,既减小了体积又提升了性能

所以可以看出来, R8 比 D8 做了更多的性能和优化工作,本质上其实就是为了让 App 更快更小。

所以了解 R8 做了什么也很重要,因为经过 R8 后你的 App 运行可能会出现某些奇怪问题,而找到这些问题的原因,就需要你清楚知道 R8 做了什么

R8 面临的最大挑战:反射与 Keep Rules

但是就像我们前面说的,R8 也带来了很多问题,比如它虽然 Tree Shaking ,但是它对于 Tree Shaking 也没办法做到尽善尽美:

  • 反射与 JNI 带来的问题 :由于 R8 是静态分析工具,它很难预测在运行时通过字符串动态调用的类、方法或字段(如反射),如果 R8 看不到某个类的静态调用,就会将其当作"死代码"移除,导致应用在运行时抛出 ClassNotFoundException 崩溃
  • 保留规则 (Keep Rules) :为了反射和 JNI 问题,开发者必须编写 Keep Rules 来明确告诉 R8 哪些类和方法不能被优化或移除,但是很多第三方库为了省事,会编写过于宽泛的规则(例如要求保留整个库的所有代码),这会严重阻碍 R8 发挥作用并拖累整体性能

恩, GSY 大体上就是这样,因为 GSY 播放器里有很多 JNI 和反射调用,所以在提供的 Keep Rules 里,都是直接用宽泛的规则,省事。

开发者体验与工具链的改进

另外,如今的 R8 开发体验和资料改进已经非常完善,比如大家一开始认识的 R8 已经是"女大十八变"了:

  • 反混淆与崩溃日志 :R8 会将类名和方法名重命名为简短的字母(如 "a", "b", "c")以节省空间,为了让崩溃日志可读,R8 会输出 Mapping 文件,现在的 Android Studio 提供了不错的集成(Android Studio 的 App Quality Insights ),开发者可以在 IDE 中直接点击按钮,自动下载和映射线上的崩溃堆栈,体验上和调试 D8 构建一样顺畅(它会利用线上的 mapping 文件对崩溃堆栈进行反混淆
  • 官方文档大翻新:在此之前,R8 的文档一言难尽,而现在 R8 团队对官方文档进行了大规模翻新,详细解释了 R8 的工作原理、Keep Rules 的语法规范、最佳实践以及如何排查不合理的保留规则

R8 会大量更改源码的情况,所以栈轨迹无法指向原始代码 ,例如行号以及类和方法的名称可能会发生变化,所以在使用 retrace 时,需要 mapping 文件,例如 : $ANDROID_HOME/cmdline-tools/latest/bin/retrace app/build/outputs/mapping/$releaseVariant/mapping.txt trace.txt

R8 的新特性与未来方向

而实际上 R8 目前还处于高速发展的阶段,未来还有需要多能力提供,例如:

  • 局部/包级别的 R8 优化 (Gradual R8) : 为了帮助历史包袱沉重的大型应用逐步迁移到 R8,R8 团队推出了按包范围开启 R8 的功能,开发者可以先对 AndroidX 库、Compose 等确定安全的包开启优化,然后逐步将自己的业务代码加入优化范围,从而降低崩溃风险
  • @UsesReflection 注解 :相比于在单独的配置文件中写晦涩的 Keep Rules,R8 团队正在推广一种新的 @UsesReflection 注解,开发者可以直接在发起反射调用的地方添加注解,指明它反射了什么目标,这种方式让配置跟随代码移动,支持条件保留,且能被 IDE 直接识别和检查
  • 优化的资源缩减 (Optimized Resource Shrinking) :过去,代码缩减和资源缩减是分开进行的,现在 R8 将这两者结合起来,可以跨越代码和 XML 资源进行全局追踪,如果一个 Activity 的代码被判定为未使用并被移除了,R8 会顺藤摸瓜将它引用的无用 XML 布局、图片资源一并剔除

所以,官方团队也建议所有开发者开启 R8,因为开启 R8 不仅能减小 APK 体积,还能大幅降低后台 ANR 的概率,并显著提升应用启动和运行速度,例如性能团队的 Shai 分享了一个数据:

根据他们与 Top 应用开发者的合作,将一个应用从不使用 R8 优化到全面深度应用 R8,其带来的性能提升效果,相当于给用户的手机免费进行了"3 到 4 年的硬件升级" ,换句话说,运行经过 R8 优化代码的旧手机,体验可以媲美运行未优化代码的最新款手机。

当然,这里的 R8 更多是 full mode 的 R8 ,R8 提供两种模式,兼容模式和全模式,全模式才是满血优化,android.enableR8.fullMode=false 的可不算,例如:

在兼容模式下,R8 不改变类中方法和字段的可见性,而在完全模式下,R8 会通过改变方法和字段的可见性来增强优化,比如将 private to public 从而支持更多内联

总而言之,那就是你需要 R8 ,拥抱 R8 ,All in R8 ,阿巴阿巴阿巴阿巴·····

相关推荐
PineappleCoder1 小时前
告别“幻影坦克”:手把手教你丝滑规避布局抖动,让页面渲染快如闪电!
前端·性能优化
前端不太难2 小时前
Flutter 页面切换后为什么会“状态丢失”或“状态常驻”?
flutter·状态模式
松叶似针2 小时前
Flutter三方库适配OpenHarmony【secure_application】— pubspec.yaml 多平台配置与依赖管理
flutter·harmonyos
武帝为此2 小时前
【Shell变量替换与测试】
前端·chrome
CappuccinoRose2 小时前
CSS 语法学习文档(十九)
前端·css·属性·flex·grid·学习资源·格式化上下文
雷电法拉珑3 小时前
财务数据批量采集
linux·前端·python
城东米粉儿3 小时前
Android Glide 笔记
android
城东米粉儿3 小时前
Android TheRouter 笔记
android