diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java index b86e5dad..c165717d 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java @@ -72,6 +72,11 @@ public class Constant { */ public static final String LOCAL_PLUGIN_APK_LIB_DIR = "p_n"; + /** + * "纯APK"插件同版本升级时插件、Odex、Native(SO库)的用于覆盖的存放目录 + */ + public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c"; + /** * 插件文件名,name-low-high-current.jar * 插件文件名规范:barcode-1-10-2.jar diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java index 676be69c..0bbfc0e3 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java @@ -272,7 +272,7 @@ public static final boolean pluginUninstall(String pluginName) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.pluginUninstall ... pluginName=" + pluginName); } - PluginInfo pi = getPlugin(pluginName, false); + PluginInfo pi = getPlugin(pluginName, true); // 插件未安装 if (pi == null) { diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java index e06f3ea9..ab5118a4 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java @@ -20,6 +20,7 @@ import android.text.TextUtils; import com.qihoo360.i.IPluginManager; +import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.helper.LogDebug; @@ -84,6 +85,8 @@ static final int getPluginProcessIndex() { */ @Deprecated static final void init(Context context) { + // 初始化操作,方便后面执行任务,不必担心Handler为空的情况 + Tasks.init(); // sUid = android.os.Process.myUid(); diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java index a8f0cf30..aaf422f5 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java @@ -1121,6 +1121,11 @@ final void pluginUninstalled(PluginInfo info) { LogDebug.d(PLUGIN_TAG, "Clear plugin cache. pn=" + info.getName()); } + // 移除卸载插件的HashMap缓存 + if (mPlugins.containsKey(info.getName())) { + mPlugins.remove(info.getName()); + } + // 移除卸载插件表快照 PluginTable.removeInfo(info); diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java index 44d4acb7..a665873f 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java @@ -108,6 +108,10 @@ public class PluginInfo implements Parcelable, Cloneable { // 若插件需要卸载,则会有此值 private PluginInfo mPendingDelete; + // 若插件需要同版本覆盖安装更新,则会有此值 + private PluginInfo mPendingCover; + private boolean mIsPendingCover; + private PluginInfo(JSONObject jo) { initPluginInfo(jo); } @@ -155,6 +159,10 @@ public PluginInfo(PluginInfo pi) { if (pi.mPendingDelete != null) { this.mPendingDelete = new PluginInfo(pi.mPendingDelete); } + this.mIsPendingCover = pi.mIsPendingCover; + if (pi.mPendingCover != null) { + this.mPendingCover = new PluginInfo(pi.mPendingCover); + } } private void initPluginInfo(JSONObject jo) { @@ -171,6 +179,15 @@ private void initPluginInfo(JSONObject jo) { if (djo != null) { mPendingDelete = new PluginInfo(djo); } + + // 缓存"待覆盖安装"的插件信息 + JSONObject cjo = jo.optJSONObject("coverinfo"); + if (cjo != null) { + mPendingCover = new PluginInfo(cjo); + } + + // 缓存"待覆盖安装"的插件覆盖字段 + mIsPendingCover = jo.optBoolean("cover"); } // 通过别名和包名来最终确认插件名 @@ -347,6 +364,7 @@ public boolean isDexExtracted() { /** * 获取APK存放的文件信息

* 若为"纯APK"插件,则会位于app_p_a中;若为"p-n"插件,则会位于"app_plugins_v3"中

+ * 注意:若支持同版本覆盖安装的话,则会位于app_p_c中;

* * @return Apk所在的File对象 */ @@ -356,6 +374,8 @@ public File getApkFile() { File dir; if (isPnPlugin()) { dir = context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0); + } else if (getIsPendingCover()) { + dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_SUB_DIR, 0); } @@ -365,6 +385,7 @@ public File getApkFile() { /** * 获取Dex(优化后)生成时所在的目录

* 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中

+ * 若支持同版本覆盖安装的话,则会位于app_p_c中;

* 注意:仅供框架内部使用 * * @return 优化后Dex所在目录的File对象 @@ -374,6 +395,8 @@ public File getDexParentDir() { Context context = RePluginInternal.getAppContext(); if (isPnPlugin()) { return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0); + } else if (getIsPendingCover()) { + return context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { return context.getDir(Constant.LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0); } @@ -394,6 +417,7 @@ public File getDexFile() { /** * 根据类型来获取SO释放的路径

* 若为"纯APK"插件,则会位于app_p_n中;若为"p-n"插件,则会位于"app_plugins_v3_libs"中

+ * 若支持同版本覆盖安装的话,则会位于app_p_c中;

* 注意:仅供框架内部使用 * * @return SO释放路径所在的File对象 @@ -404,6 +428,8 @@ public File getNativeLibsDir() { File dir; if (isPnPlugin()) { dir = context.getDir(Constant.LOCAL_PLUGIN_DATA_LIB_DIR, 0); + } else if (getIsPendingCover()) { + dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_LIB_DIR, 0); } @@ -491,6 +517,62 @@ public void setPendingDelete(PluginInfo info) { } } + /** + * 是否已准备好了新待覆盖的版本? + * + * @return 是否已准备好 + */ + public boolean isNeedCover() { + return mPendingCover != null; + } + + /** + * 获取将来要覆盖更新的插件的信息,将会在下次启动时才能被使用 + * + * @return 插件覆盖安装信息 + */ + public PluginInfo getPendingCover() { + return mPendingCover; + } + + /** + * 设置插件的覆盖更新信息。此信息有可能等到下次才能被使用

+ * 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 + * + * @param info 插件覆盖安装信息 + */ + public void setPendingCover(PluginInfo info) { + mPendingCover = info; + if (info != null) { + JSONHelper.putNoThrows(mJson, "coverinfo", info.getJSON()); + } else { + mJson.remove("coverinfo"); + } + } + + /** + * 此PluginInfo是否包含同版本覆盖的字段?只在调用RePlugin.install方法才能看到

+ * 注意:仅框架内部使用 + * + * @return 是否包含同版本覆盖字段 + */ + public boolean getIsPendingCover() { + return mIsPendingCover; + } + + /** + * 设置PluginInfo的同版本覆盖的字段

+ * 注意:仅框架内部使用 + */ + public void setIsPendingCover(boolean coverInfo) { + mIsPendingCover = coverInfo; + if (mIsPendingCover) { + JSONHelper.putNoThrows(mJson, "cover", mIsPendingCover); + } else { + mJson.remove("cover"); + } + } + /** * 获取最小支持宿主API的版本 */ diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfoList.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfoList.java index 6eb74d0a..e7400f11 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfoList.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfoList.java @@ -74,6 +74,20 @@ public void remove(String pn) { JSONHelper.remove(mJson, i); } } + if (mMap.containsKey(pn)) { + mMap.remove(pn); + } + removeListElement(mList, pn); + } + + private void removeListElement(List list, String pn) { + Iterator iterator = list.iterator(); + while(iterator.hasNext()) { + PluginInfo pluginInfo = iterator.next(); + if(TextUtils.equals(pn, pluginInfo.getName())) { + iterator.remove(); + } + } } public PluginInfo get(String pn) { diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java index 69177374..69395f9c 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java @@ -27,6 +27,7 @@ import com.qihoo360.loader2.CertUtils; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PluginNativeLibsHelper; +import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.mobilesafe.utils.pkg.PackageFilesUtil; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginEventCallbacks; @@ -155,9 +156,12 @@ private PluginInfo installLocked(String path) { } // 版本较老?直接返回 - if (!checkVersion(instPli, 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); } } @@ -199,26 +203,37 @@ private boolean verifySignature(PackageInfo pi, String path) { return true; } - private boolean checkVersion(PluginInfo instPli, PluginInfo curPli) { - if (instPli.getVersion() <= curPli.getVersion()) { + private int checkVersion(PluginInfo instPli, PluginInfo curPli) { + // 支持插件同版本覆盖安装? + // 若现在要安装的,与之前的版本相同,则覆盖掉之前的版本; + if (instPli.getVersion() == curPli.getVersion()) { + if (LogDebug.LOG) { + LogDebug.d(TAG, "isSameVersion: same version. " + + "inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion()); + } + return 0; + } + + // 若现在要安装的,比之前的版本还要旧,则忽略更新; + if (instPli.getVersion() < curPli.getVersion()) { if (LogDebug.LOG) { LogDebug.e(TAG, "checkVersion: Older than current, install fail. pn=" + curPli.getName() + "; inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion()); } - return false; + return -1; } // 已有“待更新版本”? - // 若现在要安装的,比“待更新版本”还要旧(或相同),则也可以忽略 + // 若现在要安装的,比“待更新版本”还要旧,则也可以忽略 PluginInfo curUpdatePli = curPli.getPendingUpdate(); - if (curUpdatePli != null && instPli.getVersion() <= curUpdatePli.getVersion()) { + if (curUpdatePli != null && instPli.getVersion() < curUpdatePli.getVersion()) { if (LogDebug.LOG) { LogDebug.e(TAG, "checkVersion: Older than updating plugin. Ignore. pn=" + curPli.getName() + "; " + "cur_ver=" + curPli.getVersion() + "; old_ver=" + curUpdatePli.getVersion() + "; new_ver=" + instPli.getVersion()); } - return false; + return -1; } - return true; + return 1; } private boolean copyOrMoveApk(String path, PluginInfo instPli) { @@ -272,8 +287,24 @@ private void updateOrLater(PluginInfo curPli, PluginInfo instPli) { if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin is running. Later. pn=" + curPli.getName()); } - instPli.setIsThisPendingUpdateInfo(true); - curPli.setPendingUpdate(instPli); + if (instPli.getVersion() > curPli.getVersion()) { + // 高版本升级 + instPli.setIsThisPendingUpdateInfo(true); + 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."); + } + } } else { if (LogDebug.LOG) { LogDebug.i(TAG, "updateOrLater: Not running. Update now! pn=" + curPli.getName()); @@ -340,16 +371,16 @@ private boolean updateIfNeeded(PluginInfo curInfo) { LogDebug.d(TAG, "updateIfNeeded: delete plugin. pn=" + curInfo.getName()); } - // 移除插件及其已释放的Dex、Native库等文件 - PackageFilesUtil.forceDelete(curInfo.getPendingDelete()); - mList.remove(curInfo.getName()); - return true; + // 移除插件及其已释放的Dex、Native库等文件并向各进程发送广播,同步更新 + return uninstallNow(curInfo.getPendingDelete()); } else if (curInfo.isNeedUpdate()) { // 需要更新插件?那就直接来 updateNow(curInfo, curInfo.getPendingUpdate()); return true; - + } else if (curInfo.isNeedCover()) { + updateNow(curInfo, curInfo.getPendingCover()); + return true; } else { // 既不需要删除也不需要更新 if (LogDebug.LOG) { @@ -360,8 +391,13 @@ private boolean updateIfNeeded(PluginInfo curInfo) { } private void updateNow(PluginInfo curInfo, PluginInfo newInfo) { - // 删除旧版本插件,不管是不是p-n的,且清掉Dex和Native目录 - delete(curInfo); + 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) { @@ -369,8 +405,33 @@ private void updateNow(PluginInfo curInfo, PluginInfo newInfo) { "; cur_ver=" + curInfo.getVersion() + "; update_ver=" + newInfo.getVersion()); } - curInfo.update(newInfo); - curInfo.setPendingUpdate(null); + if (covered) { + curInfo.setPendingCover(null); + } else { + curInfo.update(newInfo); + curInfo.setPendingUpdate(null); + } + } + + private void move(@NonNull PluginInfo curPi, @NonNull PluginInfo newPi) { + try { + FileUtils.copyFile(newPi.getApkFile(), curPi.getApkFile()); + FileUtils.copyFile(newPi.getDexFile(), curPi.getDexFile()); + FileUtils.copyFile(newPi.getNativeLibsDir(), curPi.getNativeLibsDir()); + } catch (IOException e) { + if (LogRelease.LOGR) { + e.printStackTrace(); + } + } finally { + try { + File parentDir = newPi.getApkFile().getParentFile(); + FileUtils.forceDelete(parentDir); + } catch (IOException e) { + if (LogRelease.LOGR) { + e.printStackTrace(); + } + } + } } private void delete(@NonNull PluginInfo pi) { @@ -436,14 +497,25 @@ private boolean uninstallNow(PluginInfo info) { // 1. 移除插件及其已释放的Dex、Native库等文件 PackageFilesUtil.forceDelete(info); - // 2. 给各进程发送广播,同步更新 - Intent intent = new Intent(PluginInfoUpdater.ACTION_UNINSTALL_PLUGIN); - intent.putExtra("obj", info); - IPC.sendLocalBroadcast2AllSync(RePluginInternal.getAppContext(), intent); - - // 3. 保存插件信息到文件中 + // 2. 保存插件信息到文件中 mList.remove(info.getName()); mList.save(mContext); + + // 3. 给各进程发送广播,同步更新 + final Intent intent = new Intent(PluginInfoUpdater.ACTION_UNINSTALL_PLUGIN); + intent.putExtra("obj", info); + // 注意:若在attachBaseContext中调用此方法,则由于此时getApplicationContext为空,导致发送广播时会出现空指针异常。 + // 则应该Post一下,待getApplicationContext有值后再发送广播。 + if (RePluginInternal.getAppContext().getApplicationContext() != null) { + IPC.sendLocalBroadcast2AllSync(RePluginInternal.getAppContext(), intent); + } else { + Tasks.post2UI(new Runnable() { + @Override + public void run() { + IPC.sendLocalBroadcast2All(RePluginInternal.getAppContext(), intent); + } + }); + } return true; }