Kotlin 反射与 MetaData 的关系在混淆后浮出水面!

2020-02-20 13:24:09 浏览数 (1)

概要

本文主要为大家介绍 Kotlin 反射的一些原理,并提示大家如果需要在使用 Kotlin 反射的工程中进行混淆,千万注意,对所有反射涉及的类和接口的父类都需要 Keep。

问题的产生

话说我曾经遇到过一个问题,这个问题可以用下面的代码复现:

代码语言:javascript复制
open class SuperClass

class SubClass: SuperClass(){
    companion object {
        fun seeErrorAfterProguard(){
            try {
                SubClass::class.supertypes.forEach {
                    debug(it)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

如果混淆的时候没有 keep 这两个类,那么使用 Kotlin 反射时会抛一个无法找到 SuperClass 这个类的异常。

可能有些朋友可能还没有反应过来这是什么意思,如果我们使用 Java 反射的话,哪怕 SuperClass 这个类已经被混淆成了 a,那么使用 SubClass 获取它的父类也应该是去找 a 这个类,而不会仍然回去找什么 SuperClass,毕竟这个类信息已经在混淆的时候被改了呀,运行时的 Kotlin 反射又是怎么知道它的存在的呢?

Kotlin 反射的小伎俩

Kotlin 反射能够拿到 Java 反射拿不到的很多东西,这一点毋庸置疑,毕竟 Kotlin 的语法特性更复杂,需要的信息也更多。可是,既然最后大家都编译成了字节码,如果字节码里面原来就有这些信息,Java 反射无论如何也不应该金屋藏娇,捂着不给人看吧——Kotlin 反射一定是另辟蹊径的,不然它又怎么会突破字节码的限制呢?

那么它是怎么做到的呢?当然是这个:

代码语言:javascript复制
@Metadata(
   mv = {1, 1, 9},
   bv = {1, 0, 2},
   k = 1,
   d1 = {"..."},
   d2 = {"Lcom/bennyhuo/kotlinspecifics/reflect/SubClass;", "Lcom/bennyhuo/kotlinspecifics/reflect/SuperClass;", "()V", "Companion", "production sources for module app"}
)
public final class SubClass extends SuperClass {
    ...
}

上面给出的是 SubClass 编译后的字节码反编译得到的 Java 代码,我们看到 Metadata 这个注解当中包含了很多信息,其中 d2 当中就包含了这个类所有的父类信息。

这时候想必各位同仁已然猜到了,其实 Kotlin 反射获取信息的过程就是一个注解读取的过程。

混淆有何罪过

既然知道了 Kotlin 反射的原理,那么我们来想想混淆,混淆对于类的处理,对于通过字面量反射操作类和对象来说是致命的,因为字面量不会作为混淆的对象。同样的,前面的注解的值也不会。也真是这个原因,混淆后 SuperClass 被混淆为了 aSubClass 的注解中存的仍然是 SuperClass

没错,混淆之后出现找不到类的问题是合乎情理的。只要是通过字面量反射操作类和对象,无论是直接还是间接,都需要注意混淆的问题。

如果大家不信,大家也可以试一下属性引用或者函数引用,看看他们在被混淆之后有什么情况发生。

那么这个应该是混淆的锅咯?当然不是,Kotlin 反射毕竟有点儿曲线救国的意思,混淆也只能长叹一口气,“你们这。。让我防不胜防啊”。

有解决方案吗?

有啊,凡涉及到使用 Kotlin 反射的类及其父类(接口)、属性、函数等,均要小心这个问题,如果出现无法找到类或者属性、函数的情形,记得 Keep 它们。

最后再说一句

解析注解这种操作。。。额,难怪它慢。。


0 人点赞