此前我们项目组开发了相关 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`transitiveforce` 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
2
3
4
java.lang.RuntimeException: Duplicate class a.a.a.a found in modules
etified-target-SDK-0.0.1.11-SNAPSHOT-runtime.jar
and ctlogin-0.4.23.04_lol_47-runtime.jar
(clogin-sso.clogin:0.4.23.04_lol_47)

在这里插入图片描述
我当时内心就是这个表情,心想:难道wtlogin 大佬们也是想采用这种方式来避免一些库的兼容问题?这问题不大,因为混淆默认从a-z进行命名,只要给混淆再配一些参数就能避免掉这个问题,于是加上以下参数

1
2
3
-obfuscationdictionary obfucationdictionary.txt
-classobfuscationdictionary obfucationdictionary.txt
-packageobfuscationdictionary 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
2
3
4
5
classpath files('wire-profiles-3.7.0.jar')
classpath files('wire-compiler-3.7.0.jar')
classpath files('wire-kotlin-generator-3.7.0.jar')
classpath files('wire-gradle-plugin-3.7.0.jar')
classpath files('wire-schema-jvm-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+的文件,既然一条路走到黑,还是走下去吧,让我明白一定要坚持下去,不要放弃。