本文主要记录 Android Developers 关于 R8 加速 Kotlin 协程的机制、在自托管媒体客户端仓库里「门槛已到但默认吃不到」的差距,以及用 minify 开/关做体积与冷启动对照时的结论边界。官方 2× 数字不能直接写成自家产品实测。
2026-07 官方博文说明:自 AGP 9.2.0 起,R8 会把多数静态可分析的 Atomic*FieldUpdater 调用改写为 Unsafe 变体,去掉反射安全检查,从而让 kotlinx.atomicfu / kotlinx.coroutines 的 launch、cancel 等路径显著变快;Compose LaunchedEffect 微基准约有 2× 量级改善。
对真实 App 来说,问题往往是:工具链版本够了,但 release 没开 minify,全程序优化根本没跑。
官方机制在做什么
kotlinx.coroutines 的 lock-free 结构大量依赖 AtomicReferenceFieldUpdater 一类按「类 + 字段名」做运行时原子访问的路径,每次操作伴随反射安全检查。官方在 Pixel 5 一类设备上测到 atomicfu 的 CAS 可显著慢于 AtomicReference。
R8 对静态可分析模式大致做三步:
- Instrumentation:旁路生成 field offset
- Replacement:调用点替换为
Unsafe原子操作 - Clean-up:删除已无用的 updater 字段与初始化
门槛与并行路径:
| 路径 | 条件 | 官方表述收益量级 |
|---|---|---|
| R8 编译期改写 | AGP ≥ 9.2.0(或 R8 9.2+) | 原子操作约 2×–4×;协程 launch/cancel 最高约 2× |
| ART 运行时 | 较新 ART / 高 target API | 协程基准约 +15% 量级(与 R8 并行) |
博文写升级 AGP 即可「by default」获得优化,但未逐字写死必须 isMinifyEnabled = true。按常规 AGP 语义,R8 全程序 shrink / optimize 发生在 开启 minify 的变体。在未开 minify 的产物上,应假设 拿不到 该改写,除非用开/关对照或 R8 日志证明。
仓库类客户端的常见差距
以含 Compose + Media3 + 自建 native(如 libmpv)的 Android 客户端为例,调研时常看到:
| 项 | 常见事实 |
|---|---|
| AGP | 已到 9.2.x |
| minify / shrink | debug / release 均未 开 |
| proguard 业务规则 | 无项目级 proguard-rules 接入 |
| release 已有差异 | 可能仅 native 日志宏、debuggable 等,不是 Java/Kotlin R8 |
此时:
- 官方协程加速 通常不作用 于现有发布 APK。
- 历史上若把 R8 和「剥 release 日志」绑在一起讨论,会错误放大风险:日志门控应继续靠
BuildConfig/ 编译期宏,不要指望 R8 替业务剥日志。
热路径形态对齐(推断,非 profiling)
| 区域 | 与官方场景关系 |
|---|---|
大量 LaunchedEffect / 手势交互 | 贴近;可能先受益 |
| 媒体墙滑动、OSD 频繁重启 effect | 可能贴近 |
| 播放 session 长生命周期轮询 | 相对不贴近 微基准式高频 launch/cancel |
推断:若 Atomic 改写生效,更可能先体现在 Compose 交互与 UI 调度 的尖峰,而不是「打开一个播放 session」本身。
为何不能「直接默认 minify」
Media 客户端 minify 的 keep 面通常包括:
- Media3 / 自建 decoder AAR、反射探测类名
- libass / libmpv / JNI 相关类
- SignalR / 序列化回调
- Compose、Room、OkHttp 等 consumer rules 未覆盖的边角
已经出现过的一类真实坑:字符串 Class.forName("…AssHandler") 在 R8 改名后探测假阴性——应改为硬 Class 引用 + 明确 keep,而不是继续依赖脆弱字符串。
目标函数建议按优先级固定:
- 正确性(双引擎播放、字幕、session、TV 焦点等核心路径)
- 包体积(相对未 minify 基线可复现下降)
- 性能(有数据再写;无数据标未量化)
明确排除:用 R8 替代业务日志门控;把官方 2× 写成自家数字。
对照采样(脱敏结论)
在同一源码状态下,对某一 channel 的 edge + release 做 minify 关/开对照(未 开 resource shrink;设备与路径已脱敏):
体积
| 变体 | APK 约 | 相对 |
|---|---|---|
| baseline(未 minify) | ~82.8 MiB | — |
| minify(R8) | ~44.7 MiB | 约 −46% |
zip 未压缩分项上,缩减几乎全在 dex;lib(native so)基本不变。结论:minify 显著缩小 APK;剩余体积由 native 主导。
冷启动(同协议、去首 median)
| 段 | 含义 | 相对 baseline(minify) |
|---|---|---|
| A | am start -W 的 TotalTime | 约 −20% 量级(约 −0.1s) |
| B | 到首页内容就绪的墙钟 | 约 −20% 量级(约 −1.5s) |
B 段含 uiautomator 探测开销,只适合 同协议对照,不宜解读为纯业务 CPU 时间。也 不是 官方协程 2× 微基准的复现。
推荐路线
| 选项 | 做法 | 建议 |
|---|---|---|
| 维持现状 | 默认关闭 minify | 无证据前,默认发布可保持 |
| 探测 minify | 本地/旁路打开,不进默认 channel | 专项主路径 |
| 默认 minify + shrink | 写入 release 并进全渠道 | 仅当探测证据充分 |
| 指望 ART +15% | 不 minify | 不能替代专项 |
实践顺序:
- 锁定目标 = 体积 + 性能 + 正确性(排除日志门控重构)
- 固定 channel / ABI / 是否 shrink resources 的口径
- 迭代 keep + 双引擎 / 字幕 / 登录 CUJ
- 有 mapping 归档策略后再考虑默认开启
相关阅读
参考与边界
- How R8 made Kotlin Coroutines on Android 2x faster(Android Developers Blog)
- Enable app optimization with R8
- 体积与冷启动数字来自单设备、固定协议采样;换机型 / 换 channel 需重测