ProGuard

Posted on By ᵇᵒ

弄了个小工具,用于生成任意 class 的实例。其中做了些判断,对于 interface、abstract class 等不支持类型报错。 踩了个混淆的坑,简单记录下。

如下测试 class:

data class A(val b: B)
data class B(val a: A)

在 debug 环境正常生成 A、B 的实例,在 release 环境下报错:
java.lang.IllegalStateException: Cannot instantiate abstract class: class k2.a

第一反应应该是混淆出问题了,搜 mapping 文件,k2.a 对应的果然是 class A。
网上搜到类似 StackOverFlow Proguard, Android, and abstract class instantiation, 大意是 R8 检测到 class A 和 B 的构造函数没有任何地方调用,就移除了,然后也不知怎么的 Modifier 就变成了 abstract class(单纯依据是否有构造函数?)。

当然,这里的 abstract class 不是关键,主要是构造函数被移除了,即使使用 Unsafe 去构造对应的 instance 也会失败。
解决办法就是在混淆规则里 keep 住这两 class 的构造函数:

# keep class A、B 对应签名的构造函数
-keepclassmembers class com.package.A {
  <init>(com.package.B);
}
-keepclassmembers class com.package.B {
  <init>(com.package.A);
}

或者

# keep class A、B 所有的构造函数
-keepclassmembers class com.package.A {
  <init>(...);
}
-keepclassmembers class com.package.B {
  <init>(...);
}

这个问题的根源是混淆工具无法检测 Reflect/Unsafe 等方式的调用,导致误判对应代码未使用从而被优化移除。老生常谈的问题了,在未来很长一段时间内应该都不会有什么好的解决方案。
作为开发者来说,避坑的关键点在于,任何通过反射调用的 class 都需要特别关注 是否需要在混淆规则里 keep。