前言:前一段时间新开源了一种全面插件化的方案-- RePlugin,之前一直都在关注 DroidPlugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化的需求,另一方面也考虑到 DroidPlugin 不是特别稳定,Android系统每更新一次 DroidPlugin 可能就会出现一些 Bug,毕竟 Hook 了 Android 原生的太多东西,系统一旦更新引发 Bug 是在所难免的。当然,这些并不能否认 DroidPlugin 的优秀,它的原理和思路值得我们深入探究、学习,前一段时间更新过几篇插件化的原理分析的文章(基于 DrodiPlugin 原理)学习过程中不得不叹服作者的思路和技术深度!前几篇小白也能看懂的插件化系列文章仍然会不定期更新,但目前我们可以先来学习学习 RePlugin,毕竟多学无害,也能互相参考他们的思路,比较优缺点。

RePlugin 是一套完整的、稳定的、适合全面使用的、占坑类插件化方案:

  • 完整的:让插件运行起来“像单品那样”,支持大部分特性。
  • 稳定的:官方宣称,其框架奔溃率“万分之一”。
  • 适合全面使用的:其目的是让应用内的“所有功能皆为插件”。
  • 占坑类:以稳定为前提的 Manifest 占坑思路。
  • 插件化方案:基于 Android 原生 API 和语言来开发,充分利用原生特性。

(以上定义引自官方wiki)

一、集成主工程

1、在项目根目录的 build.gradle 下添加 RePlugin Host Gradle 依赖:

 buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// 1、添加RePlugin Host Gradle依赖
classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
}
}

2、在 app/build.gradle 下添加 RePlugin Host Library 依赖(为了更清晰的表示出代码添加的位置,将原有代码也一并贴出):

 apply plugin: 'com.android.application'

 android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "cn.codingblock.repluginstudy"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} apply plugin: 'replugin-host-gradle'// 集成 RePlugin 添加的配置 // 集成 RePlugin 添加的配置
repluginHostConfig {
useAppCompat = true // 如果项目需要支持 AppComat,则需要将此配置置为 true
// 如果应用需要个性化配置坑位数量,则需要添加以下代码进行配置
// countNotTranslucentStandard = 6
// countNotTranslucentSingleTop = 2
// countNotTranslucentSingleTask = 3
// countNotTranslucentSingleInstance = 2
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: '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'
}

以上代码有三点需要注意:

  • 需要将 apply plugin: 'replugin-host-gradle' 放在 android {...} 之后。
  • 如果项目需要支持 AppComat,则需要将 repluginHostConfig 的 userAppCompat 置为 true。
  • 如果应用需要个性化配置坑位数量,则需要在 repluginHostConfig 中添加以下代码进行配置:
countNotTranslucentStandard = 6
countNotTranslucentSingleTop = 2
countNotTranslucentSingleTask = 3
countNotTranslucentSingleInstance = 2

3、让工程的 Application 直接继承自 RePluginApplication:

  public class MyApplication extends RePluginApplication { } 

当然,同时不要忘了在 AndroidManifest 对 MyApplication 的相关配置。

  • 说明:有时候由于项目原有结构的需要,我们可能不能直接使用继承 RePluginApplication 的方式,这个问题看来 RePlugin 开发者也想到了,所以还特地多了一种选择,下面是项目的 Application 不继承 RePluginApplication 的方式:
 public class MyApplication extends Application {

     @Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
RePlugin.App.attachBaseContext(this);
} @Override
public void onCreate() {
super.onCreate();
RePlugin.App.onCreate();
} @Override
public void onLowMemory() {
super.onLowMemory();
RePlugin.App.onLowMemory();
} @Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
RePlugin.App.onTrimMemory(level);
} @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
RePlugin.App.onConfigurationChanged(newConfig);
}
}

二、集成插件

新建一个工程做为插件APP,这里为了方便起见,直接在主工程中新建了一个 Module。

1、同集成主工程类似,在根目录的 build.gradle 添加 RePlugin Plugin Gradle 依赖(若是单独创建插件工程,则不需要添加注释1下面的代码):

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// 1、添加RePlugin Host Gradle依赖(主工程用)
classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
// 2、添加RePlugin Plugin Gradle依赖(插件工程用)
classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
}
}

2、在 app/build.gradle 中添加 replugin-plugin-gradle 插件和 replugin-plugin-lib 依赖:

apply plugin: 'com.android.application'

android {
...
} apply plugin: 'replugin-plugin-gradle' // 集成 RePlugin 添加的配置 dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: '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目录。

(一)外置插件的安装(升级)、启动、卸载

  • 安装插件:
PluginInfo pluginInfo = RePlugin.install(Environment.getExternalStorageDirectory().getPath().toString() + "/plugin1.apk");
System.out.println(pluginInfo);

同时别忘了添加文件读写的权限。 输出日下:

10-30 16:10:23.769 20280-20280/cn.codingblock.repluginstudy I/System.out: PInfo { <cn.codingblock.plugin1:1(4)> [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.dex nlib=/data/data/cn.codingblock.repluginstudy/app_p_n/-347346251 }

安装成功了! (升级插件也是用 install() 方法,不可降级,同本版可覆盖安装)

  • 启动插件

先来看一下 RePlugin.java 中启动插件相关的源码:

 /**
* 创建一个用来定向到插件组件的Intent <p>
* <p>
* 推荐用法: <p>
* <code>
* Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity");
* </code> <p>
* 当然,也可以用标准的Android创建方法: <p>
* <code>
* Intent in = new Intent(); <p>
* in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"));
* </code>
*
* @param pluginName 插件名
* @param cls 目标全名
* @return 可以被RePlugin识别的Intent
* @since 1.0.0
*/
public static Intent createIntent(String pluginName, String cls) {
Intent in = new Intent();
in.setComponent(createComponentName(pluginName, cls));
return in;
} /**
* 开启一个插件的Activity <p>
* 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象
*
* @param context Context对象
* @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名
* @return 插件Activity是否被成功打开?
* FIXME 是否需要Exception来做?
* @see #createIntent(String, String)
* @since 1.0.0
*/
public static boolean startActivity(Context context, Intent intent) {
// TODO 先用旧的开启Activity方案,以后再优化
ComponentName cn = intent.getComponent();
if (cn == null) {
// TODO 需要支持Action方案
return false;
}
String plugin = cn.getPackageName();
String cls = cn.getClassName();
return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
}

根据 RePlugin 的 startActivity() 和 createIntent() 方法注释中的示例可知,启动插件需要先用插件的名字和目标Activity的全路径创建一个 Intent,然后调用 RePlugin.startActviity() 启动即可:

Intent intent = RePlugin.createIntent("Plugin1", "cn.codingblock.plugin1.MainActivity");
if (!RePlugin.startActivity(MainActivity.this, intent)) {
Toast.makeText(mContext, "启动失败", Toast.LENGTH_LONG).show();
}

点击按钮,输出如下:

10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: start activity: intent=Intent { cmp=Plugin1/cn.codingblock.plugin1.MainActivity } plugin=Plugin1 activity=cn.codingblock.plugin1.MainActivity process=-2147483648
10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: start activity: intent=Intent { cmp=Plugin1/cn.codingblock.plugin1.MainActivity } plugin=Plugin1 activity=cn.codingblock.plugin1.MainActivity process=-2147483648 download=true
10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: plugin=Plugin1 not found, start download ...
10-30 16:21:02.469 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: isNeedToDownload(): V5 file not exists. Plugin = Plugin1

启动失败了!(插件名称确实是:Plugin1,而不是 plugin1 )

把 createIntent() 方法的第一参数换成插件的包名 cn.codingblock.plugin1 试一试,居然可以了。

但是,注释总不会这样赤裸裸的坑我们吧!

  • 卸载插件
RePlugin.uninstall("Plugin1");

点击卸载,输入如下:

10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=Plugin1
10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: Not installed. pluginName=Plugin1

没卸载成功?哈哈,这个简单,原套路把参数换成包名,果然可以了:

10-30 16:41:46.179 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=cn.codingblock.plugin1
10-30 16:41:46.202 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: sendIntent pr=cn.codingblock.repluginstudy intent=Intent { act=ACTION_UNINSTALL_PLUGIN (has extras) }
10-30 16:41:46.203 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: Clear plugin cache. pn=cn.codingblock.plugin1
10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: removeInfo plugin table: info=PInfo { <cn.codingblock.plugin1:1(4)> [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.dex nlib=/data/user/0/cn.codingblock.repluginstudy/app_p_n/-347346251 } rc=true
10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: cached filename: cn.codingblock.plugin1 -> null
10-30 16:41:46.275 10193-10263/cn.codingblock.repluginstudy V/RenderScript: 0xb34e8000 Launching thread(s), CPUs 4

启动插件那里毕竟在官方教程里面找不到,但是 Plugin.uninstall() 方法传入插件名即可这可是官方文档说的,这次不会是官方文档和源码注释合起伙来坑我们把? 经过多次试验后,有个有趣的发现:对于启动插件创建 Intent 的createIntent() 方法和 卸载插件的 RePlugin.uninstall() 方法,如果项目是使用继承 RePluginApplication 方式的话,参数传包名才生效;如果不是继承的方式传插件名才生效!(本人是在一款小米3手机上试验的,由于并没有广泛测试,所以不保证其他手机也是这个套路)这真是奇了葩了!

卸载插件时有一点需要注意:如果插件正在运行,则不会立即卸载插件,而是将卸载诉求记录下来。直到所有“正在使用插件”的进程结束并重启后才会生效。(引自官方说明)

(二)内置插件

添加内置插件非常简单,首先在主工程的 assets 目录下创建一个 plugins 文件夹,然后将要作为插件的 apk 后缀名改成 .jar 并放入到新建的 plugins 文件夹下,剩下的事情就不用管了,交给 RePlugin 即可,也就说,框架会自动加载插件。

  • 内置插件无需开发者安装,启动方式和外置插件一致,但不可删除。
  • 内置插件可通过 RePlugin.install() 升级(需要先将升级包下载好),升级后等同于外置插件。

四、小结

初步体验了一下发现,虽然目前有可能会有那么一点坑需要踩一踩,在使用起来也不比 DroidPlugin 方便,需要在宿主和插件两端都要做集成工作。但总体明显发现,这次的插件化框架明显比以前那些的插件化框架资料更加的全面、丰富,而且从 wiki 上发现 RePlugin 团队充满了很大的热情在孜孜不倦维护、更新,并且计划明确,哪些功能在未来会添加、哪些功能在未来会被舍弃,一目了然,让我们更加看到了 RePlugin 美好的未来,我相信在未来的插件化领域即使 RePlugin 不能一家独大,也必然处于一个非常重要的地位!

Android插件化-RePlugin项目集成与使用的更多相关文章

  1. Android 全面插件化 RePlugin 流程与源码解析

    转自 Android 全面插件化 RePlugin 流程与源码解析 RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发普 ...

  2. 有关Android插件化思考

    最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接.随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内 ...

  3. Android 插件化开发(四):插件化实现方案

    在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...

  4. Android插件化开发

    客户端开发给人的印象往往是小巧,快速奔跑.但随着产品的发展,目前产生了大量的门户型客户端.功能模块持续集成,开发人员迅速增长.不同的开发小组开发不同的功能模块,甚至还有其他客户端集成进入.能做到功能模 ...

  5. Android 插件化和热修复知识梳理

    概述 在Android开发中,插件化和热修复的话题越来越多的被大家提及,同时随着技术的迭代,各种框架的发展更新,插件化和热修复的框架似乎已经日趋成熟,许多开发者也把这两项技术运用到实际开发协作和正式的 ...

  6. 《Android插件化开发指南》面世

    本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...

  7. Android插件化技术——原理篇

    <Android插件化技术——原理篇>     转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...

  8. Android 插件化 动态升级

    最新内容请见原文:Android 插件化 动态升级 不少朋友私信以及 Android开源交流几个 QQ 群 中都问到这个问题,这里简单介绍下 1.作用 大多数朋友开始接触这个问题是因为 App 爆棚了 ...

  9. Android插件化的思考——仿QQ一键换肤,思考比实现更重要!

    Android插件化的思考--仿QQ一键换肤,思考比实现更重要! 今天群友希望写一个关于插件的Blog,思来想去,插件也不是很懂,只是用大致的思路看看能不能模拟一个,思路还是比较重要的,如果你有兴趣的 ...

随机推荐

  1. Spring MVC知识点整理

    网上Spring MVC相关知识点的介绍已经有很多了,但是大部分文章都是介绍其中的一部分知识点. 本文希望能够向读者做一个基本整体的介绍,首先我们先来了解下Spring MVC的基础接口和组件.   ...

  2. ubuntu中设置php7.0-fpm开机自启动

    1.编写/etc/init/php7.0-fpm脚本如下 sudo vim /etc/init/php7.0-fpm #!/bin/sh### BEGIN INIT INFO# Provides: p ...

  3. MVC查询数据接收及校验

    本来想写一篇aspx的TreeView控件绑值的文章的,在写案例的时候,写了一半,发现有些地方还得考虑以下,就留待下次了. 这一篇的话,是最近在开发一个项目的时候,有大量的页面和数据表,需要花式查询, ...

  4. angular学习笔记01

    angular.js路由功能 用于实现单页应用 //html 代码 <div ng-view></div> //js代码 angular.module('myM1',['ng' ...

  5. Python 接口测试(二)

    三:http状态码含义(来源于w3school): 状态码: 1xx: 信息 消息:          描述: 100 Continue   服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客 ...

  6. Day4 装饰器——迭代器——生成器

    一 装饰器 1.1 函数对象 一 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二 利用该特性,优雅的取代多 ...

  7. 【HIVE】sql语句转换成mapreduce

    1.hive是什么? 2.MapReduce框架实现SQL基本操作的原理是什么? 3.Hive怎样实现SQL的词法和语法解析? 连接:http://www.aboutyun.com/thread-20 ...

  8. Linux入门之常用命令(2)

    (三) 链接文件 ln [-s] [源文件] [目标文件]       -s表示符号链接 没有则是硬链接 硬链接是一个独立文件 (相当于一个副本) 符号链接是一个链接文件(相当于一个快捷方式) 但是修 ...

  9. 关于JetBrains CLion 激活 (CLion License Activation)的解决办法,带hosts详细修改

    CLion版本号:JetBrains CLion 2017.2.1 第一行选择Activite,第二行Activate license with:选择Activation code. 这个时候里面的代 ...

  10. GCD hdu2588

    GCD Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...