简述
一般来说,SDK依赖库的Api兼容性问题一直是个隐藏的问题,通常没有很好的方式解决,即使使用语义化版本管理,在众多基础SDK的引用依赖下,不能100%保证其中一个基础SDK的Api发生不兼容的改变后,该改变可能是对外暴露方法的签名改变、方法名称改变亦或是类名包名等改变,而发生这些不兼容Api的变化后,不能保证所有依赖该基础SDK的上层SDK全部对应的升级依赖版本。当然,良好的开发模式对于基础SDK开发来讲,对外暴露的Api的改变,一般不能直接改变其方法签名以及包名类名等,而应该相应的标为@Deprecated
提供向下兼容
但是,作为团队多人协作开发模式下,不能100%保证所有基础SDK的开发都以兼容方式进行Api的改变,除此之外,使用到的一些三方开源SDK,这个也不能保证它提供的Api是否是兼容的,以及在进行组件化、插件化过程中,Api兼容性问题是必须要考虑的
为什么要考虑?这里所说的Api兼容性问题,不是发生在项目编译期,而是在运行期,因为项目中的SDK依赖库依赖进来时,是已经编译好的字节码文件,所以SDK依赖库内的兼容性问题,只有在程序运行期才可出现,一般表现为Crash
或无响应,且出现上述情况的前提条件是代码刚好执行到了这段,否则还是不会有任何异常
示例
一个通俗的例子,假如有A
、B
、C
三个SDK
A和B都依赖了C,在一个版本迭代中,其中C的一个对外暴露的方法发生了签名的变更,而A对应的更新了C的依赖,B并不知道所以没更新
那么在集成打包后,Gradle版本会自动解决传递依赖的版本冲突选择版本,假如同深度且无直接依赖都为传递依赖,那么C的相同版本类型高版本会作为冲突解决的版本,App在编译过程肯定是没问题的,而在运行到的某一个地方,则发生了Crash
,method not found exception
即使exclude
后使用直接依赖不使用传递依赖,结果还是一样的
方案
SDK依赖库Api兼容性检测方案,我们即要检测类也要检测方法的完整性
对于类的检测
我们需要获取每个依赖库所包含的所有Class的所有引用,然后一一进行该字节码对象的获取,如果不能获取到,那么这个类则不存在,即是不兼容的Api
对于方法的检测
我们需要知道某个不兼容的方法在哪里调用了,而获取某个类所有调用的方法怎么获取?我们想到抽象语法树特征,像平时我们代码的编译、IDEA工程加载、Lint检测等这些过程中,其实中间都做了抽象语法树,这个可以把一个类所有调用的方法获取到,而获取到后,同样进行该方法对象的获取,如果获取不到,那么这个方法则不存在,即是不兼容的Api
实现
对于上述两种方式的检测,调研后其实javassist
是支持的,本身提供了对一个方法进行树解析的Api,当然原理还是通过抽象语法树,如下:
1 | method.instrument(CodeConverter/ExprEditor) |
ApiInspect
对于Api兼容性检测,我抽象封装成了一个Gradle
插件,可以很方便的引入进行工程内非兼容性Api的检测,检测后也会有相应检测结果给出
Github地址为:https://github.com/Sunzxyong/ApiInspect
对于检测的时机,是在应用构建阶段
对于检测结果,当App构建完成后,有两种方式进行输出,一是控制台打印,二是生成检测结果的文本文件,如下:
当有不兼容的Api时,会打印上述日志,如第一个Log的含义为:
不兼容(不存在)的Api是base库中的Tracker类,而出现的地方是在ui库中LoadingView这个类中有它的引用
而除了控制台,对应的检测信息与结果也会在build目录下的api-inspect
生成
其中
inspect-info
为检测信息,即所检测的包以及类
其中inspect-result
为检测结果,即所检测的不兼容的Api
插件地址
https://github.com/Sunzxyong/ApiInspect