背景信息
背景是最近我们提供的SDK给合作方接入,对方接入的时候编译报这个错误。
报错内容如下
Exception in thread "main" java.lang.IllegalStateException: Expected a load for Lorg/yeshen/hello/test/u9; to set up parameter 0 for org/yeshen/hello/test/u9$$Lambda$0 but got 89
at com.google.common.base.Preconditions.checkState(Preconditions.java:756)
at com.google.devtools.build.android.desugar.LambdaDesugaring$InvokedynamicRewriter.attemptAllocationBeforeArgumentLoads(LambdaDesugaring.java:535)
at com.google.devtools.build.android.desugar.LambdaDesugaring$InvokedynamicRewriter.visitInvokeDynamicInsn(LambdaDesugaring.java:420)
at org.objectweb.asm.ClassReader.a(Unknown Source)
at org.objectweb.asm.ClassReader.b(Unknown Source)
at org.objectweb.asm.ClassReader.accept(Unknown Source)
at org.objectweb.asm.ClassReader.accept(Unknown Source)
at com.google.devtools.build.android.desugar.Desugar.desugarClassesInInput(Desugar.java:401)
at com.google.devtools.build.android.desugar.Desugar.desugarOneInput(Desugar.java:326)
at com.google.devtools.build.android.desugar.Desugar.desugar(Desugar.java:280)
at com.google.devtools.build.android.desugar.Desugar.main(Desugar.java:584)FAILURE: Build failed with an exception.* What went wrong:
Execution failed for task ':app:transformClassesWithDesugarForDebug'.
原因
// 合作方配置
classpath 'com.android.tools.build:gradle:3.0.1'// 我方配置
classpath 'com.android.tools.build:gradle:4.0.1'
沟通之后,我调整了 com.android.tools.build:gradle
,和对方调整成一致之后,重现到了问题。
查了相关资料 1,大概是java jdk8支持了Lambda表达式,但是在Android中为了向下兼容,虽然支持了jdk8的语法,但是在编译 dex 阶段会 desugar 成内部类的实现。这个报错是两个 tools.build 的版本 (It was made the default dexer in Android Gradle plugin 3.1 and it then became responsible for desugaring in 3.2.) 2,对 Lambda 的处理有区别导致的。
解决思路
所以我理解解决思路有几个:
-
同步版本(我这边降低版本,或者对方升级版本)
=> 沟通后,对方表示无法降低版本
=> 我这边降低版本一是会导致原本正常使用的kotlin出现问题;二是我这边很多内部的依赖,一揽子都需要重新编译,维护成本很高。 -
代码中逐行替换Lambda表达式成匿名内部类
=> 可以绕过问题,但是代码中已经到处都是Lambda表达式,维护成本巨高不下。 -
从Android编译工具链中找相关的降级/兼容配置
=> 未找到。我这边使用的是D8,在D8中没找到配置。 -
参考 RetroLambda 提供插件给合作方做兼容 3
=> 自己写的话,大概是:app:compileDebugJavaWithJavac
(对应javac)之后,和app:transformDexArchiveWithDexMergerForDebug
(对应dx)之前,根据java/lang/invoke/LambdaMetafactory.metafactory
方法,直接将原本在运行时生成在内存中的J8Sample\$\$Lambda\$1.class
,在javac编译结束之后,dx编译dex之前,直接生成到本地,并使用生成的J8Sample\$\$Lambda\$1
类修改J8Sample.class
字节码文件,将J8Sample.class
中的invokedynamic
指令替换成invokestatic
指令。
=> 或者让合作方直接使用 retrolambda 插件。
=> 修改合作方的编译工具,对方大概率不接受。 -
把代码抽离成dex,运行时动态加载dex代码,而不是直接给对方接入使用。
=> 自己写的话,大概是:- 获取已经输出的sdk, 把
代码
和资源
分离,资源
作为新sdk的一部分直接使用。 - 把步骤1获得的
代码
编译后输出成apk,放在asset下. - 动态加载asset中的
代码
,完成功能。
- 获取已经输出的sdk, 把
参考资料
https://jakewharton.com/androids-java-8-support/ ↩︎
https://android-developers.googleblog.com/2017/08/next-generation-dex-compiler-now-in.html ↩︎
https://tech.meituan.com/2019/10/17/android-java-8.html ↩︎