Android混淆/代码优化导致kotlin data class/fastjson反射异常原因自查

2,508 阅读3分钟
  1. 如果你的项目用了fastjson库或者其他利用反射来进行序列化反序列化操作的库,而且反序列化的对象是kotlin 的 data class类,那么你需要引入org.jetbrains.kotlin:kotlin-reflect:$kotlin_version这个库,因为data class 类默认是没有空的构造函数的,所以如果反射获取类的默认(空)构造函数会失败,fastjson就会抛出com.alibaba.fastjson.JSONException: default constructor not found. 。引入这个库同时还需要加上反混淆配置 -keepattributes Annotation

    -keep class kotlin.** { *; }
    
    -keep class org.jetbrains.** { *; }
    

    关于为什么引入这个依赖就可以了,其实是fastJson对kotlin做的兼容处理。fastJson在遇到kotlin类(KClass)时,会使用反射获取kotlin.reflact相关的类,这也是我们要加混淆的原因,具体代码请看com.alibaba.fastjson.util.TypeUtils.getKoltinConstructorParameters()方法

  2. 如果项目使用了fastJson,而且混淆之后出现崩溃

    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.reflect.AccessibleObject.isAccessible()' on a null object reference

    那有可能的原因是你没有加fastjson的反混淆代码,在proguard-rules.pro里加上就好了

    #FastJson反混淆
    -keepattributes Signature
    -dontwarn com.alibaba.fastjson.**
    -keep class com.alibaba.fastjson.**{*; }
    
  3. 如果项目使用了fastJson,而且混淆之后出现了异常

    Caused by: com.alibaba.fastjson.JSONException: default constructor not found. class com.company.module.net.bean.NetBean

    这个异常一看就是混淆导致fastjson找不到空构造函数而引起的问题,因为fastjson反序列化是通过反射来实现的,所以项目里在fastjson里用到的类都需要加反混淆,这里我随便拿一个类举例

    package com.company.module.net.bean
    
    data class NetBean(
        val url: String,
        val host: String,
        val method: String,
        val type: Int,
    ) {
        fun toRequest(requestBody: RequestBody): Request {
            return Request.Builder().method(method, requestBody).url(url).build()
        }
    }
    

    如果你要给这个类加反混淆,那你就需要在这个类所在的modlue的proguard-rules.pro里加上

    -keep class com.company.module.net.bean.NetBean {*;}
    

    这样就能保持这个类和它的子类以及它所有的字段和方法都不被混淆,关于混淆的更多写法以及说明,推荐看这篇文章,讲的很细由浅入深 Android 混淆实战

  4. 如果给你的类加了反混淆之后,运行还是报同样的错误default constructor not found,或者什么其他的方法在反射的时候找不到,那很可能是因为开启了代码优化之后,编译器在编译的时候把没有引用过的方法字节码给优化掉了,导致实际打出来的包确实是没有构造方法/反射要调用的方法,所以只加反混淆还不够。

    下面是我把一个类反混淆之后,jadx查看混淆后的代码,可以看到是没有构造方法的,至于它的成员方法toRequest还在的原因是因为我在其他地方调用了它,而把这个类实例化的方式是通过fastjson反射,并没有调用这个类的构造方法,所以构造方法被编译器优化掉了 A4F9CBC5-93D6-440d-A6B8-F8CE0E4FCE60.png

    解决办法很简单,就是在你会用反射调用的类/方法/字段上加上一个@Keep注解 Snipaste_2023-03-23_19-51-44.png

    这个注解能使用的地方很多,不过主要是用在方法和类上,注释的翻译如下: 表示在构建时缩小代码时不应删除带注释的元素。 这通常用于仅通过反射访问的方法和类,因此编译器可能认为代码未被使用。

目前踩到的关于kotlin/fastjson/反射/混淆的坑就是这么多,如果遇到其他类似的问题,都会归总到这里,欢迎路过的各位大佬多多补充 ; )