升级AGP至8.8.2后,R8混淆导致发布的Android SDK中Kotlin类功能失效
最近我在把多模块SDK项目的AGP从8.3.2升级到8.8.2后,碰到了个挺头疼的小众问题——因为我们的库模块都开了isMinifyEnabled = true,只给客户暴露必要API,结果升级后有个核心组件直接罢工了。
这个出问题的组件是个Kotlin单例类,代码长这样:
object LoggerProvider { @JvmStatic var logger: Logger = StubLogger() }
集成SDK的客户反馈,设置自定义Logger后完全不生效,始终用的是默认的StubLogger。排查半天后才发现,是AGP 8.8.2配套的R8混淆优化逻辑变了,把这个单例的关键访问逻辑给改没了。
问题根源分析
AGP 8.8.x里的R8做了更激进的代码优化,对于Kotlin的object单例,它可能会把@JvmStatic修饰的可变属性(var)的getter/setter方法给混淆内联,甚至修改单例的初始化逻辑,导致外部设置的属性值根本没法覆盖默认值。再加上我们没给这个类加针对性的混淆保留规则,R8直接把核心访问逻辑优化没了。
亲测有效的解决方法
我试了几个方案,下面这几个是确实能解决问题的:
给关键类添加混淆保留规则:在库模块的
proguard-rules.pro里加上以下规则,强制R8保留这个单例的结构和属性访问方法:-keep class 你的实际包名.LoggerProvider { public static ** logger; public static ** getLogger(); public static void setLogger(**); } -keepclassmembers class 你的实际包名.LoggerProvider { @org.jetbrains.annotations.NotNull public static ** logger; public static ** getLogger(); public static void setLogger(**); }记得把
你的实际包名替换成项目里的真实包路径,这样R8就不会动这个类的核心访问逻辑了。改用
@JvmField修饰属性:如果业务场景允许,把代码改成下面这样,@JvmField会让Kotlin直接生成公开的字段,绕开R8对访问方法的优化:object LoggerProvider { @JvmField var logger: Logger = StubLogger() }不过要注意,
@JvmField修饰的var没有Kotlin属性的委托等特性,如果之前的代码依赖了setter的额外逻辑,这个方案可能不适用。临时降级R8版本:如果上面的方法暂时没法落地,可以在项目根目录的
build.gradle.kts里指定降级R8到和AGP 8.3.2兼容的版本:buildscript { dependencies { classpath("com.android.tools:r8:8.3.4") // 对应AGP 8.3.2的R8版本 } }这只是权宜之计,长期来看还是建议用混淆规则的方案,毕竟跟上AGP版本更新是趋势。
验证方案有效性
修改完成后,你可以用javap工具反编译混淆后的class文件,检查类结构:
javap -c 你的实际包名.LoggerProvider
如果能看到公开的logger字段或者对应的getLogger()、setLogger()方法,就说明规则生效了,再打包SDK让客户测试确认功能恢复正常即可。
内容来源于stack exchange

