getActivity/GsonFactory

[Bug]:开启混淆后,Kotlin的data class 内部类解析失败。

Closed this issue · 13 comments

框架版本【必填】

9.6

问题描述【必填】

开启混淆后,Kotlin的data class 内部类解析失败。

复现步骤【必填】

创建一个kotlin 文件
在其中定义多个data class ,并且相互有所引用,使用@SerializedName方法进行注解。
开启混淆,解析对应代码。

是否必现【必填】

项目 targetSdkVersion【必填】

33

出现问题的手机信息【必填】

全部

出现问题的安卓版本【必填】

全部

问题信息的来源渠道【必填】

自己遇到的

是部分机型还是所有机型都会出现【必答】

全部

框架最新的版本是否存在这个问题【必答】

框架文档是否提及了该问题【必答】

是否已经查阅框架文档但还未能解决的【必答】

issue 列表中是否有人曾提过类似的问题【必答】

是否已经搜索过了 issue 列表但还未能解决的【必答】

是否可以通过 Demo 来复现该问题【必答】

提供报错堆栈

2024-04-09 19:18:54.732  6374-16483 System.err              com.wepie.wespy                      W  java.lang.ClassNotFoundException: Didn't find class "com.wepie.wespy.module.party.home.PartyShareData" on path: DexPathList[[zip file "/data/app/~~UzbQn9r_moNkE7-KSr4Snw==/com.wepie.wespy-PAPtm3RmfiEAj_iMkz4ovw==/base.apk"],nativeLibraryDirectories=[/data/app/~~UzbQn9r_moNkE7-KSr4Snw==/com.wepie.wespy-PAPtm3RmfiEAj_iMkz4ovw==/lib/arm64, /data/app/~~UzbQn9r_moNkE7-KSr4Snw==/com.wepie.wespy-PAPtm3RmfiEAj_iMkz4ovw==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]
2024-04-09 19:18:54.732  6374-6374  SurfaceControl          com.wepie.wespy                      I   setExtendedRangeBrightness sc=Surface(name=com.wepie.wespy/com.wepie.wespy.module.party.home.PartyHomeActivity)/@0x25d32fc,currentBufferRatio=1.0,desiredRatio=1.0
2024-04-09 19:18:54.732  6374-16483 System.err              com.wepie.wespy                      W  	at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
2024-04-09 19:18:54.732  6374-16483 System.err              com.wepie.wespy                      W  	at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.parseType(KDeclarationContainerImpl.kt:266)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.loadParameterTypes(KDeclarationContainerImpl.kt:257)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findConstructorBySignature(KDeclarationContainerImpl.kt:228)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:67)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:36)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:62)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(Unknown Source:7)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:63)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at kotlin.reflect.jvm.KCallablesJvm.setAccessible(KCallablesJvm.kt:82)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.constructor.KotlinDataClassDefaultValueConstructor.construct(KotlinDataClassDefaultValueConstructor.kt:29)
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.constructor.l.construct(ReflectCreatorConstructor.java:29)
2024-04-09 19:18:54.733  6374-16662 BLASTBufferQueue        com.wepie.wespy                      D  [VRI[PartyHomeActivity]#15](f:0,a:1) acquireNextBufferLocked size=24x24 mFrameNumber=1 applyTransaction=true mTimestamp=108821324931038(auto) mPendingTransactions.size=0 graphicBufferId=27376121544767 transform=0
2024-04-09 19:18:54.733  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.b.e.read(ReflectiveTypeAdapter.java:58)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.c.c$1.a(ReflectiveTypeUtils.java:110)
2024-04-09 19:18:54.734  6374-16662 VRI[PartyHomeActivity]  com.wepie.wespy                      D  Received frameCommittedCallback lastAttemptedDrawFrameNum=1 didProduceBuffer=true syncBuffer=false
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.b.e.read(ReflectiveTypeAdapter.java:71)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.c.c$1.a(ReflectiveTypeUtils.java:110)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.hjq.gson.factory.b.e.read(ReflectiveTypeAdapter.java:71)
2024-04-09 19:18:54.734  6374-6374  VRI[PartyHomeActivity]  com.wepie.wespy                      D  draw finished.
2024-04-09 19:18:54.734  6374-6374  VRI[PartyHomeActivity]  com.wepie.wespy                      D  reportDrawFinished
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.google.gson.Gson.fromJson(Gson.java:1227)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.google.gson.Gson.fromJson(Gson.java:1329)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.google.gson.Gson.fromJson(Gson.java:1300)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.three.http.callback.DataCallback.parseResponse(DataCallback.java:74)
2024-04-09 19:18:54.734  6374-6374  ViewRootImplExtImpl     com.wepie.wespy                      D  setMaxDequeuedBufferCount: 2
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at com.three.http.core.HttpUtil$3.onResponse(HttpUtil.java:288)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at okhttp3.RealCall$AsyncCall.execute(RealCall.java:174)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
2024-04-09 19:18:54.734  6374-16483 System.err              com.wepie.wespy                      W  	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
2024-04-09 19:18:54.735  6374-16483 System.err              com.wepie.wespy                      W  	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
2024-04-09 19:18:54.735  6374-16483 System.err              com.wepie.wespy                      W  	at java.lang.Thread.run(Thread.java:1012)

提供截图或视频

No response

提供解决方案

堆栈中的com.wepie.wespy.module.party.home.PartyShareData是原类名,但是被混淆了。通过反编译kotlin的字节码发现里面确实有一些反射代码,所以理论上keep一下就可以。

可以通过添加混淆规则解决,但是看堆栈还是报在了sdk这里,正常java类直接这样用没什么问题,还是想知道与根本原因.
目前使用8.0版本也无该问题。

根据demo差异,发现我们并未使用kotlin的kotlin-reflect库,有空后排查一下这里。

小伙子,找不到类这个问题跟 Gson 解析没有任何关系吧?类找不到是因为 ClassLoader 没有加载所在类的 dex 文件到 dex 数组中,这个问题大概率是在编译的过程中,出现问题的类没有编译进去导致的,你可以用 jadx 反编译核查一下。

小伙子,找不到类这个问题跟 Gson 解析没有任何关系吧?类找不到是因为 ClassLoader 没有加载所在类的 dex 文件到 dex 数组中,这个问题大概率是在编译的过程中,出现问题的类没有编译进去导致的,你可以用 jadx 反编译核查一下。

使用Gson直接解析和8.0版本都是好的。主要还是混淆的问题,有些地方用的反射

@rejigtian 小伙子,Kotlin data class 使用 Gson 和 GsonFactory 8.0 版本去解析都是有 Bug 的,看似能解析,但是存在较大的问题,具体有什么问题你可以回看一下版本更新记录,就算 8.0 的版本能解析,我不推荐你用。

这边使用Demo复现了一下。
这里构造方法调用了反射,混淆后的类名是b.a.a.a,而这里反射继续调用fun (com.test.rejig.SimpleData2, kotlin.String): 就会出bug。
image
对应Kt代码
image
混淆后的代码
image
问题堆栈:
2024-04-11 11:01:14.221 14264-14264 AndroidRuntime com.hjq.gson.factory.demo E FATAL EXCEPTION: main Process: com.hjq.gson.factory.demo, PID: 14264 java.lang.RuntimeException: java.lang.reflect.InvocationTargetException at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:622) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)  Caused by: java.lang.ClassNotFoundException: Didn't find class "com.test.rejig.SimpleData2" on path: DexPathList[[zip file "/data/app/~~F9_ejQI75TeuaZlEL3kMlA==/com.hjq.gson.factory.demo-zFGm5So5coIS8ufehHFl8g==/base.apk"],nativeLibraryDirectories=[/data/app/~~F9_ejQI75TeuaZlEL3kMlA==/com.hjq.gson.factory.demo-zFGm5So5coIS8ufehHFl8g==/lib/arm64, /data/app/~~F9_ejQI75TeuaZlEL3kMlA==/com.hjq.gson.factory.demo-zFGm5So5coIS8ufehHFl8g==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312) at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.parseType(:267) at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.loadParameterTypes(:258) at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findConstructorBySignature(:229) at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(:67) at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(:36) at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(:62) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(:31) at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(Unknown Source:7) at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(:63) at kotlin.reflect.jvm.KCallablesJvm.setAccessible(:82) at com.hjq.gson.factory.constructor.KotlinDataClassDefaultValueConstructor.construct(:29) at com.hjq.gson.factory.constructor.ReflectCreatorConstructor.construct(:29) at com.hjq.gson.factory.element.ReflectiveTypeAdapter.read(:58) at com.google.gson.Gson.fromJson(:1227) at com.google.gson.Gson.fromJson(:1137) at com.google.gson.Gson.fromJson(:1047) at com.google.gson.Gson.fromJson(:982) at com.hjq.gson.factory.demo.MainActivity$1.onTitleClick(:31) at com.hjq.bar.TitleBar.onClick(:423) at android.view.View.performClick(View.java:7479) at android.widget.TextView.performClick(TextView.java:12296) at android.view.View.performClickInternal(View.java:7456) at android.view.View.access$3600(View.java:813) at android.view.View$PerformClick.run(View.java:28476) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:262) at android.app.ActivityThread.main(ActivityThread.java:8769) at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963) 

demo代码在这里,在demo基础上,新增了kt类和混淆规则
https://github.com/rejigtian/GsonFactory/tree/test-bug

你这个是类找不到的异常,属于运行环境的问题,GsonFactory 是 Json 框架,不存在任何类加载的逻辑,除非你有直接的证据证明这个就是 GsonFactory 的问题,否则我将不会介入处理。

这边看8.0的代码KotlinDataClassDefaultValueConstructor这个类中没有使用Kt的反射去构造,所以是正常的。

@rejigtian 8.0 那种反射创建是存在缺陷的,并且无法解决,一旦 kotlin data class 在字段超过 32 个的情况下默认值不生效的问题,还有一些其他别的缺陷,也是没有办法解决的,框架是不可能回退回到之前那种方法去反射类的构造函数。

这里是混淆,和类加载没关系。混淆后的类名变了,而通过Kt的反射去构造的方法中,还是使用的混淆前的类名,当然找不到对应的类。这里把Kt代码编译成字节码在反编译成Java代码,会发现里面确实是存在硬编码的。所以通过Kt反射去构造这个Kt的内部类,存在这个问题。
image

 `val rawTypeKotlin = rawType.kotlin
    // 寻找 Kotlin 主构造函数,如果找不到就不往下执行
    val constructor = rawTypeKotlin.primaryConstructor ?: return null
    constructor.isAccessible = true`

这里直接这样调用混淆的类就有问题,也就是这个方案必须要要求数据类不被混淆,加入keep规则。

小伙子,这个问题不是 Kotlin 官方提供的反射库有问题?你试一下 org.jetbrains.kotlin:kotlin-reflect 最新版本是否有这个问题,如果有的话反馈一下给 Kotlin 官方处理,临时的解决方案是先 Keep 对应的类。

找了相关资料,确实用KClass反射,不能去混淆。
在Kotlin中使用KClass进行反射时,可能会遇到混淆(obfuscation)问题。如果你的Kotlin代码被混淆,反射将不能正确工作,因为混淆会改变类和函数的名称。为了解决这个问题,你需要在混淆配置文件中保护这些反射使用的类和方法不被混淆。
在你的proguard-rules.pro(如果你使用的是ProGuard作为混淆工具)文件中,你需要添加规则来保护这些反射使用的类。

不过使用GsonFactory的开发者可能并不知道其内部实现是KClass的反射机制,因为Gson库本身的解析方案也没用这个,使用GsonFactory主要还是希望对Gson库有一些优化,既然有差异,可能踩坑。感觉可以在文档中写一下。
或者混淆规则中让开发者添加
-keepnames class com.test.rejig.** {
public (...);
}
把com.test.rejig替换成自己的包名

避免构造方法被混淆。

Kt的封装确实很好用,但是如果有更好的方案去实例化对象也能解决该问题