实际项目开发过程中经常会遇到和各种第三方app的对接,实际产品中也开发了SDK便于与第三方二次开发,但是这样第三方还是会有不少开发成本。件化的好处在于宿主和插件分开编译,宿主和插件的可以并行开发、互不干涉,宿主可以按实际需求进行相应插件的下载、安装及运行。网上开源的插件化框架也很多,都有各自的特点和不足,在项目中应客户的实际需求,采用了360的replugin插件化框架。
Replugin是360手机卫士2017年开源的安卓插件化开发框架,属于占坑类插件化开发框架。相比于其他插件化方案,replugin的hook点更少,经过360手机卫士众多插件多年的验证,稳定性值得信赖。Replugin宿主无需升级,即可支持新增的四大组件,hook点只有Classloader一处,易于集成,插件管理稳定成熟,支持插件安装、升级、卸载、版本管理等。
1、replugin的接入
对replugin插件的开发及集成进行了些研究,从小demo试验到将app相关组件进行插件化改造,并编写demo进行插件集成后的测试,将其中遇到的问题进行一些总结。
① 插件的开发
插件的接入主要是一些gradle配置,首先需要在项目工程的build.gradle文件(不是module的build.gradle文件)中添加360 replugin的gradle依赖,包括host和plugin的依赖。
然后在插件的build.gradle文件中添加相关配置,需要引入replugin的gradle插件,在repluginPluginConfig中配置插件的名称、包名及launcher activity,然后在dependencies中引入'com.qihoo360.replugin:replugin-plugin-lib:2.2.4'库。
然后在插件的AndroidManifest.xml文件中进行配置,填写插件名称,”com.qihoo360.plugin.version.ver”的value为100,不做改动。
以上配置完成后,插件的配置就算完成了,将插件打包成apk后将plugin1.apk后缀名改掉,改为plugin1.jar,插件的名称自己取。
② 宿主的接入
宿主的接入包括gradle配置、application的继承以及插件的引入。
在宿主module的build.gradle中引入replugin的host插件,在dependencies中引入库” com.qihoo360.replugin:replugin-host-lib:2.2.4”
然后在宿主的application中需要继承RePluginApplication并实现相应的接口,如果没采用继承方式的话,需要在application生命周期的方法中进行相应的一些初始化操作。这里采用继承的方式。最后将插件放到宿主的assets/plugins目录下。
至此完成了宿主中插件的接入,而要在宿主中调起插件,只需启动插件的launcher activity。
2、对接过程中遇到的问题
宿主apk单独运行正常并不代表集成到宿主apk能运行不出差错。
① 权限问题
插件中在AndroidManifest.xml中声明的权限,宿主中也相应要声明这些使用权限,或者动态检查申请权限。
② so库目录问题
集成后开始运行,运行时发现了so库找不到的问题。
java.lang.UnsatisfiedLinkError: com.qihoo360.replugin.PluginDexClassLoader[DexPathList[[zip file "/data/data/hik.xxx/app_plugins_v3/xxx10-10-100.jar"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]] couldn't find "libstlport_shared.so"
at java.lang.Runtime.loadLibrary(Runtime.java:366)
at java.lang.System.loadLibrary(System.java:988)
so库找不到的问题,一般是由于app中jniLibs下有多个so库目录,但实际的so库并没有这些目录中所有的cpu架构的so库,导致so库找不到。把app依赖的几个module都看了一遍后,发现确实有一个module中jniLibs目录下有除了armeabi及armeabi-v7a以外的目录,多了mips及x86目录,将其删了之后,重新编译插件,然后在宿主工程中运行,发现仍然报这个错误。最终查了些相关资料,自己尝试了多种方法后,发现要现在host的jniLibs目录下先定好所采用的cpu架构,并放上一个空so库文件进去。
LibNativeTest.so无任何实际用途,纯粹为了占坑。但为何是这样呢,这2天看了下replugin源码,偶然间瞥到如下中文注释。代码在PluginNativeLibsHelper.java中。
③ 资源未找到引起崩溃
插件内部跳转,跳转到一个activity时报资源文件abc_fade_out.xml未找到引起崩溃,找了一圈发现abc_fade_out.xml并不是插件apk中引入的资源,而是系统资源。
然错误提示指示系统的资源没找到,但实际问题肯定应该是自身程序代码问题导致的。定位到出问题的代码片段,打日志,发现确实在调用AudioPlayUtil.getInstance()之后就没往下执行了。那这个方法中究竟为何会导致崩溃呢。跟踪一下代码,看下整个的执行流程。
如上图所示,AudioPlayUtil单例初始化时传入了application,在构造函数中,通过getApplicationContext获取到Context并保存起来,因为是资源找不到引起的问题,所以我们主要关注与资源相关的代码。得到上下文Context后,其中调用了context.getResources去拿资源,最终去res的raw目录找资源。插件运行没问题,但集成后运行出现了问题,而整个方法只有一个参数传进去。所以肯定是context出现了问题。
实际运行中只是一个apk,一个应用,所以在activity中通过getApplication()获取到的是宿主的application而不是插件的。我们实际是想拿到插件中的资源,所以应该使用插件的上下文来获取。查找了RePlugin的相关api,发现只有一个getPluginContext()方法来获取插件的上下文。所以对代码进行了下改造,传入插件的上下文,资源获取问题得到解决。获取资源的地方很多,像很多Activity中使用getResources().getText()都没有报错,所以需要通过Context获取资源时,如果传入的是application上下文,则需要判断是宿主还是插件的上下文。
3、总结
replugin的接入确实很方便,但是实际集成过程中还是会出现各种问题。replugin插件可以以两种方式进行集成,内置模式和外置模式,插件以内置模式方式进行集成时,需打包到宿主apk中。以外置方式插件可以下载后再安装、运行。插件化除了集成灵活外,插件有独立的版本号,升级时可以对插件独立进行升级而不需要整个app进行升级。而且各插件可以独立并行开发,增强开发效率。但同时插件化也有不少问题,插件化技术一般都使用了hook,针对不同手机厂商的定制安卓系统,兼容性需要经过测试检验,稳定性也可能会是个问题,而且如果出现了问题,排查问题时对开发者的技术要求更高。这只是简单的集成,集成后,插件宿主间通信等,还有很多细节还需继续深入。