一.概述
通过本篇文章的学习,你将学会:
1.什么是组件化和插件化
2.RePlugin集成到自己的项目中
3.RePlugin的使用
二.组件化和插件化
组件化
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
android工程的组件一般分为两种,lib组件和application组件,application组件是指该组件本身就可以运行并打包成apk,lib组件是指该组件属于app的一部分,可以供其它组件使用但是本身不能打包成apk
插件化
插件化是将一个apk根据业务功能拆分成不同的子apk(也就是不同的插件),包括一个宿主和多个插件,每个子apk可以独立编译打包,最终发布上线的是集成后的apk。在apk使用时,每个插件是动态加载的,插件也可以进行热修复和热更新。
利用插件化方案,可以让您的应用变得“小而精”。只有当用户需要使用某个特定功能时,才可以下载并开启,且可以随时卸载插件。不用去应用市场等到大包升级,用户可以随时体验到新版的应用。
组件化和插件化区别
技术 | 单位 | 实现内容 | 灵活性 | 特性 | 静动态 |
---|---|---|---|---|---|
组件化 | moudle | 是解耦与加快编译,隔离不需要关注的部分 | 按加载时机切换,是作为lib,还是apk | 组:组本来就是一个系统,每个组件不是真正意义上的独立模块 | 静态加载 |
插件化 | apk | 是解耦与加快编译,同时实现热插拔也就是热更新 | 加载的是apk,可以动态下载,动态更新,比组件化更灵活 | 插:是独立的apk,每个插件可以作为一个完全独立的apk运行,也可以和其他插件集成为大apk | 动态加载,只用真正使用某个插件时,才加载该插件 |
插件化是基于多APK的,而组件化本质上还是只有一个 APK。组件化和插件化的最大区别(应该也是唯一区别)就是组件化在运行时不具备动态添加和修改组件的功能,但是插件化是可以的。
三.RePlugin的集成
插件化开发分为主程序和其他的插件
主程序接入
1.项目根目录添加RePlugin Host Gradle 依赖:
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依赖:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.4'
...
}
}
2.添加 RePlugin Host Library 依赖
在 app/build.gradle 中应用 replugin-host-gradle 插件,并添加 replugin-host-lib 依赖:
// ATTENTION!!! Must be PLACED AFTER "android{}" to read the applicationId
apply plugin: 'replugin-host-gradle'
/**
* 配置项均为可选配置,默认无需添加
* 更多可选配置项参见replugin-host-gradle的RepluginConfig类
* 可更改配置项参见 自动生成RePluginHostConfig.java
*/
repluginHostConfig {
/**
* 是否使用 AppCompat 库
* 不需要个性化配置时,无需添加
*/
useAppCompat = true
}
dependencies {
compile 'com.qihoo360.replugin:replugin-host-lib:2.2.4'
...
}
注意:apply plugin: 'replugin-host-gradle'需要放在android{}的后面,防止出现无法读取applicationId,导致生成的坑位出现异常。
3.配置application
第一种方式:可以让我们自定义的application直接继承RePluginApplication
第二种方式:
public class MainApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
RePlugin.App.attachBaseContext(this);
....
}
@Override
public void onCreate() {
super.onCreate();
RePlugin.App.onCreate();
....
}
}
注意:请将RePlugin.App的调用方法,放在“仅次于super.xxx()”方法的后面,不要忘记清单文件的注册
插件接入
1.添加 RePlugin Plugin Gradle 依赖
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-plugin-gradle 依赖:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.4'
...
}
}
2.添加 RePlugin Plugin Library 依赖:
apply plugin: 'replugin-plugin-gradle'
dependencies {
compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.4'
...
}
注意:apply plugin: 'replugin-host-gradle'需要放在android{}的后面,防止出现无法读取applicationId
3.在清淡文件
注意:插件的versionCode也可以用于我们进行插件的版本升级,通过服务器获取插件的最新版本,与当前插件本本对比进行更新。
PluginInfo info = RePlugin.getPluginInfo(pluginName);
if (info.getVersion() < serviceVersion) {//插件最新版本号由接口获得,然后进行对比,插件版本低于接口的版本就下载更新
downPlugin(context, "http://插件地址", pluginName, activityName, true);
}
通过以上分别对主程序和插件的接入,我们就可以进行插件化开发,以下还考虑到其安全性:
安全与签名校验
对外来的Dex和Apk做“校验”,需要:
1.打开签名校验
第一种方式:继承RePluginApplication:在创建 RePluginConfig 时调用其 setVerifySign(true) 即可。
第二种方式:非继承:需要在调用 RePlugin.App.attachBaseContext() 的地方,传递RePluginConfig,并设置setVerifySign即可:
RePluginConfig c = new RePluginConfig();
c.setVerifySign(true);
...
RePlugin.App.attachBaseContext(context, c);
2.加入合法签名
调用 RePlugin.addCertSignature() 来完成。
RePlugin.addCertSignature("379C790B7B726B51AC58E8FCBCFEB586");
其中,其参数传递的是签名证书的MD5,且去掉“:”’。
四.RePlugin的使用
插件(子apk包)我们分为内置插件和外置插件
内置插件
内置插件是指可以“随着主程序发版”而下发的插件,通常这个插件会放到主程序的Assets目录下。针对内置插件而言,开发者可无需调用安装方法,由RePlugin来“按需安装”。
添加内置插件
1.将apk包改名为:[插件名].jar
2.放入主程序的assets/plugins目录
外置插件
外置插件是指可通过“下载”、“放入SD卡”等方式获取的apk来安装并运行的插件。
安装和启动插件
启动一个插件首先我们判断插件是否安装,如果插件已经安装,则打开插件并检查插件版本更新,如果插件没有安装,则下载插件并安装插件。看我们下面的插件管理类:
public class MMCPlugin {
private static MMCPlugin instance = null;
private MMCPlugin() {
}
public static MMCPlugin getInstance() {
if (instance == null) {
instance = new MMCPlugin();
}
return instance;
}
/**
* 打开插件
*
* @param context
* @param pluginName
* @param activityName
* @param installListener
*/
public void openPlugin(Context context, String pluginName, String activityName, InstallListener installListener) {
this.installListener = installListener;
if (RePlugin.isPluginInstalled(pluginName)) {//判断是否已经安装,安装了的话,就打开Activity,并且检查插件版本,需要更新的话就下载插件
RePlugin.startActivity(context, RePlugin.createIntent(pluginName, activityName));
if (installListener != null) {
installListener.onSuccess();
}
PluginInfo info = RePlugin.getPluginInfo(pluginName);
if (info.getVersion() < 2) {//版本号由你们接口获得,然后进行对比,插件版本低于接口的版本就下载更新
downPlugin(context, "http://插件地址", pluginName, activityName, true);
}
} else {
// downPlugin(context, "http://插件地址", pluginName, activityName, false);
//本例子我们不进行下载直接进行安装插件
installPlugin(context,pluginName,activityName,false);
}
}
/**
* 安装插件
*
* @param context
* @param pluginName
* @param activityName
*/
public void installPlugin(final Context context, final String pluginName, final String activityName, boolean isUpdate) {
final PluginInfo info = RePlugin.install(Environment.getExternalStorageDirectory() + "/" + pluginName + ".apk");
if (info != null) {
if (isUpdate) {//判断,是否为更新,如果是更新就预加载,下次打开就是最新的插件,不是更新就开始安装
RePlugin.preload(info);
} else {
new Thread(new Runnable() {
@Override
public void run() {
RePlugin.startActivity(context, RePlugin.createIntent(info.getName(), activityName));
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
if (installListener != null) {
installListener.onSuccess();
}
}
});
}
}).start();
}
} else {
if (installListener != null) {
installListener.onFail("安装失败");
}
}
}
/**
* 下载插件
*
* @param context
* @param fileUrl
* @param pluginName
* @param activityName
* @param isUpdate 是否是更新
*/
public void downPlugin(final Context context, String fileUrl, final String pluginName, final String activityName, final boolean isUpdate) { //获取文件存储权限
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} //下载插件,里面的下载方法可以换成你们自己的,例如okhttp,xutils3等等下载都行,然后在回调中处理那几个方法就行
OkGo.get(fileUrl).tag(context).execute(new FileCallback(Environment.getExternalStorageDirectory().getPath(), pluginName + ".apk") {
@Override
public void onSuccess(Response response) {
installPlugin(context, pluginName, activityName, isUpdate);
}
@Override
public void downloadProgress(Progress progress) {
super.downloadProgress(progress);
if (installListener != null) {
installListener.onInstalling((int) (progress.fraction * 100));
}
}
@Override
public void onError(Response response) {
super.onError(response);
if (installListener != null) {
installListener.onFail("下载失败");
}
}
});
}
/**
* 打开插件的Activity
*
* @param context
* @param pluginName
* @param activityName
*/
public void openActivity(Context context, String pluginName, String activityName) {
RePlugin.startActivity(context, RePlugin.createIntent(pluginName, activityName));
}
/**
* 打开插件的Activity 可带参数传递
*
* @param context
* @param intent
* @param pluginName
* @param activityName
*/
public void openActivity(Context context, Intent intent, String pluginName, String activityName) {
intent.setComponent(new ComponentName(pluginName, activityName));
RePlugin.startActivity(context, intent);
}
/**
* 打开插件的Activity 带回调
*
* @param activity
* @param intent
* @param pluginName
* @param activityName
* @param requestCode
*/
public void openActivityForResult(Activity activity, Intent intent, String pluginName, String activityName, int requestCode) {
intent.setComponent(new ComponentName(pluginName, activityName));
RePlugin.startActivityForResult(activity, intent, requestCode, null);
}
private InstallListener installListener;
public interface InstallListener {
void onInstalling(int progress);
void onFail(String msg);
void onSuccess();
}
}
在我们主程序需要打开插件的地方去调用openPlugin方法:
tv_open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv_open.setClickable(false);
MMCPlugin.getInstance().openPlugin(MainActivity.this, "plugintest", "com.linkbasic.plugintest.MainActivity", new MMCPlugin.InstallListener() {
@Override
public void onInstalling(int progress) {
tv_open.setText("正在下载插件:" + progress + "%");
if (progress == 100) {
tv_open.setText("插件下载完成,正在安装...");
}
}
@Override
public void onFail(String msg) {
tv_open.setClickable(true);
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess() {
tv_open.setClickable(true);
tv_open.setText("打开插件");
}
});
}
});
从上面我们知道,插件的安装和升级都是调用:
PluginInfo info = RePlugin.install(Environment.getExternalStorageDirectory() + "/" + pluginName + ".apk");
插件的启用调用:
RePlugin.startActivity(context, RePlugin.createIntent(info.getName(), activityName));
其中info.getName()使我们插件的名称,activityName使我们插件中需要跳入的activity路径。
这样我们就实现了插件的安装(更新)和启动。
五.总结
以上就是关于Android插件化和RePlugin的相关知识点,如有不足或者错误的地方请在下方指正。在技术这块,我们需要多看更需要多写,我们只有不断学习,不断进步才能不被淘汰。