一种Api兼容性检测方案

2,626 阅读4分钟

简述

一般来说,SDK依赖库的Api兼容性问题一直是个隐藏的问题,通常没有很好的方式解决,即使使用语义化版本管理,在众多基础SDK的引用依赖下,不能100%保证其中一个基础SDK的Api发生不兼容的改变后,该改变可能是对外暴露方法的签名改变、方法名称改变亦或是类名包名等改变,而发生这些不兼容Api的变化后,不能保证所有依赖该基础SDK的上层SDK全部对应的升级依赖版本。当然,良好的开发模式对于基础SDK开发来讲,对外暴露的Api的改变,一般不能直接改变其方法签名以及包名类名等,而应该相应的标为@Deprecated提供向下兼容

但是,作为团队多人协作开发模式下,不能100%保证所有基础SDK的开发都以兼容方式进行Api的改变,除此之外,使用到的一些三方开源SDK,这个也不能保证它提供的Api是否是兼容的,以及在进行组件化、插件化过程中,Api兼容性问题是必须要考虑的

为什么要考虑?这里所说的Api兼容性问题,不是发生在项目编译期,而是在运行期,因为项目中的SDK依赖库依赖进来时,是已经编译好的字节码文件,所以SDK依赖库内的兼容性问题,只有在程序运行期才可出现,一般表现为Crash或无响应,且出现上述情况的前提条件是代码刚好执行到了这段,否则还是不会有任何异常

示例

一个通俗的例子,假如有ABC三个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,当然原理还是通过抽象语法树,如下:

method.instrument(CodeConverter/ExprEditor)

ApiInspect

对于Api兼容性检测,我抽象封装成了一个Gradle插件,可以很方便的引入进行工程内非兼容性Api的检测,检测后也会有相应检测结果给出

Github地址为:github.com/Sunzxyong/A…

对于检测的时机,是在应用构建阶段

对于检测结果,当App构建完成后,有两种方式进行输出,一是控制台打印,二是生成检测结果的文本文件,如下:

当有不兼容的Api时,会打印上述日志,如第一个Log的含义为:

不兼容(不存在)的Api是base库中的Tracker类,而出现的地方是在ui库中LoadingView这个类中有它的引用

而除了控制台,对应的检测信息与结果也会在build目录下的api-inspect生成

其中inspect-info为检测信息,即所检测的包以及类
其中inspect-result为检测结果,即所检测的不兼容的Api

插件地址

github.com/Sunzxyong/A…

微信公众号