接入sdk库的几点心得体会
由于项目的特殊性,最近一直在往app工程里接入(更新)第三方sdk,并且app的一些基础控件也被其他项目组封装成了sdk来导入。在不停的sdk更新迭代过程中碰到了一些问题,记录一下为后续排查问题依据。
-
sdk本身内部可以依赖其他sdk库(假设为库A),此时库A不需要app显示导入,它在导入sdk时会自动导入进来(sdk发布时的aar文件有一个配对的pom.xml文件,他可以用来描述依赖),但可以通过
exclude
关键字取消对库A的自动导入。 -
如果sdk本身依赖另一个库A,且app本身也对A库有导入依赖,这时就存在有两个地方对同一个库A的导入了。如果两处的版本号不一致的话,最终库A的什么版本是哪个呢?是以app的导入为准吗?还是以版本高的为准?结论是以版本号高的引用那个为准(打印依赖有向图可以看出)。
问题:明明我只是升级了sdk的版本,但最终的依赖库A的版本怎么也变了?由于库A的内容变了,那调用库A的代码就有可能存在不一致性了,特别是涉及到大版本更新时。这是可以通过依赖关系图来查看依赖的变化点和变化来源在哪里。 -
另外一种情况:还是以上面的结论为基础,假设app依赖的库A版本比较高(最终以这个高版本为准),且这个高版本库A相比sdk依赖的低版本刚好删掉某一个API接口(比如是大版本升级),而sdk内部又刚好有调用这个删掉了的接口。
问题:此时是app竟然可以编译过!!! 只是会等到在运行时抛出找不到方法的异常。这怎么办?很严重,很隐秘的问题。
原因:针对这种情况刚开始还不能理解,都少了一个调用方法了为啥编译时没报错?后来想想也是正常的,这是因为app导入的sdk是以aar的形式引入的,而这个aar在sdk发布时就已经编译成字节码(class)了,所以此时编译app时编译器并不会再用javac去编译aar了(直接将aar的class类merge到app中),于是最终编译器没有检查调用关系的错误。
验证:即使使用exclude
将依赖库A排除依赖(最终的apk不包含库A的代码)也仍可以编译过。
今天碰到了这种情况,一时无法理解:
首先是一个sdk依赖的库A低版本(3.0.1)的接口:
//3.0.1
public static Context build(Activity activity, boolean param1, boolean param2) {
//...
}
然后是app依赖的库A高版本(3.0.3)的接口:
//3.0.3
public static Context build(Context context, boolean param1, boolean param2) {
//...
}
3.0.1版本与3.0.3版本的差异只是build()函数的第一个参数Activity
变为了Context
,但app升级了库A3.0.3版本后编译能通过,sdk内部却必现抛找不到方法异常:
java.lang.NoSuchMethodError: No static method build(Landroid/app/Activity;ZZ)Landroid/content/Context;
且我测试在app里调用这个build()方法又是正常的。为什么呢?原因就是上面说的(删掉了方法与修改了方法签名本质上是一样的),即使这里满足多态性(Context
是Activity
的基类)也不行。
- 在编写sdk时,暴露的API类和接口不宜轻易改动,否则可能会出现上述的依赖问题。废弃的方法和类可以使用注解
@Deprecated
标识起来,需要改动的接口使用方法重载的方式增加接口,而不是去改动接口。
查看app的依赖关系的命令:
./gradlew :app:dependencies