Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin uninstall functionality optimized for synchronizing multiple p… #199

Merged
merged 4 commits into from
Jul 13, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ public class Constant {
*/
public static final String LOCAL_PLUGIN_APK_LIB_DIR = "p_n";

/**
* "纯APK"插件同版本升级时插件、Odex、Native(SO库)的用于覆盖的存放目录
* Added by Zhiwei Liu
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议去掉Added by 字样,因为开源后的PR可以体现改动点。

*/
public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c";

/**
* 插件文件名,name-low-high-current.jar
* 插件文件名规范:barcode-1-10-2.jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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");
}

// 通过别名和包名来最终确认插件名
Expand Down Expand Up @@ -347,6 +364,7 @@ public boolean isDexExtracted() {
/**
* 获取APK存放的文件信息 <p>
* 若为"纯APK"插件,则会位于app_p_a中;若为"p-n"插件,则会位于"app_plugins_v3"中 <p>
* 注意:若支持同版本覆盖安装的话,则会位于app_p_c中; <p>
*
* @return Apk所在的File对象
*/
Expand All @@ -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);
}
Expand All @@ -365,6 +385,7 @@ public File getApkFile() {
/**
* 获取Dex(优化后)生成时所在的目录 <p>
* 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中 <p>
* 若支持同版本覆盖安装的话,则会位于app_p_c中; <p>
* 注意:仅供框架内部使用
*
* @return 优化后Dex所在目录的File对象
Expand All @@ -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);
}
Expand All @@ -394,6 +417,7 @@ public File getDexFile() {
/**
* 根据类型来获取SO释放的路径 <p>
* 若为"纯APK"插件,则会位于app_p_n中;若为"p-n"插件,则会位于"app_plugins_v3_libs"中 <p>
* 若支持同版本覆盖安装的话,则会位于app_p_c中; <p>
* 注意:仅供框架内部使用
*
* @return SO释放路径所在的File对象
Expand All @@ -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);
}
Expand Down Expand Up @@ -491,6 +517,62 @@ public void setPendingDelete(PluginInfo info) {
}
}

/**
* 是否已准备好了新待覆盖的版本?
*
* @return 是否已准备好
*/
public boolean isNeedCover() {
return mPendingCover != null;
}

/**
* 获取将来要覆盖更新的插件的信息,将会在下次启动时才能被使用
*
* @return 插件覆盖安装信息
*/
public PluginInfo getPendingCover() {
return mPendingCover;
}

/**
* 设置插件的覆盖更新信息。此信息有可能等到下次才能被使用 <p>
* 注意:若为“纯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方法才能看到 <p>
* 注意:仅框架内部使用
*
* @return 是否包含同版本覆盖字段
*/
public boolean getIsPendingCover() {
return mIsPendingCover;
}

/**
* 设置PluginInfo的同版本覆盖的字段 <p>
* 注意:仅框架内部使用
*/
public void setIsPendingCover(boolean coverInfo) {
mIsPendingCover = coverInfo;
if (mIsPendingCover) {
JSONHelper.putNoThrows(mJson, "cover", mIsPendingCover);
} else {
mJson.remove("cover");
}
}

/**
* 获取最小支持宿主API的版本
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public void remove(String pn) {
JSONHelper.remove(mJson, i);
}
}
if (mMap.containsKey(pn)) {
mMap.remove(pn);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是也应该清掉mList?

}

public PluginInfo get(String pn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -200,7 +201,19 @@ private boolean verifySignature(PackageInfo pi, String path) {
}

private boolean checkVersion(PluginInfo instPli, PluginInfo curPli) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议改下返回值为int,-1为instPli小于curPli,1为instPli大于curPli,0为两者相等,这样就不必再checkVersion中做set操作了

if (instPli.getVersion() <= curPli.getVersion()) {
// 支持插件同版本覆盖安装?
// 若现在要安装的,与之前的版本相同,则覆盖掉之前的版本;
if (instPli.getVersion() == curPli.getVersion()) {
if (LogDebug.LOG) {
LogDebug.d(TAG, "isSameVersion: same version. " +
"inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion());
}
instPli.setIsPendingCover(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议改下返回值为int,-1为instPli小于curPli,1为instPli大于curPli,0为两者相等,这样就不必再checkVersion中做set操作了

return true;
}

// 若现在要安装的,比之前的版本还要旧,则忽略更新;
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());
Expand All @@ -209,9 +222,9 @@ private boolean checkVersion(PluginInfo instPli, PluginInfo curPli) {
}

// 已有“待更新版本”?
// 若现在要安装的,比“待更新版本”还要旧(或相同),则也可以忽略
// 若现在要安装的,比“待更新版本”还要旧,则也可以忽略
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());
Expand Down Expand Up @@ -272,8 +285,14 @@ 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果已经要做升级了,应该需要做一次setPendingCover(null)和setPendingDelete(null),之后的操作覆盖之前的。并打个Log出来

} else if (instPli.getVersion() == curPli.getVersion()){
// 同版本覆盖
curPli.setPendingCover(instPli);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同理,应该setPendingDelete(null),但无需setPendingUpdate,因为在updatePendingUpdate时就已经return了(建议加个注释说明一下)

}
} else {
if (LogDebug.LOG) {
LogDebug.i(TAG, "updateOrLater: Not running. Update now! pn=" + curPli.getName());
Expand Down Expand Up @@ -340,16 +359,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) {
Expand All @@ -360,17 +379,46 @@ 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) {
LogDebug.i(TAG, "updateNow: Update. pn=" + curInfo.getVersion() +
"; 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 {
FileUtils.forceDelete(newPi.getApkFile().getParentFile());
Copy link
Contributor

@jiongxuan jiongxuan Jul 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议这么写,这样减少歧义:

File parentDir = newPi.getApkFile().getParentFile();
FileUtils.forceDelete(parentDir);

} catch (IOException e) {
if (LogRelease.LOGR) {
e.printStackTrace();
}
}
}
}

private void delete(@NonNull PluginInfo pi) {
Expand Down Expand Up @@ -436,14 +484,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会为空,则此情况仅在UI线程进行更新
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这块儿的注释还是不够清晰。建议改为:

“注意:若在attachBaseContext中调用此方法,则由于此时getApplicationContext为空,导致发送广播时会出现空指针异常。则应该Post一下,待getApplicationContext有值后再发送广播”

if (RePluginInternal.getAppContext().getApplicationContext() != null) {
IPC.sendLocalBroadcast2AllSync(RePluginInternal.getAppContext(), intent);
} else {
Tasks.init();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RePlugin.onCreate内部也调用了Tasks.init,不如挪到attachBaseContext中靠前位置,这样这里就不用调用了。

Tasks.post2UI(new Runnable() {
@Override
public void run() {
IPC.sendLocalBroadcast2All(RePluginInternal.getAppContext(), intent);
}
});
}
return true;
}

Expand Down