一、插件下载
插件的下载分为两种情况,一是当插件不存在时,需要插件的下载;二是当插件需要更新时,需要下载最新版本的插件。下面针对这两种情况分别介绍:
1、插件不存在时
1.1 下载时机
用户主动点击触发插件的功能而插件不存在时,触发插件的下载。
注意:这里一定是用户主动触发插件的下载,不能是代码的被动触发。具体原因下面再详细分析。
官方给我们提供了一个回调方法,当要打开的Activity所对应的插件不存在时会回调onPluginNotExistsForActivity
方法,通常在这里触发“下载”插件的逻辑。我们先来看一下这个方法的原型:
public boolean onPluginNotExistsForActivity(Context context, String plugin, Intent intent, int process);
第一个参数表示上下文的Context对象,第二个参数表示插件名,第三个参数表示要打开的Activity的Intent对象,第四个参数表示要打开的Activity所在的进程。
目前项目中使用路由机制封装了将要执行的请求操作数据(包括插件的请求),所以onPluginNotExistsForActivity
方法是不能被执行的,所以我们可以在插件不存在时通过手动调用onPluginNotExistsForActivity
方法。
在插件下载并加载成功后需要执行之前的请求操作。因此这里存在一个问题:
如何将MuActionRequest对象传递到onPluginNotExistsForActivity方法中
上面我们分析了onPluginNotExistsForActivity
方法的参数,其中有一个Intent对象,可以利用Intent对象传递MuActionRequest对象(因为MuActionRequest实现了Parcelable接口)。
MuActionRequest类的requestObject对象是一个自定义对象,也需要实现Parcelable接口。
下面的代码展示了如何触发插件不存在时的下载:
//插件不存在时调用
Intent intent = new Intent();
//muActionRequest是MuActionRequest对象
intent.putExtra(“MUACTION_REQUEST”, muActionRequest);
//手动触发onPluginNotExistsForActivity方法
RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(
context, muActionRequest.getModule(), intent, IPluginManager.PROCESS_AUTO);
//触发回调方法,在这里进行插件的下载
@Override
public boolean onPluginNotExistsForActivity(Context context, String plugin, Intent intent, int process) {
//todo 这里做插件的下载操作
...
if(download) { //download表示下载完毕,这里是伪代码
... //这里做插件的安装操作,Replugin.install
if(installSuccess) { //插件安装成功,这里是伪代码
//获取Intent传递过来的MuActionRequest对象
MuActionRequest muActionRequest = intent.getParcelableExtra("MUACTION_REQUEST");
//继续触发用户想要进行的操作
LocalModuleManager.getInstance().route(context, muActionRequest);
}
}
return super.onPluginNotExistsForActivity(context, plugin, intent, process);
}
对于外置插件,生成插件时,需要在manifest中增加
<meta-data android:name="com.qihoo360.plugin.name" android:value="settingabout" />
作为插件别名,否则插件使用的自己的包名作为插件名。
1.2 下载过程
当插件不存在而触发插件的第一次下载时,不建议使用静默下载(即在后台进行下载,用户无感知),而建议使用同步下载(弹出对话框进行提示用户)。
根据用户所处的网络环境分为两种情况:
-
移动网络环境下,提示插件大小(插件信息的获取在App启动或者推送时),如下图所示:
当用户点击了“下载”按钮时,触发插件的下载,弹出下载进度框,如下图所示:
下载完成后需要对压缩包进行解压缩,签名校验等操作,这些步骤这里先跳过,后面再详细说这一块。 -
WIFI环境下,直接弹出插件的下载进度框,如下图所示:
可以看到WIFI环境相比于移动网络环境少了插件大小提示这一步,其他流程都是相同的,在下载完成后也需要对压缩包进行解压缩,签名校验等操作。
下图展示了插件不存在时的下载流程:
2、插件更新时
2.1 下载时机
在App启动时检测插件是否需要更新,下载需要更新的插件;或者在收到后台更新推送时,下载需要更新的插件。
- App启动时,由于WelcomeActivity是第一个启动的Activity,我们在WelcomeActivity的
onCreate()
方法中检测插件的更新情况,若有插件需要更新则下载并更新插件。例如在onCreate()
方法中调用如下代码:
private void checkPluginUpdate() {
//获取当前app的插件列表
List<PluginInfo> pluginInfoList = RePlugin.getPluginInfoList();
List<PluginBean> curPluginBeanList = new ArrayList<>();
//将当前的插件列表转换成List<PluginBean>
for (PluginInfo pluginInfo : pluginInfoList) {
PluginBean pluginBean = new PluginBean();
pluginBean.versionCode = pluginInfo.getVersion();
curPluginBeanList.add(pluginBean);
}
//checkPluginUpdate方法的作用是检测是否有插件更新,有则下载并更新
PluginManager.getInstance().checkPluginUpdate(this, curPluginBeanList, new PluginManager.onUpdateResultListener());
}
- 在收到后台更新推送时,已经知道了需要更新的插件的信息,这时我们仅需要去下载并更新插件即可。例如我们可以调用如下代码:
PluginManager.getInstance().downloadPluginList(context, pluginBeanList);
其中pluginBeanList为需要更新的插件列表。
2.2 下载过程
插件更新时默认使用静默下载,只有在强制更新时才使用同步更新(弹出更新下载对话框)。我们还是根据App启动和更新推送这两种方案分别进行说明:
-
App启动时,先去检测是否有需要更新的插件。插件的更新是根据插件的版本号来确定的,当现有插件的版本号小于获取到的插件版本号时则进行插件的更新。
然后去检测需要更新的插件是否是强制升级的插件(强制升级代表是紧急修复的插件),对于需要强制升级的插件则弹出下载对话框,用户点击下载则弹出下载进度的对话框进行下载;对于不需要强制升级的插件则使用静默下载的方式在后台进行下载。
App启动时检测更新下载的流程图如下所示:
-
更新推送时,由于不确定用户当前所处的界面,因此收到更新推送只能使用静默下载。相比于App启动的检测更新并下载,这里只需要需要做下载的那一步,而且下载方式是静默下载。
更新推送下载的流程图如下图所示:
二、插件加载
对于插件加载来说,插件不存在与插件更新的加载逻辑基本上是一样的,所以这里合为一处说明。
1、插件不存在与插件更新
1.1 加载时机
不管插件不存在还是插件的更新,都需要插件的下载流程,而在插件下载完成后即可触发插件的加载流程。
1.2 加载过程
插件不存在时
由于插件不存在而触发插件下载操作,在插件下载完成后,通过Replugin.install(filePath)
方法去加载插件,其中filePath为插件后的存储地址。-
插件更新时
对于插件的更新分为普通插件的更新和紧急插件(一般为修复crash的插件)的更新。所以这里分为两种情况:普通插件的更新,在插件下载完成后,直接调用
Replugin.install(filePath)
方法加载插件,此时不管插件是否正在运行,插件只会在下次重启后生效。紧急插件的更新,如果在App启动时下载,由于下载完毕后需要重启App,所以在下载之前就要弹框提示用户下载完毕后重启App。这里是一个优化点,是否有办法可以不用重启而进行插件的更新。
插件的更新在调用Replugin.install(filePath)方法后不会立即生效,待App重启后插件才会生效。不管是内部插件还是外部插件都遵循此逻辑。
1.3 插件的版本
对于插件的更新,Replugin是不允许插件降级的,所以需要设置插件的版本号。对于版本号的设置,需要在manifest中增加如下代码:
<meta-data
android:name="com.qihoo360.plugin.version.ver"
android:value="101"/>
官方建议版本号使用“三位数”组成。其中,第一位是大版本,第二位是功能版本,第三位是修复版本。当然我们也可以根据业务来定,五位数、六位数都可以。
三、容错机制
既然有插件的下载与加载,那么也要考虑到插件下载、加载失败后的情况。
1、下载、加载失败
1.1 插件下载失败
由于我们使用了离线包组件,其中提供了下载离线包失败的回调方法,我们可以在失败的方法中进行埋点。代码如下:
updateResultUIDelegate.setTaskUiCallBack(new TaskUiCallBack() {
@Override
public void onSuccess() {
}
@Override
public void onFailure() {
//todo 下载失败,可以在这里进行埋点
}
@Override
public void onProgress(int value) {
}
});
1.2 插件安装失败
插件下载成功后下一步是使用Replugin.install
方法进行安装。对于安装失败的情况,官方有给出的一些原因:
VERIFY_SIGN_FAIL(签名校验失败),VERIFY_VER_FAIL(插件版本太老,不支持版本降级),
- 是否开启了“签名校验”功能且签名不在“白名单”之中?——通常在Logcat中会出现“verifySignature: invalid cert: ”。如是,则请参考“安全与签名校验”一节,了解如何将签名加白,或关闭签名校验功能(默认为关闭),对应InstallResult值为VERIFY_SIGN_FAIL。
- 插件版本太旧了,不支持版本降级——通常在Logcat中会出现“checkVersion: Older than*** ”,这表明当前的插件版本太旧了,由于Replugin不支持版本的降级,因此我们需要用一个新版本来进行安装,对应InstallResult值为VERIFY_VER_FAIL。
- APK安装包是否有问题?——请将“插件APK”直接安装到设备上(而非作为插件)试试。如果在设备中安装失败,则插件安装也一定是失败的,对应InstallResult值为READ_PKG_INFO_FAIL。
- 设备内部存储空间是否不足?——通常出现此问题时其Logcat会出现“copyOrMoveApk: Copy/Move Failed”的警告。如是,则需要告知用户去清理手机,对应InstallResult值为COPY_APK_FAIL。
我们可以通过重写RePluginEventCallbacks的onInstallPluginFailed方法获取上述的InstallResult值,并针对不同的值进行埋点。
@Override
public void onInstallPluginFailed(String path, InstallResult code) {
//todo 安装失败的回调方法,根据code的值进行埋点
super.onInstallPluginFailed(path, code);
}
总结:在下载、安装失败后可以在回调方法中针对每一种情况使用Toast弹出原因展示给用户,并且可以弹出一个对话框进去吐槽反馈页面对该情况进行反馈。同时在下载、安装失败的地方使用目前的埋点方式进行埋点,方便后续分析解决。
四、插件卸载、安全与签名校验
1、插件卸载
卸载插件,只需要使用Replugin.uninstall(pluginName)
方法即可,而参数只需传递一个“插件名"。
这里有两点需要注意的:
- 如果插件正在运行,则不会立即卸载插件,而是将卸载诉求记录下来。直到所有“正在使用插件”的进程结束并重启后才会生效。
- 由于内置插件是捆在主程序包内的,故无法卸载“内置插件”。
2、安全与签名校验
若要对插件进行签名校验,仅需要两步即可:
-
打开开关
创建RepluginConfig对象并调用其setVerifySign(true) 方法。参数true 表示打开校验开关。推荐将!BuildConfig.DEBUG参数传递过去,这表示:在Debug环境下无需校验签名,只有Release才会校验。
RePluginConfig c = new RePluginConfig();
c.setVerifySign(!BuildConfig.DEBUG);
Note:在Replugin 2.1.4版本开始,默认将”关闭“签名校验,之前默认为”开启“。
-
加入合法签名
在打开开关后,我们还需要将这个合法的签名加入到Replugin的”白名单“中,可调用Replugin.addCertSignature()来完成。例如:
// Add signature to "White List"
RePlugin.addCertSignature("379C790B7B726B51AC58E8FCBCFEB586");
这里的参数是签名证书的MD5值。addCertSignature这个方法说明一旦签名证书的MD5值被添加进来,则通过该签名证书的插件将被允许被执行,否则不能被执行。
总结:官方建议开启安全和签名校验。但是如果在调用Install方法前就已经对APK包做了校验,则可关闭,以避免重复校验。由于我们项目中在下载APK包的时候会对该包进行MD5校验,所以这里可以选择关闭签名校验。如果要使用签名校验,则不要使用宿主的签名,应该为插件单独使用一个签名。