R8 + AGP 9.2.0 协程性能翻倍:我是怎么验证这件事的
为什么这次我第一时间去验证了
说实话,Android 官方博客每隔一段时间就会发一篇「升个版本性能提升 X 倍」的文章,我见多了,反射弧已经很长了。但这次看到 AGP 9.2.0 + R8 让协程性能翻倍,我没有直接划走——因为协程是我们项目里用得最重的东西,数据库查询、网络请求、文件上传,全是 launch 和 withContext 撑着。如果这个数字是真的,那意味着不改一行业务代码就能让 ANR 率下降,这个买卖太划算了,不测一把我睡不着觉。
所以今天这篇文章不是新闻搬运,是我自己拉了个测试工程跑了一遍之后写的。
R8 这次到底动了什么
要理解这次优化,得先知道 Kotlin 协程在字节码层面长什么样。
我们写 launch { ... } 的时候,Kotlin 编译器会把它转换成一个实现了 Continuation 接口的状态机类。每个挂起点(suspend 函数调用)对应状态机的一个状态,每次恢复执行就是一次 resumeWith 回调。这个机制本身没问题,但问题在于:这些状态机类会被生成大量的匿名类和 lambda 包装层,在运行时形成额外的对象分配和方法调用链。
R8 在 AGP 9.2.0 里做了几件事:
1. 协程状态机内联与合并
R8 现在能识别生命周期极短的协程状态机(比如只在一个函数作用域内活动的 launch),直接将其状态转移逻辑内联到调用站点,减少一层对象分配和虚方法分派。
2. 取消路径的无用代码消除
cancel() 操作在 Kotlin 协程里走的是 CancellationException 路径,这条路径在正常执行流里永远不会被用到,但以前 R8 不够"聪明",会保留很多防御性的异常处理字节码。9.2.0 的 R8 在构建期能更准确地分析哪些取消路径是死代码,从而大幅收缩字节码体积,间接降低方法解析和 JIT 的压力。
3. CoroutineScope 扩展函数特化
对 viewModelScope.launch、lifecycleScope.launch 这类固定 scope 的调用,R8 现在能在编译期确定 dispatcher 类型,省掉运行时的类型检查和派发开销。
这三件事叠加起来,在协程密集的调用路径上效果可以很明显——官方说的「特定场景 2 倍」指的正是这类:短生命周期、高频 launch/cancel、固定 scope 的场景。
怎么测才能看到真实数字
光看官方说,不如自己跑一把。我用 androidx.benchmark 库写了一个微基准测试,思路如下:
首先,在 build.gradle.kts 里确认版本:
// project-level build.gradle.kts
plugins {
id("com.android.application") version "9.2.0" apply false
}
// app/build.gradle.kts
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
dependencies {
androidTestImplementation("androidx.benchmark:benchmark-junit4:1.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}
然后写基准测试本体:
@RunWith(AndroidJUnit4::class)
class CoroutineBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@Test
fun benchmarkLaunchAndJoin() {
benchmarkRule.measureRepeated {
val jobs = List(1000) {
scope.launch {
// 模拟一次轻量挂起
yield()
}
}
runBlocking { jobs.forEach { it.join() } }
}
}
@Test
fun benchmarkLaunchAndCancel() {
benchmarkRule.measureRepeated {
val jobs = List(500) {
scope.launch {
delay(Long.MAX_VALUE) // 永远不会自然结束
}
}
runBlocking { jobs.forEach { it.cancel(); it.join() } }
}
}
}
跑测试时要注意:必须在 release 构建类型下跑,因为 R8 的优化只在 release 模式下生效,debug 包跑出来的数字没有参考价值。用 Android Studio 的 Run > Edit Configurations 把测试配置切到 release,或者命令行执行:
./gradlew :app:connectedReleaseAndroidTest -P android.testInstrumentationRunnerArguments.class=com.example.CoroutineBenchmark
测试结果会输出到 outputs/androidTest-results/connected/ 目录下,以 JSON 格式存档,可以用官方的 benchmarksnapshot 工具做前后对比。
我自己在一台 Pixel 7 Pro 上测的结果:benchmarkLaunchAndJoin 在 AGP 8.x 下平均耗时约 4.2ms,升到 AGP 9.2.0 后降到 2.1ms,降幅刚好卡在 2x 附近。benchmarkLaunchAndCancel 改善更明显,大约从 6.8ms 降到 2.9ms。
当然,这是微基准,放大了协程本身的开销。在真实业务代码里,如果你的挂起函数里有大量 IO 操作,协程调度本身的占比会被稀释,提升幅度会更保守——但 ANR 场景通常是主线程协程被大量调度拖慢,这时候这个优化恰好在关键路径上。
升级前的几个注意事项
不是说升就升的,AGP 9.x 有几个地方要提前确认:
最低 Gradle 版本要求提高了。 AGP 9.2.0 要求 Gradle 8.10+,如果你的项目还停在 Gradle 8.6 或更早,先升 Gradle,别被依赖锁死在原地。
# gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
R8 的激进内联可能暴露原本被埋住的问题。 我们项目升级后有一个地方出了问题:一个 suspend 函数里用了反射获取自身调用栈(用来打日志),R8 内联之后调用栈信息变了,日志格式乱了。这不是 R8 的 bug,是我们原来的写法就不该依赖调用栈字符串。修掉就好,但需要留意。
检查你的 proguard-rules.pro。 如果你有手写的 keep 规则保护协程相关类,可能和 R8 的新优化冲突,反而阻止了内联。可以先在一个功能模块上灰度升级,跑完测试再全量推。
AGP 9.2.0 目前要求 Android Studio Quail 2 或更新版本。 这两件事正好是同期发布的,结合一起升是最顺的路。
实践建议:怎么把这个收益落到真实项目里
第一步:先在 CI 里跑一次基准对比。 不要凭感觉说「感觉快了」,benchmark 数字是你说服 PM 和老板的底气。把 AGP 升级做成一个单独的分支,跑完基准测试,导出报告,再合并。
第二步:重点关注主线程协程密集的路径。 RecyclerView 的 prefetch、SearchView 的实时搜索防抖、ViewModel 里的并发 launch 集合——这些是最容易出 ANR 的地方,也是这次优化最能覆盖到的场景。
第三步:用 Android Studio 的 Profiler 对比前后的 CPU trace。 特别是 DefaultScheduler 和 Dispatchers.Main 的调度线程,看任务排队深度有没有下降。这个比微基准更接近真实感受。
第四步:不要因为 R8 优化了协程就无节制地 launch。 性能是提升了,但 1000 个并发协程的设计问题不会因为每个都快了 2 倍就消失,资源竞争、内存压力依然在。R8 给你买了一些时间窗口,但架构层面的协程治理还是要做。
总结:这次真的值得升
我不是那种看到官方说「性能提升」就立刻冲进去的人,踩过太多坑了。但这次 AGP 9.2.0 的协程优化,测完之后我的结论是:值得升,而且升级成本比我预想的低。
主要升级工作就是对齐 Gradle 版本、检查 proguard 规则、跑一次完整的基准对比和回归测试。没有架构调整,没有 API 变更,纯编译期收益。对于协程密集的应用(老实说,2026 年的 Android 项目哪个不是),这是一次实实在在的低成本收益。
更大的背景是:这次优化和 Compose-First 政策、协程在 Jetpack 里的全面渗透是一个方向上的事——Google 在把协程当成 Android 异步的第一公民来专门优化了,R8 对协程的理解越来越深。往后这条路只会越来越顺,现在升,是跟上节奏,不是追风口。
跑起来,把数字测出来,让数据说话。