在项目中,遇到一个问题。应用本身包含一个内置插件A.jar,打开app,从未使用过A插件,此时通过RePlugin.install方法,安装A插件的高版本。安装回调成功后,插件实际未更新。重启app,通过RePlugin.getPluginInfoList获取到两个A插件,一个低版本jar,一个高版本jar(一脸懵逼)。
场景分析:
失败场景:
1、打开app,插件一次未拉起
2、尝试install高版本插件,广播返回安装成功,实际拉起插件,还是旧版本插件
3、重启app,拉起插件,依然是低版本插件。
4、RePlugin.getPluginInfoList,返回两个一样的插件信息,一个高版本,一个低版本
成功场景:
1、打开app,拉起插件,然后关闭
2、重新打开app,尝试install高版本插件,拉起插件,成功了(TAT)
通过debug源码,找到代码轨迹:RePlugin.install--->MP.pluginDownloaded(path)----->PluginProcessMain.getPluginHost().pluginDownloaded(path)------->PluginManagerServer.getService().install(path)------>PluginManagerServer.this.installLocked(path)------->PluginManagerServer.updateOrLater(PluginInfo curPli, PluginInfo instPli)等等
public static final PluginInfo pluginDownloaded(String path) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... path=" + path);
}ProcessLocker lock = null;
try {
if (path != null) {
File f = new File(path);
String fileName = f.getName();
String fileDir = f.getParent();
if (fileName.startsWith("p-n-")) {
lock = new ProcessLocker(RePluginInternal.getAppContext(), fileDir, fileName + ".lock");
}
}if (lock != null && !lock.tryLock()) {
// 加锁
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... lock file + " + path + " failed! ");
}
}PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path);
if (info != null) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginSucceed(info);
}
return info;
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.pded: " + e.getMessage(), e);
}
} finally {
// 去锁
if (lock != null) {
lock.unlock();
}
}
return null;
}
public PluginInfo pluginDownloaded(String path) throws RemoteException {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "pluginDownloaded: path=" + path);
}// 通过路径来判断是采用新方案,还是旧的P-N(即将废弃,有多种)方案
PluginInfo pi;
String fn = new File(path).getName();
if (fn.startsWith("p-n-") || fn.startsWith("v-plugin-") || fn.startsWith("plugin-s-") || fn.startsWith("p-m-")) {
pi = pluginDownloadedForPn(path);
} else {
pi = mManager.getService().install(path);
}if (pi != null) {
// 通常到这里,表示“安装已成功”,这时不管处于什么状态,都应该通知外界更新插件内存表
syncInstalledPluginInfo2All(pi);}
return pi;
}
private PluginInfo installLocked(String path) {
final boolean verifySignEnable = RePlugin.getConfig().getVerifySign();
final int flags = verifySignEnable ? PackageManager.GET_META_DATA | PackageManager.GET_SIGNATURES : PackageManager.GET_META_DATA;// 1. 读取APK内容
PackageInfo pi = mContext.getPackageManager().getPackageArchiveInfo(path, flags);
if (pi == null) {
if (LogDebug.LOG) {
LogDebug.e(TAG, "installLocked: Not a valid apk. path=" + path);
}RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.READ_PKG_INFO_FAIL);
return null;
}// 2. 校验插件签名
if (verifySignEnable) {
if (!verifySignature(pi, path)) {
return null;
}
}// 3. 解析出名字和三元组
PluginInfo instPli = PluginInfo.parseFromPackageInfo(pi, path);
if (LogDebug.LOG) {
LogDebug.i(TAG, "installLocked: Info=" + instPli);
}
instPli.setType(PluginInfo.TYPE_NOT_INSTALL);// 若要安装的插件版本小于或等于当前版本,则安装失败
// NOTE 绝大多数情况下,应该在调用RePlugin.install方法前,根据云端回传的信息来判断,以防止下载旧插件,浪费流量
// NOTE 这里仅做双保险,或通过特殊渠道安装时会有用// 注意:这里必须用“非Clone过的”PluginInfo,因为要修改里面的内容
PluginInfo curPli = MP.getPlugin(instPli.getName(), false);
if (curPli != null) {
if (LogDebug.LOG) {
LogDebug.i(TAG, "installLocked: Has installed plugin. current=" + curPli);
}// 版本较老?直接返回
final int checkResult = checkVersion(instPli, curPli);
if (checkResult < 0) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.VERIFY_VER_FAIL);
return null;
} else if (checkResult == 0) {
instPli.setIsPendingCover(true);
}
}// 4. 将合法的APK改名后,移动(或复制,见RePluginConfig.isMoveFileWhenInstalling)到新位置
// 注意:不能和p-n的最终释放位置相同,因为管理方式不一样
if (!copyOrMoveApk(path, instPli)) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.COPY_APK_FAIL);
return null;
}// 5. 从插件中释放 So 文件
PluginNativeLibsHelper.install(instPli.getPath(), instPli.getNativeLibsDir());// 6. 若已经安装旧版本插件,则尝试更新插件信息,否则直接加入到列表中
if (curPli != null) {
updateOrLater(curPli, instPli);
} else {
mList.add(instPli);
}// 7. 保存插件信息到文件中,下次可直接使用
mList.save(mContext);return instPli;
}
private void updateOrLater(PluginInfo curPli, PluginInfo instPli) {
if (LogDebug.LOG) {
LogDebug.d(TAG, "updateOrLater: Need update. pn=" + curPli.getName() +
"; cur_ver=" + curPli.getVersion() + "; update_ver=" + instPli.getVersion());
}
// 既然要更新到新的"纯APK"方案,自然需要把旧p-n的信息迁移到新列表中
// FIXME 看看有没有别的兼容问题,尤其是和两个List表之间的
if (curPli.isPnPlugin()) {
mList.add(curPli);
}// 已有“待更新版本”?
PluginInfo curUpdatePli = curPli.getPendingUpdate();
if (curUpdatePli != null) {
updatePendingUpdate(curPli, instPli, curUpdatePli);// 由于"打算要更新"的前提是插件正在被运行,且下次重启时会清空这个信息,既然这次只是替换"打算要更新"的插件信息
// 则不必再做后面诸如"插件是否存在"等判断,直接返回即可
return;
}// 正在运行?Later到下次使用时再释放。否则直接开始更新
//场景一出现的问题就在这里出现分歧
if (RePlugin.isPluginRunning(curPli.getName())) {
if (LogDebug.LOG) {
LogDebug.w(TAG, "updateOrLater: Plugin is running. Later. pn=" + curPli.getName());
}
if (instPli.getVersion() > curPli.getVersion() ||
instPli.getVersion() == curPli.getVersion() && getPluginType(instPli) != getPluginType(curPli)) {
// 高版本升级
curPli.setPendingUpdate(instPli);
curPli.setPendingDelete(null);
curPli.setPendingCover(null);
if (LogDebug.LOG) {
LogDebug.w(TAG, "updateOrLater: Plugin need update high version. clear PendingDelete and PendingCover.");
}
} else if (instPli.getVersion() == curPli.getVersion()) {
// 同版本覆盖
curPli.setPendingCover(instPli);
curPli.setPendingDelete(null);
// 注意:并不需要对PendingUpdate信息做处理,因为此前的updatePendingUpdate方法时就已经返回了
if (LogDebug.LOG) {
LogDebug.w(TAG, "updateOrLater: Plugin need update same version. clear PendingDelete.");
}
}// 设置其Parent为curPli,在PmBase.newPluginFound时会用到
instPli.setParentInfo(curPli);
} else {
if (LogDebug.LOG) {
LogDebug.i(TAG, "updateOrLater: Not running. Update now! pn=" + curPli.getName());
}
updateNow(curPli, instPli);//场景二走这边,直接进行了插件升级
}
}
private void updateNow(PluginInfo curInfo, PluginInfo newInfo) {
final boolean covered = newInfo.getIsPendingCover();
if (covered) {
move(curInfo, newInfo);
} else {
// 删除旧版本插件,不管是不是p-n的,且清掉Dex和Native目录
delete(curInfo);
}newInfo.setType(PluginInfo.TYPE_EXTRACTED);
if (LogDebug.LOG) {
LogDebug.i(TAG, "updateNow: Update. pn=" + curInfo.getVersion() +
"; cur_ver=" + curInfo.getVersion() + "; update_ver=" + newInfo.getVersion());
}if (covered) {
curInfo.setPendingCover(null);
newInfo.setIsPendingCover(false);
//修改isPendingCover属性后必须同时修改json中的path路径
newInfo.setPath(newInfo.getApkFile().getPath());
} else {
curInfo.update(newInfo);
curInfo.setPendingUpdate(null);
}
}
通过查看源码,在这一步出现分歧,在PluginManagerServer.updateOrLater(PluginInfo curPli, PluginInfo instPli)方法中,RePlugin.isPluginRunning(curPli.getName())判断不同,该方法判断即将更新的低版本插件,是否在运行,若在运行下次使用时再释放。否则直接开始更新。那么问题来了,插件使用一次后,再次打开app插件为何未运行?以及在运行时,无法覆盖更新的逻辑是否存在问题?
1、先看第一个问题,打开APP,为拉起插件,插件如何处于running状态?
通过下图看出,因为内置插件做了preload操作,replugin把插件加入到了running状态。
代码中去除preload后,安装正常...
还有一些问题,有待继续研究...