记一次Android依赖库版本不兼容的问题处理过程
此前我们项目组开发了相关 SDK 并集成到 App 工程中进行测试,发现业务App中的 wire (一个与 protobuf 相关的库),版本为1.5.1,而 SDK 中所依赖的版本为3.7.0,两者之间相互不兼容。如果要让业务升级到高版本的库的话,初步排查低版本中使用的某个类而高版本中已废除,单纯的就这一个类涉及100多个文件,工程量太大了,SDK中亦然。所以不能通过简单地更改版本号来解决版本冲突问题,最后经过一系列的尝试,终于解决了该问题。
分析和处理
最初集成 SDK 到 App 运行时发现报错:
1 | java.lang.NoClassDefFoundError: Failed resolution of: Lcom/squareup/wire/ProtoEnum; |
检查代码发现 ProtoEnum
位于wire 1.5.1中,打印依赖树发现App中的库被SDK所依赖的库给覆盖掉了,自动升级到新的版本,但新的版本又不存在该类。
| | | \--- com.squareup.wire:wire-runtime:1.5.1 -> 3.7.0
以为只是简单的版本冲突的问题,尝试解冲突,我们知道处理Android版本冲突主要使用 exclude``transitive
force
gralde 处理依赖的关键字解决依赖冲突,但我无论使用什么操作整个项目中所打出来的Apk只存在一个版本:要么1.5.1要么3.7.0,对比两个库:
发现高版本相比于低版本多了太多的类,以及一部分类进行了改名,至此我们可以得出一个结论:wire库高版本(3.7.1)与低版本(1.5.1)完全不兼容。 现在摆在我面前有两条路可以走:
一、手动升级App中的低版本
二、手动降级SDK中的高版本
对于第一种,发现到App中大量文件使用 Wire 中的 Message
类,虽然两个版本都有Message
类,但是两者“今非昔比”,涉及到太多的方法改动,而 Message
类在App有100+文件使用,如果一个个改过去,可能XXXXXXXX了。
两个库之间有这么大的差异,甚至1.X版本不支持kotlin,而SDK中大量代码都是使用的 Kotlin,那么第二种降低SDK的高版本也自然变得不太现实。
向大佬们请教,有被指点到:是否可以通过 ffat-aar+混淆的方式将 wire库跟SDK合并打包到一起?
!!大佬毕竟大佬,我的脑子瞬间有一种叮咚的感觉,其实这种方式也就是将SDK变向的重命名,将两个不同的版本库进行“共存”,现在要做的就是:将 SDK 中的高版本的库包名给改掉,以达到两个不同版本库之间的兼容。
说干就干,打好了库之后运行发现:
1 | java.lang.RuntimeException: Duplicate class a.a.a.a found in modules |
我当时内心就是这个表情,心想:难道wtlogin
大佬们也是想采用这种方式来避免一些库的兼容问题?这问题不大,因为混淆默认从a-z进行命名,只要给混淆再配一些参数就能避免掉这个问题,于是加上以下参数
1 | -obfuscationdictionary obfucationdictionary.txt |
当再次运行的时候,发现又报错了:
1 | java.lang.NoClassDefFoundError: Failed resolution of: Lcom/squareup/wire/ProtoAdapter; |
???ProtoAdapter
是属于3.7.0高版本的类,不是已经被混淆了么?为什么还会报这个错?
难道是使用混淆的方式是不行的?于是继续搜寻解决方案,了解到使用 jarjar.jar可以对包重新命名打包,尝试了一下运行,依然报错
1 | java.lang.NoClassDefFoundError: Failed resolution of: Lcom/squareup/wire/ProtoAdapter; |
到底哪里还有 使用 com/squareup/wire/ProtoAdapter
呢?使用jd-gui
对重新打好的 jar 包的内容进行搜索.
果然有搜到相应的内容,这是一个字符串,第一反应就是反射。我瞬间明白了,这是一串字符串并且是写死的,回到App中来,如下图所示:wire
库的作用是将 .proto 文件生成咱们通常所说的 Model 类,下图的 AudioEffect
就是通过 .proto 文件中定义好的属性生成的。
那该怎么办呢?就一个依赖库版本冲突的问题,常规的方法就这些呀,难道真的要肝一波了么?
到这里我们大概知道是怎么回事了,我们的目标很简单,就是改一个包名,但是包又在 wire-gradle-plugin
插件中,所以改wire的运行库当然是不行的……
那还有一个终极办法:改源码。
说干就干,直接拉 wire 源码
,主要关注以下几个目录:wire-gradle-plugin
: wire-gradle 插件的主要源码wire-compiler
: wire编译.proto相关的操作wire-runtime
: wire运行时所需要的类
接下来要做的就是 右键+rename,重新编译打包,也……就1000多处改动
打完包之后发现整个插件生成出来类的包还是包含"com.squareup.wire.ProtoAdapter"
,我的目标是生成:"com.squareup.xxxx_.ProtoAdapter"
,跟wire-plguin-gradle的源码,发现有这么一处:
它回在运行中重新从仓库中拉取com.squareup.wire:wire-runtime:3.7.0
,于是需要对整个 wire-runtime
也重新打包,最终生成了一系列jar包如下所示:然后将其作为 plugin
1 | classpath files('wire-profiles-3.7.0.jar') |
然后再将运行时所需要的类跟随SDK一起打包
1 | implementation files('src/libs/wire-runtime-jvm-3.7.0.jar') |
重新编译打包,run、install 成功运行!
总结
1、整个一系列操作,让我又学到了很多平时没有接触过的东西,比如:jarjar.jar,这个库对一些比较小的库存,或者说轻量级的库重新命名会比较快速的解决。本文中所述的 wire 库实在是太复杂,只能从源码层面进行操作了。还有就是在看 wire工程源码的时候又发现了一个shadowJar(利用gradle shadowjar构建包含依赖的JAR包),之前一直用 ffat-aar打入的依赖,不知道这个插件怎么样,后面学习学习试试。
2、在改整个源码之前,其实内心是比较抗拒的,因为一般改源码这种操作都是比较危险,或者更耗时,有可能就算改完了,也不一定能正常运行,一度想放弃,但实在是不想去改业务中那100+的文件,既然一条路走到黑,还是走下去吧,让我明白一定要坚持下去,不要放弃。