RePlugin插件的安装流程相对简单点,整个安装其实就是将APK文件中的dex,res, so等移动到一个特定的路径的过程。
无论是插件的安装和启动,RePlugin对外暴露的都是RePlugin.java提供的接口.
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\replugin
/**
* 安装或升级此插件
* 注意:
* 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件
* 2、支持“纯APK”和“p-n”(旧版,即将废弃)插件
* 3、此方法是【同步】的,耗时较少
* 4、不会触发插件“启动”逻辑,因此只要插件“当前没有被使用”,再次调用此方法则新插件立即生效
*
* @param path 插件安装的地址。必须是“绝对路径”。通常可以用context.getFilesDir()来做
* @return 安装成功的插件信息,外界可直接读取
* @since 2.0.0 (1.x版本为installDelayed)
*/
public static PluginInfo install(String path) {
...
// 若为p-n开头的插件,则必须是从宿主设置的“插件安装路径”上(默认为files目录)才能安装,其余均不允许
if (path.startsWith("p-n-")) {
String installPath = RePlugin.getConfig().getPnInstallDir().getAbsolutePath();
if (!path.startsWith(installPath)) {
if (LogDebug.LOG) {
LogDebug.e(TAG, "install: Must be installed from the specified path. Path=" + path + "; Allowed=" + installPath);
}
return null;
}
}
return MP.pluginDownloaded(path);
}
MP.pluginDownloaded(path)
replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\MP\
public static final PluginInfo pluginDownloaded(String path) {
...
PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path);
if (info != null) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginSucceed(info);
}
return info;
}
这里具体的安装流程调用的是pluginDownloaded(path)
, 那就必须清楚getPluginHost()返回的对象的类型
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\PluginProcessMain\
public static final IPluginHost getPluginHost() {
if (sPluginHostLocal != null) {
return sPluginHostLocal;
}
// 可能是第一次,或者常驻进程退出了
if (sPluginHostRemote == null) {
if (LogDebug.LOG) {
if (IPC.isPersistentProcess()) {
LogDebug.e(PLUGIN_TAG, "插件框架未正常初始化");
throw new RuntimeException("插件框架未正常初始化");
}
}
// 再次唤起常驻进程
connectToHostSvc();
}
return sPluginHostRemote;
}
如果是常驻进程会使用sPluginHostLocal
,非常驻进程则会使用sPluginHostRemote
, 如果常驻进程没有被启动,此时必须先去启动。
/**
* 非常驻进程调用,获取常驻进程的 IPluginHost
*/
static final void connectToHostSvc() {
Context context = PMF.getApplicationContext();
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
if (binder == null) {
// 无法连接到常驻进程,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p fhb fail");
}
System.exit(1);
}
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (LOGR) {
LogRelease.i(PLUGIN_TAG, "p.p d, p.h s n");
}
// 检测到常驻进程退出,插件进程自杀
if (PluginManager.isPluginProcess()) {
if (LOGR) {
// persistent process exception, PLUGIN process quit now
LogRelease.i(MAIN_TAG, "p p e, pp q n");
}
System.exit(0);
}
sPluginHostRemote = null;
// 断开和插件化管理器服务端的连接,因为已经失效
PluginManagerProxy.disconnect();
}
}, 0);
} catch (RemoteException e) {
// 无法连接到常驻进程,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p p.h l2a: " + e.getMessage(), e);
}
System.exit(1);
}
//
sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "host binder.i = " + PluginProcessMain.sPluginHostRemote);
}
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostRemote);
// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p p.h l3a: " + e.getMessage(), e);
}
System.exit(1);
}
// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\PluginProviderStub\
private static final IBinder proxyFetchHostBinder(Context context, String selection) {
Cursor cursor = null;
try {
Uri uri = ProcessPitProviderPersist.URI;
cursor = context.getContentResolver().query(uri, PROJECTION_MAIN, selection, null, null);
if (cursor == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "proxy fetch binder: cursor is null");
}
return null;
}
while (cursor.moveToNext()) {
//
}
IBinder binder = BinderCursor.getBinder(cursor);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "proxy fetch binder: binder=" + binder);
}
return binder;
} finally {
CloseableUtils.closeQuietly(cursor);
}
}
源码路径:F:\doc\my\replugin\RePlugin\replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\replugin\component\process\ProcessPitProviderPersist\
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
sInvoked = true;
return PluginProviderStub.stubMain(uri, projection, selection, selectionArgs, sortOrder);
}
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\PluginProviderStub\
public static final Cursor stubMain(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
//
if (LOG) {
LogDebug.d(PLUGIN_TAG, "stubMain projection=" + Arrays.toString(projection) + " selection=" + selection);
}
if (SELECTION_MAIN_BINDER.equals(selection)) {
return BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder());
}
if (SELECTION_MAIN_PREF.equals(selection)) {
// 需要枷锁否?
initPref();
return BinderCursor.queryBinder(sPrefImpl);
}
return null;
}
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\PmBase\
final IBinder getHostBinder() {
return mHostSvc;
}
/**
* Persistent(常驻)进程的初始化
*
*/
private final void initForServer() {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search plugins from file system");
}
mHostSvc = new PmHostSvc(mContext, this);
PluginProcessMain.installHost(mHostSvc);
StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);
// 兼容即将废弃的p-n方案 by Jiongxuan Zhang
mAll = new Builder.PxAll();
Builder.builder(mContext, mAll);
refreshPluginMap(mAll.getPlugins());
// [Newest!] 使用全新的RePlugin APK方案
// Added by Jiongxuan Zhang
try {
List<PluginInfo> l = PluginManagerProxy.load();
if (l != null) {
// 将"纯APK"插件信息并入总的插件信息表中,方便查询
// 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准
refreshPluginMap(l);
}
} catch (RemoteException e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "lst.p: " + e.getMessage(), e);
}
}
}
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\loader2\PmHostSvc\
@Override
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;
}
源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\replugin\packages\PluginManagerServer\
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;
}