RePlugin源码解析之—插件的安装

RePlugin插件的安装

RePlugin插件的安装流程相对简单点,整个安装其实就是将APK文件中的dex,res, so等移动到一个特定的路径的过程。

插件具体的安装流程

无论是插件的安装和启动,RePlugin对外暴露的都是RePlugin.java提供的接口.

  1. 对于安装会调用install方法:

源码路径:replugin-host-library\replugin-host-lib\src\main\java\com\qihoo360\replugin

/**
 * 安装或升级此插件 <p>
 * 注意: <p>
 * 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件 <p>
 * 2、支持“纯APK”和“p-n”(旧版,即将废弃)插件 <p>
 * 3、此方法是【同步】的,耗时较少 <p>
 * 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);
}
  1. 前面的判断先忽略,我们直接看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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值