Replugin源码阅读---框架初始化

642 阅读12分钟

阅读RePlugin源码, 希望搞清楚以下几件事:

1、RePluginClassLoader初始化以及使用

2、PluginDexClassLoader初始化

3、如何加载插件中的Activity

4、插件安装流程

5、常驻进程与非常驻进程、为何要使用常驻进程、实现进程间通信的方式

  RePlugin默认会使用一个常驻进程作为Server端, 其他插件进程和宿主进程全部属于Client端. 如果修改不使用常驻进程, 那么宿主的主进程将作为插件管理进程, 而不管是使用宿主进程还是使用默认的常驻进程, Server端其实就是创建了一个运行在该进程中的Provider, 通过 Providerquery 方法返回了Binder对象来实现多进程直接的沟通和数据共享, 或者说是插件之间和宿主之间沟通和数据共享, 插件的安装、卸载、更新、状态判断等全部都在这个Server端完成.

  Replugin使用占坑的方式来实现插件化, replugin-host-gradle这个gradle插件会在编译的时候自动将坑位信息生成在主工程的AndroidManifest.xml中, RePluginClassLoader的唯一hook点是hook了系统的ClassLoader, 当启动四大组件时会通过Client端发起远程调用去Server做一系列事情, 例如检查插件是否安装、安装插件、提取优化dex文件、分配坑位、启动坑位, 这样可以欺骗系统达到不在AndroidManifest.xml注册的小郭, 最后在Client端加载要被启动的四大组件, 因为已经hook了系统的ClassLoader, 所以可以对系统的类加载过程进行拦截, 将之前分配的坑位信息替换成真正要启动的组件信息并使用与之对应的ClassLoader来进行类的加载, 从而启动未在AndroidManifest.xml中注册的组件

  Replugin框架将插件的管理工作统一放在一个进程中, 其他进程需要通过插件管理进程返回的Binder对象来进程操作, 这样既保证了信息的安全性, 又可以分担其他进程的工作压力. 框架的初始化主要创建了一些来管理和操作插件的Binder对象, 然后通过区分进程来分别初始化插件管理进程和Client进程各自要做的事情, 插件管理进程主要是对 插件信息的更新和维护, 而Client进程主要是需要获取到插件管理的进程Binder对象来进行后续的操作等, 在各进程初始化完成以后将会对所有的插件进行存储, 然后hook系统的ClassLoader, 最后加载默认插件.

1.RePluginApplication.attachBaseContext
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    RePluginConfig c = createConfig();
    if (c == null) {
        c = new RePluginConfig();
    }
    RePluginCallbacks cb = createCallbacks();
    if (cb != null) {
        c.setCallbacks(cb);
    }
    // 1.初始化入口
    RePlugin.App.attachBaseContext(this, c);
}
2.Replugin.App.attachBaseContext
 public static void attachBaseContext(Application app, RePluginConfig config) {
    if (sAttached) {
        return;
    }
    RePluginInternal.init(app);
    sConfig = config;
    sConfig.initDefaults(app);
    /**
     * 1.初始化进程间通信辅助类, 不同进程的创建会使attachBaseContext方法执行多次,
     *   IPC.init()会标记当前进程类型, 将会影响下面的代码在不同进程中的逻辑
     */
    IPC.init(app);
    /**
     * 2.初始化HostConfigHelper(通过反射HostConfig来实现)
     *   在编译期间replugin-host-gradle自动生成的RepluginHostConfig, 这个方法是一个空
     *   方法, 反射初始化的逻辑在static代码块中.
     */
    HostConfigHelper.init();
    // 3.用来管理插件的状态: 正常运行、被禁用等情况
    PluginStatusController.setAppContext(app);
    // 4.初始化RePlugin框架和hook系统的PathClassLoader的入口
    PMF.init(app);
    // 5.加载内置插件
    PMF.callAttach();
    sAttached = true;
}
3.IPC.init不同进程执行不同的逻辑
public static void init(Context context) {
	// 1.获取当前进程名
    sCurrentProcess = SysUtils.getCurrentProcessName();
    // 2.获取当前进程pid
    sCurrentPid = Process.myPid();
    // 3.获取宿主包名
    sPackageName = context.getApplicationInfo().packageName;
    // 如果配置常驻进程可用, 设置常驻进程名
    if (HostConfigHelper.PERSISTENT_ENABLE) {
        String cppn = HostConfigHelper.PERSISTENT_NAME;
        if (!TextUtils.isEmpty(cppn)) {
            if (cppn.startsWith(":")) {
            	// 4.常驻进程名为 包名:GuardService
                sPersistentProcessName = sPackageName + cppn;
            } else {
                sPersistentProcessName = cppn;
            }
        }
    } else {
   		// 5.如果不使用常驻进程管理插件, 则使用当前进程名称
        sPersistentProcessName = sPackageName;
    }
    // 6.判断当前进程是否为主进程
    sIsUIProcess = sCurrentProcess.equals(sPackageName);
    // 7.判断当前进程是否为常驻进程
    sIsPersistentProcess = sCurrentProcess.equals(sPersistentProcessName);
}

  通过proc文件获取当前进程名、进程id和宿主包名, 然后设置常驻进程的名称, 最后标记当前进程是否为UI进程或常驻进程. 多进程的情况下, Application.onCreate会被调用多次, 这里也会被调用多次.

  常驻进程进程名为 包名:GuardService, 多次调用导致了IPC中判断当前是否是插件管理进程时的值不同而影响后面的执行逻辑不同.

4.PMF.init
public static final void init(Application application) {
    setApplicationContext(application);
	/**
     * 1.这里创建了一个叫Tasks的类, 在里面创建了一个主线程的Handler
     * 2.通过当前进程的名字判断应该将插件分配到哪个进程中
     */
    PluginManager.init(application);

    sPluginMgr = new PmBase(application);
    sPluginMgr.init();

    Factory.sPluginManager = PMF.getLocal();
    Factory2.sPLProxy = PMF.getInternal();
	
    PatchClassLoaderUtils.patch(application);
}

  Replugin最重要的两个核心: PmBase的创建和初始化, PmBase它本身和它内部引用的其他对象掌握了Replugin中很多重要的功能, 例如: 分配坑位、初始化插件信息、Client端连接Server端、加载插件、更新插件、删除插件等.

5.PmBase构造函数
PmBase(Context context) {
	// 1.mContext -> Application
    mContext = context;
	// 2.UI进程或者是插件进程
    if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
        String suffix;
        // 3.如果是UI进程, 设置前缀为N1
        if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
            suffix = "N1";
        } else {
        	// 4.非UI进程前置为0或者1 
            suffix = "" + PluginManager.sPluginProcessIndex;
        }
        /**
         * CONTAINER_PROVIDER_PART: .loader.p.Provider
         * 5.包名.loader.p.ProviderN1 或者 包名.loader.p.Provider0 或者
         *	 包名.loader.p.Provider1添加到set中
         */
        mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);
        /**
         * CONTAINER_SERVICE_PART: .loader.s.Service
         * 6.包名.loader.s.ServiceN1 或者 包名.loader.s.Service0 或者
         *   包名.loader.s.Service1添加到set中
         */
        mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
    }
    /**
     * 7.创建PluginProcessPer类, 代表当前Client进程, 也可以简单的想象成是插件, 但并不全是插件
     *   PluginProcessPer这个类创建的时候构造中又创建了两个对象.
     */
    mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);
    // 8.创建PluginCommImpl类, 负责宿主与插件、插件间的通信
    mLocal = new PluginCommImpl(context, this);
    // 9.Replugin框架中内部逻辑使用的很多方法都在这里, 包括插件中通过"反射"调用的内部逻辑
    mInternal = new PluginLibraryInternalProxy(this);
}

PmBase的构造方法主要做了四件事情:

1.根据当前进程类型, 拼接坑位provider和service所对应名称并存入不同的HashSet中, PmBase类中处理保存了Provider、Service、Activity的坑位信息, 这些名字全部都是Replugin在编译时在AndroidManifest.xml中声明的坑位名字.

2.创建了一个叫RepluginProcessPer的类, 它是一个Binder对象, 代表了"Client端", 使用它来和Server端进行通信, 这个类的构造中创建了两个类, 一个是PluginContainers, 用来管理Activity坑位信息的容器, 初始化了多种不同启动模式和样式Activity的坑位信息, 另一个PluginServiceServer类, 这个类是Replugin中的一个核心类, 主要负责对Service的提供和调度工作, 例如startService、stopService、bindService、unbindService全部都由这个类管理.

3.创建了一个叫PluginCommImpl的类, 负责宿主与插件、插件之间的通信, 很多对提供方法都经过这里中转或者最终调到这里

4.创建了一个PluginLibraryInternalProxy的类, REplugin框架中内部逻辑使用的很多方法都在这里, 包括插件中通过"反射"调用的内部逻辑如PluginActivity类的调用, Factory2等.

6.PmBase.init
void init() {
	// 1.判断是否使用常驻进程管理插件
    if (HostConfigHelper.PERSISTENT_ENABLE) {
        // 2.(默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client
        if (IPC.isPersistentProcess()) {
            // 3.初始化“Server”所做工作
            initForServer();
        } else {
            // 4.连接到Server
            initForClient();
        }
    } else {
        // 5.“UI进程”作为插件管理进程(唯一进程),则UI进程既可以作为Server也可以作为Client
        if (IPC.isUIProcess()) {
            // 6.尝试初始化Server所做工作,
            initForServer();
            // 7.注册该进程信息到“插件管理进程”中
            // 注意:这里无需再做 initForClient,因为不需要再走一次Binder
            PMF.sPluginMgr.attach();
        } else {
            // 8.其它进程?直接连接到Server即可
            initForClient();
        }
    }
    // 最新快照
    PluginTable.initPlugins(mPlugins);
}
7.PmBase.initForServer
private final void initForServer() {
	// 1.返回Binder对象
    mHostSvc = new PmHostSvc(mContext, this);
    // 2.将PmHostSvc赋值给PluginProcessMain, 将PluginManagerService中的Binder对象
    //   Stub赋值给PluginManagerProxy
    PluginProcessMain.installHost(mHostSvc);
    // 3.清理之前的任务
    StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);
    // 兼容即将废弃的p-n方案
    // 4.这个类封装了各种插件类型的集合, 还有生成插件模型信息和删除信息的方法
    mAll = new Builder.PxAll();
    // 5.搜索所有本地插件和V5插件信息, 并添加进Builder集合中就是mAll字段, 然后删除一些不符合
    //   规则的插件信息, 这里搜索了所有本地插件, 也就是放在assets中的插件, 通过插件自动生成的
    //	 json文件来扫描的, v5是通过context.getDir路径来扫描的.
    Builder.builder(mContext, mAll);
    // 6.将刚扫描的本地插件封装成Plugin添加进mPlugins中, mPlugins代表所有的插件的集合
    refreshPluginMap(mAll.getPlugins());

    // [Newest!] 使用全新的RePlugin APK方案
    /**
     * 7.load是远程调用, 最终调用了PluginManagerService的loadLocked方法,
     *   这里主要是判断之前安装的插件是否需要更新或删除等操作, 然后进行响应的操作并返回
     *   处理后的集合.
     *   返回的集合是一个副本, 这样可以保证信息的安全性
     */
    List<PluginInfo> l = PluginManagerProxy.load();
    if (l != null) {
        // 将"纯APK"插件信息并入总的插件信息表中,方便查询
        // 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准
        // 8.将之前的插件信息也添加进mPlugins中, mPlugins代表所有插件的集合
        refreshPluginMap(l);
    }
}

  PmHostSvc为Server端, 它直接或间接参与了Server端要做的所有事情, PmHostSvc参考的是AMS的结构和原理.

8.PmHostSvc构造函数
PmHostSvc(Context context, PmBase packm) {
    mContext = context;
    mPluginMgr = packm;
    // 1.创建一个service管理者
    mServiceMgr = new PluginServiceServer(context);
    // 2.创建一个插件管理者, 用来控制插件的安装☎️卸载、获取等
    mManager = new PluginManagerServer(context);
}
9.PluginProcessMain.installHost
static final void installHost(IPluginHost host) {
	// 1.持有PmHostSvc
    sPluginHostLocal = host;
    // 2.连接到插件化管理器的服务端
    PluginManagerProxy.connectToServer(sPluginHostLocal);
}
10.PluginManagerProxy.connectToServer
public static void connectToServer(IPluginHost host) throws RemoteException {
    if (sRemote != null) {
        return;
    }
    sRemote = host.fetchManagerServer();
}
11.PmHostSvc.fetchManagerServer
方法调用链:
PmHostSvc.fetchManagerServer()
->PluginManagerServer.getService()

private IPluginManagerServer mStub;

public PluginManagerServer(Context context) {
    mContext = context;
    mStub = new Stub();
}

public IPluginManagerServer getService() {
    return mStub;
}

class Stub extends IPluginServiceServer.Stub

  PmHostSvc对外提供了一些功能, 例如要安装一个插件, 那么首先要获取到管理插件安装的Binder对象IPluginManagerServer, 而且并不是只有一个进程可以安装插件, 所有的进程都可以, 那么要想保证插件信息的唯一性和正确性, 就只能有一个地方来统一管理这些信息, 那么这个地方就是PmHostSvc, 只要想使用安装插件这个方法就必须要通过PmHostSvc得到IPluginManagerServer的Binder对象, 因为只有PmHostSvc对外提供了获得IPluginManagerServer的功能.

12.PmBase.initForClient()
private final void initForClient() {
    // 1.先尝试连接
    PluginProcessMain.connectToHostSvc();
    // 2.然后从常驻进程获取插件列表, 并更新插件列表和在initForServer()执行的更新的插件信息
    refreshPluginsFromHostSvc();
}
13.PluginProcessMain.connectToHostSvc
static final void connectToHostSvc() {
    Context context = PMF.getApplicationContext();
    // 1.进行远程调用返回Binder, 实际返回常驻进程中的PmHostSvc对象
    IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
    // 2.通过调用asInterface方法确定是否需要返回远程代理, PmHostSvc的代理
    sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
    // 3.连接到插件化管理器的服务端
    PluginManagerProxy.connectToServer(sPluginHostRemote);
    // 将当前进程的"正在运行"列表和常驻做同步
    // TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
    PluginManagerProxy.syncRunningPlugins();
    // 注册该进程信息到“插件管理进程”中
    PMF.sPluginMgr.attach();
}
14.PluginProviderStub.proxyFetchHostBinder
调用链:
PluginProviderStub.proxyFetchHostBinder()
-> ProcessPitProviderPersist.query()
-> PluginProviderStub.stubMain()
-> PMF.sPluginMgr.getHostBinder()
-> return mHostSvc = new PmHostSvc();
15.PmBase.initForServer

  插件管理进程最主要干的事情是创建了PmHostSvc这个Binder对象, 它其中还管理另外两个最重要的Binder对象, PluginServerSvice: 管理Service, PluginManagerServer: 管理插件安装、卸载等的. 接着从assets的json文件中和v5安装目录中扫描本地的插件信息, 将信息添加进mPlugins中.

16.PmBase.initForClient

  通过Provider的方式请求插件管理进程返回PmHostSvc这个Binder对象, 接着通过PmHostSvc再得到PluginManagerServer这个Binder对象并把当前 进程信息注册到Server端, 最后通过得到的Binder对象来同步进程信息和更新插件信息.

二、插件信息的加载

截止到目前, 插件框架初始化流程梳理清楚了, 但是加载插件信息的流程目前还不清除, 接下来进行分析

2.1 PluginManagerProxy.load
方法调用链:
PmBase.initForServer()
-> PluginManagerProxy.load()
-> sRemote.load()
-> PluginManagerServer.loadLocked() 

private List<PluginInfo> loadLocked() {
    if (!mList.load(mContext)) {
        return null;
    }
    // 执行“更新或删除Pending”插件,并返回结果
    return updateAllLocked();
}
2.2 PluginInfoList.load
PluginInfoList用于存储所有的插件信息

public boolean load(Context context) {
    // 1. 读出字符串
	final File f = getFile(context);
	final String result = FileUtils.readFileToString(f, Charsets.UTF_8);
	if (TextUtils.isEmpty(result)) {
        return false;
    }
    // 2. 解析出JSON
    final JSONArray jArr = new JSONArray(result);
    for (int i = 0; i < jArr.length(); i++) {
        final JSONObject jo = jArr.optJSONObject(i);
        final PluginInfo pi = PluginInfo.createByJO(jo);
        if (pi == null) {
            continue;
        }
        addToMap(pi);
    }
    return true;
}

  解析p.l文件, 获取所有插件信息, 将插件信息通过addToMap存储在mMap中.

2.3 PmBase.refreshPluginMap
private final void refreshPluginMap(List<PluginInfo> plugins) {
    if (plugins == null) {
        return;
    }
    for (PluginInfo info : plugins) {
        Plugin plugin = Plugin.build(info);
        // 将插件信息存储在Plugin中, Plugins存储至mPlugins中
        putPluginObject(info, plugin);
    }
}
2.4 PluginTable.initPlugins
static final void initPlugins(Map<String, Plugin> plugins) {
    synchronized (PLUGINS) {
        for (Plugin plugin : plugins.values()) {
            putPluginInfo(plugin.mInfo);
        }
    }
}

private static void putPluginInfo(PluginInfo info) {
    // 同时加入PackageName和Alias(如有)
    PLUGINS.put(info.getPackageName(), info);
    if (!TextUtils.isEmpty(info.getAlias())) {
        // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已
        PLUGINS.put(info.getAlias(), info);
    }
}

  注意先后顺序, 只有常驻进程才会对mPlugins进行初始化, 然后读取插件信息存储在mPlugins中, 然后通过putPluginInfo存储在PLUGINS中

2.5 PmBase.refreshPluginsFromHostSvc
private void refreshPluginsFromHostSvc() {
	// 1.调用常驻进程的PmHostSvc对象, 获取PluginInfo的集合
    List<PluginInfo> plugins = PluginProcessMain.getPluginHost().listPlugins();
    // 判断是否有需要更新的插件
    // 执行此操作前,判断下当前插件的运行进程,具体可以限制仅允许该插件运行在一个进程且为自身进程中
    List<PluginInfo> updatedPlugins = null;
    if (isNeedToUpdate(plugins)) {
		updatedPlugins = PluginManagerProxy.updateAllPlugins();
    }
    if (updatedPlugins != null) {
        refreshPluginMap(updatedPlugins);
    } else {
    	// 2.通过常驻进程的PmHostSvc对象获取插件信息列表, 然后存储在自己进程的mPlugins中
        refreshPluginMap(plugins);
    }
}
2.6 PluginProcessMain.getPluginHost
/**
 * sPluginHostLocal 常驻进程使用,非常驻进程为null buyuntao
 * sPluginHostRemote 非常驻进程使用,常驻进程为null,用于非常驻进程连接常驻进程  buyuntao
 */
public static final IPluginHost getPluginHost() {
    if (sPluginHostLocal != null) {
    	//1.当前进程为常驻进程: PmHostSvc
        return sPluginHostLocal;
    }
    // 可能是第一次,或者常驻进程退出了
    if (sPluginHostRemote == null) {
        // 再次唤起常驻进程
        connectToHostSvc();
    }
    // 2.当前进程为非常驻进程, 返回常驻进程的Binder对象:PmHostSvc
    return sPluginHostRemote;
}

  initForServer中初始化PmHostSvc以后通过installHost将PmHostSvc赋值给sPluginHostLocal.

  这里之所以要再次判断sPluginHostRemote是否为null, 如果为null的情况下通过connectToHostSvc(), 有很多坑位组件使用android:process=":GuardService"属性,因此如果Persistent进程不小心被杀掉了,在任何需要启动这些坑位组件的地方都会将Persistent进程重新启动起来

唯一插件化Replugin源码及原理深度剖析--初始化之框架核心