Android插件化-RePlugin项目集成与使用详解
前言:前一段时间新开源了一种全面插件化的方案--RePlugin,之前一种都在关注DroidPlugin并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化的需求,另一方面也考虑到DroidPlugin不是特别稳定,Android系统每更新一次DroidPlugin可能就会出现一些Bug,毕竟Hook了Android原生的太多东西,系统一旦更新引发Bug是在所难免的。当然,这些并不能否认DroidPlugin的优秀,它的原理和思路值得我们深入探究、学习,前一段时间更新过几篇插件化的原理分析的文章(基于DrodiPlugin原理)学习过程中不得不叹服作者的思路和技术深度!前几篇小白也能看懂的插件化系列文章仍然会不定期更新,但目前我们可以先来学习学习RePlugin,毕竟多学无害,也能互相参考他们的思路,比较优缺点。
1.什么是RePlugin?
在Android开发领域,有关插件化的讨论一直热度不减。目前市面上的插件化方案虽然很多,但多数只能实现某些功能的插件化,距离开发者的预期尚有相当差距。对此,在近期GMTC全球移动技术大会上,360手机卫士主程序架构负责人张炅轩宣布,360的插件化框架RePlugin已经可以实现“全面插件化”,同时具有出色的稳定性和灵活性,可适用于各种类型的应用上。
“RePlugin预计7月份开源,这将是我们献给安卓世界最好的礼物。”360如是说。
2.RePlugin有什么用?
RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePluginTeam研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
3.RePlugin官方介绍
其主要优势有:
- 极其灵活:主程序无需升级(无需在Manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件
- 非常稳定:Hook点仅有一处(ClassLoader),无任何BinderHook!如此可做到其崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的AndroidROM
- 特性丰富:支持近乎所有在“单品”开发时的特性。包括静态Receiver、Task-Affinity坑位、自定义Theme、进程坑位、AppCompat、DataBinding等
- 易于集成:无论插件还是主程序,只需“数行”就能完成接入
- 管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等
- 数亿支撑:有360手机卫士庞大的数亿用户做支撑,三年多的残酷验证,确保App用到的方案是最稳定、最适合使用的
一、集成主工程
1、在项目根目录的build.gradle下添加RePluginHostGradle依赖:
buildscript{
repositories{
jcenter()
}
dependencies{
classpath'com.android.tools.build:gradle:2.3.3'
//1、添加RePluginHostGradle依赖
classpath'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
}
}
2、在app/build.gradle下添加RePluginHostLibrary依赖(为了更清晰的表示出代码添加的位置,将原有代码也一并贴出):
applyplugin:'com.android.application'
android{
compileSdkVersion26
buildToolsVersion"26.0.1"
defaultConfig{
applicationId"cn.codingblock.repluginstudy"
minSdkVersion21
targetSdkVersion26
versionCode1
versionName"1.0"
testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner"
}
buildTypes{
release{
minifyEnabledfalse
proguardFilesgetDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
applyplugin:'replugin-host-gradle'//集成RePlugin添加的配置
//集成RePlugin添加的配置
repluginHostConfig{
useAppCompat=true//如果项目需要支持AppComat,则需要将此配置置为true
//如果应用需要个性化配置坑位数量,则需要添加以下代码进行配置
//countNotTranslucentStandard=6
//countNotTranslucentSingleTop=2
//countNotTranslucentSingleTask=3
//countNotTranslucentSingleInstance=2
}
dependencies{
compilefileTree(dir:'libs',include:['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2',{
excludegroup:'com.android.support',module:'support-annotations'
})
compile'com.android.support:appcompat-v7:26.+'
compile'com.android.support.constraint:constraint-layout:1.0.2'
compile'com.qihoo360.replugin:replugin-host-lib:2.2.1'//集成RePlugin添加的配置
testCompile'junit:junit:4.12'
}
以上代码有三点需要注意:
- 需要将applyplugin:'replugin-host-gradle'放在android{...}之后。
- 如果项目需要支持AppComat,则需要将repluginHostConfig的userAppCompat置为true。
- 如果应用需要个性化配置坑位数量,则需要在repluginHostConfig中添加以下代码进行配置:
countNotTranslucentStandard=6 countNotTranslucentSingleTop=2 countNotTranslucentSingleTask=3 countNotTranslucentSingleInstance=2
3、让工程的Application直接继承自RePluginApplication:
publicclassMyApplicationextendsRePluginApplication{}
当然,同时不要忘了在AndroidManifest对MyApplication的相关配置。
说明:有时候由于项目原有结构的需要,我们可能不能直接使用继承RePluginApplication的方式,这个问题看来RePlugin开发者也想到了,所以还特地多了一种选择,下面是项目的Application不继承RePluginApplication的方式:
publicclassMyApplicationextendsApplication{
@Override
protectedvoidattachBaseContext(Contextbase){
super.attachBaseContext(base);
RePlugin.App.attachBaseContext(this);
}
@Override
publicvoidonCreate(){
super.onCreate();
RePlugin.App.onCreate();
}
@Override
publicvoidonLowMemory(){
super.onLowMemory();
RePlugin.App.onLowMemory();
}
@Override
publicvoidonTrimMemory(intlevel){
super.onTrimMemory(level);
RePlugin.App.onTrimMemory(level);
}
@Override
publicvoidonConfigurationChanged(ConfigurationnewConfig){
super.onConfigurationChanged(newConfig);
RePlugin.App.onConfigurationChanged(newConfig);
}
}
二、集成插件
新建一个工程做为插件APP,这里为了方便起见,直接在主工程中新建了一个Module。
1、同集成主工程类似,在根目录的build.gradle添加RePluginPluginGradle依赖(若是单独创建插件工程,则不需要添加注释1下面的代码):
buildscript{
repositories{
jcenter()
}
dependencies{
classpath'com.android.tools.build:gradle:2.3.3'
//1、添加RePluginHostGradle依赖(主工程用)
classpath'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
//2、添加RePluginPluginGradle依赖(插件工程用)
classpath'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
}
}
2、在app/build.gradle中添加replugin-plugin-gradle插件和replugin-plugin-lib依赖:
applyplugin:'com.android.application'
android{
...
}
applyplugin:'replugin-plugin-gradle'//集成RePlugin添加的配置
dependencies{
compilefileTree(dir:'libs',include:['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2',{
excludegroup:'com.android.support',module:'support-annotations'
})
compile'com.android.support:appcompat-v7:26.+'
compile'com.android.support.constraint:constraint-layout:1.0.2'
compile'com.qihoo360.replugin:replugin-plugin-lib:2.2.1'//集成RePlugin添加的配置
testCompile'junit:junit:4.12'
}
三、管理插件
RePlugin对插件定义两种方式一种是外置插件、一种是内置插件。
- 外置插件:即从网络下载或者从SD卡中获得的,以.apk结尾。
- 内置插件:内置于APP之中,并随APP一并发版,需要将插件apk改成.jar结尾放入主程序的assets/plugins目录。
(一)外置插件的安装(升级)、启动、卸载
安装插件:
PluginInfopluginInfo=RePlugin.install(Environment.getExternalStorageDirectory().getPath().toString()+"/plugin1.apk"); System.out.println(pluginInfo);
同时别忘了添加文件读写的权限。输出日下:
10-3016:10:23.76920280-20280/cn.codingblock.repluginstudyI/System.out:PInfo{[APK][DEX_EXTRACTED]processes=[]js={"pkgname":"cn.codingblock.plugin1","name":"cn.codingblock.plugin1","low":10,"high":10,"ver":1,"verv":2814792716779521,"path":"\/data\/user\/0\/cn.codingblock.repluginstudy\/app_p_a\/-347346251.jar","type":11,"frm_ver":4}dex=/data/data/cn.codingblock.repluginstudy/app_p_od/-347346251.dexnlib=/data/data/cn.codingblock.repluginstudy/app_p_n/-347346251}
安装成功了!(升级插件也是用install()方法,不可降级,同本版可覆盖安装)
启动插件
先来看一下RePlugin.java中启动插件相关的源码
/** *创建一个用来定向到插件组件的Intent*
*推荐用法:
*
*Intentin=RePlugin.createIntent("clean","com.qihoo360.mobilesafe.clean.CleanActivity"); **当然,也可以用标准的Android创建方法:
*
*Intentin=newIntent();* *@parampluginName插件名 *@paramcls目标全名 *@return可以被RePlugin识别的Intent *@since1.0.0 */ publicstaticIntentcreateIntent(StringpluginName,Stringcls){ Intentin=newIntent(); in.setComponent(createComponentName(pluginName,cls)); returnin; } /** *开启一个插件的Activity*in.setComponent(newComponentName("clean","com.qihoo360.mobilesafe.clean.CleanActivity")); *
*其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象 * *@paramcontextContext对象 *@paramintent要打开Activity的Intent,其中ComponentName的Key必须为插件名 *@return插件Activity是否被成功打开? *FIXME是否需要Exception来做? *@see#createIntent(String,String) *@since1.0.0 */ publicstaticbooleanstartActivity(Contextcontext,Intentintent){ //TODO先用旧的开启Activity方案,以后再优化 ComponentNamecn=intent.getComponent(); if(cn==null){ //TODO需要支持Action方案 returnfalse; } Stringplugin=cn.getPackageName(); Stringcls=cn.getClassName(); returnFactory.startActivityWithNoInjectCN(context,intent,plugin,cls,IPluginManager.PROCESS_AUTO); }
根据RePlugin的startActivity()和createIntent()方法注释中的示例可知,启动插件需要先用插件的名字和目标Activity的全路径创建一个Intent,然后调用RePlugin.startActviity()启动即可:
Intentintent=RePlugin.createIntent("Plugin1","cn.codingblock.plugin1.MainActivity");
if(!RePlugin.startActivity(MainActivity.this,intent)){
Toast.makeText(mContext,"启动失败",Toast.LENGTH_LONG).show();
}
点击按钮,输出如下:
10-3016:21:02.46420280-20280/cn.codingblock.repluginstudyD/RePlugin.ws001:startactivity:intent=Intent{cmp=Plugin1/cn.codingblock.plugin1.MainActivity}plugin=Plugin1activity=cn.codingblock.plugin1.MainActivityprocess=-2147483648
10-3016:21:02.46420280-20280/cn.codingblock.repluginstudyD/RePlugin.ws001:startactivity:intent=Intent{cmp=Plugin1/cn.codingblock.plugin1.MainActivity}plugin=Plugin1activity=cn.codingblock.plugin1.MainActivityprocess=-2147483648download=true
10-3016:21:02.46420280-20280/cn.codingblock.repluginstudyD/RePlugin.ws001:plugin=Plugin1notfound,startdownload...
10-3016:21:02.46920280-20280/cn.codingblock.repluginstudyD/RePlugin.ws001:isNeedToDownload():V5filenotexists.Plugin=Plugin1
启动失败了!(插件名称确实是:Plugin1,而不是plugin1)
把==createIntent()方法的第一参数换成插件的包名cn.codingblock.plugin1==试一试,居然可以了。
但是,注释总不会这样赤裸裸的坑我们吧!
卸载插件
RePlugin.uninstall("Plugin1");
点击卸载,输入如下:
10-3016:31:21.9885006-5006/cn.codingblock.repluginstudyD/RePlugin.ws001:MP.pluginUninstall...pluginName=Plugin1 10-3016:31:21.9885006-5006/cn.codingblock.repluginstudyD/RePlugin.ws001:Notinstalled.pluginName=Plugin1
没卸载成功?哈哈,这个简单,原套路把参数换成包名,果然可以了:
10-3016:41:46.17910193-10193/cn.codingblock.repluginstudyD/RePlugin.ws001:MP.pluginUninstall...pluginName=cn.codingblock.plugin1
10-3016:41:46.20210193-10193/cn.codingblock.repluginstudyD/RePlugin.ws001:sendIntentpr=cn.codingblock.repluginstudyintent=Intent{act=ACTION_UNINSTALL_PLUGIN(hasextras)}
10-3016:41:46.20310193-10193/cn.codingblock.repluginstudyD/RePlugin.ws001:Clearplugincache.pn=cn.codingblock.plugin1
10-3016:41:46.20410193-10193/cn.codingblock.repluginstudyD/RePlugin.ws001:removeInfoplugintable:info=PInfo{[APK]processes=[]js={"pkgname":"cn.codingblock.plugin1","name":"cn.codingblock.plugin1","low":10,"high":10,"ver":1,"verv":2814792716779521,"path":"\/data\/user\/0\/cn.codingblock.repluginstudy\/app_p_a\/-347346251.jar","type":11,"frm_ver":4,"used":true}dex=/data/user/0/cn.codingblock.repluginstudy/app_p_od/-347346251.dexnlib=/data/user/0/cn.codingblock.repluginstudy/app_p_n/-347346251}rc=true
10-3016:41:46.20410193-10193/cn.codingblock.repluginstudyD/RePlugin.ws001:cachedfilename:cn.codingblock.plugin1->null
10-3016:41:46.27510193-10263/cn.codingblock.repluginstudyV/RenderScript:0xb34e8000Launchingthread(s),CPUs4
启动插件那里毕竟在官方教程里面找不到,但是Plugin.uninstall()方法传入插件名即可这可是官方文档说的,这次不会是官方文档和源码注释合起伙来坑我们把?经过多次试验后,有个有趣的发现:对于启动插件创建Intent的createIntent()方法和卸载插件的RePlugin.uninstall()方法,如果项目是使用继承RePluginApplication方式的话,参数传包名才生效;如果不是继承的方式传插件名才生效!(本人是在一款小米3手机上试验的,由于并没有广泛测试,所以不保证其他手机也是这个套路)这真是奇了葩了!
卸载插件时有一点需要注意:如果插件正在运行,则不会立即卸载插件,而是将卸载诉求记录下来。直到所有“正在使用插件”的进程结束并重启后才会生效。(引自官方说明)
(二)内置插件
添加内置插件非常简单,首先在主工程的assets目录下创建一个plugins文件夹,然后将要作为插件的apk后缀名改成.jar并放入到新建的plugins文件夹下,剩下的事情就不用管了,交给RePlugin即可,也就说,框架会自动加载插件。
- 内置插件无需开发者安装,启动方式和外置插件一致,但不可删除。
- 内置插件可通过RePlugin.install()升级(需要先将升级包下载好),升级后等同于外置插件。
四、小结
初步体验了一下发现,虽然目前有可能会有那么一点坑需要踩一踩,在使用起来也不比DroidPlugin方便,需要在宿主和插件两端都要做集成工作。但总体明显发现,这次的插件化框架明显比以前那些的插件化框架资料更加的全面、丰富,而且从wiki上发现RePlugin团队充满了很大的热情在孜孜不倦维护、更新,并且计划明确,哪些功能在未来会添加、哪些功能在未来会被舍弃,一目了然,让我们更加看到了RePlugin美好的未来,我相信在未来的插件化领域即使RePlugin不能一家独大,也必然处于一个非常重要的地位!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。