Replugin源码解析之replugin-host-library---多进程初始化及通信

概述

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

其实Replugin还是使用的占坑的方式来实现的插件化,replugin-host-gradle这个gradle插件会在编译的时候自动将坑位信息生成在主工程的AndroidManifest.xml中,Replugin的唯一hook点是hook了系统了ClassLoader,当启动四大组件的时候会通过Clent端发起远程调用去Server做一系列的事情,例如检测插件是否安装,安装插件,提取优化dex文件,分配坑位,启动坑位,这样可以欺骗系统达到不在AndroidManifest.xml注册的效果,最后在Clent端加载要被启动的四大组件,因为已经hook了系统的ClassLoader,所以可以对系统的类加载过程进行拦截,将之前分配的坑位信息替换成真正要启动的组件信息并使用与之对应的ClassLoader来进行类的加载,从而启动未在AndroidManifest.xml中注册的组件。
先Replugin给我们提供了一个RePluginApplication的类,方便宿主之间继承,如果不继承该类必须手动调用初始化相关方法。初始化的代码要在Application的attachBaseContext中执行,为什么要在这个方法中初始化呢?因为在应用启动后Replugin必须要尽量早的初始化框架相关代码才能够保证后续在使用相关功能的时候可以得到正确的执行,例如我们已经知道了Replugin其中的的核心之一是通过hook住了系统ClassLoader来加载未注册组件的,那么就要尽量早的接管系统的类加载过程,而attachBaseContext是ContextWrapper中的方法,这个方法在Application被创建后,调用attach的方法时被调用的,可以说是应用端可以收到最早的回调。

源码

1:入口,com.qihoo360.replugin.RePluginApplication

   @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
      //获取宿主创建的RePluginConfig
        RePluginConfig c = createConfig();
        if (c == null) {
            c = new RePluginConfig();
        }
      //createCallbacks 方法默认返回null,子类可以复写
    //这个类提供了宿主和插件的ClassLoader
        RePluginCallbacks cb = createCallbacks();
        if (cb != null) {
      //将 callback设置给c,如果子类重写则以设置的callback为准,否则如果为null,下面的attachBaseContext会创建一个默认的
            c.setCallbacks(cb);
        }
      //开始初始化
        RePlugin.App.attachBaseContext(this, c);
    }

2:com.qihoo360.replugin.RePlugin.App

//App是RePlugin的内部类
 public static void attachBaseContext(Application app, RePluginConfig config) {

        //保证只会初始化一次
        if (sAttached) {
            return;
        }

        //缓存Application
        RePluginInternal.init(app);
        sConfig = config;

        //1.获取pn插件安装目录,即context.getFilesDir()
        //2.判断如果RePluginCallbacks为空创建一个
        //3.判断RePluginEventCallbacks为空创建一个
        sConfig.initDefaults(app);

        //初始化进程间通信辅助类,因为不同进程的创建会使attachBaseContext方法走多次,
        //IPC.init中会标记当前进程类型,将会影响下面的代码在不同进程中的逻辑,存储当前进程的信息
        IPC.init(app);

        // 初始化HostConfigHelper,通过反射宿主RePluginHostConfig实现的,具体参数请参看RePluginHostConfig
        //就是在编译期间replugin-host-gradle自动生成的RepluginHostConfig,这个方法是一个空方法,反射初始化的逻辑在static语句块中
        HostConfigHelper.init();

        //缓存Application
        AppVar.sAppContext = app;

        //PluginStatusController用来管理插件的运行状态:
        //用来管理插件的状态:正常运行、被禁用,等情况
        //设置Application的引用
        PluginStatusController.setAppContext(app);

         //真正初始化Replugin的框架和hook住了系统的PatchClassLoader地方
        //最重要的地方
        PMF.init(app);
        //加载默认插件
        PMF.callAttach();
        //标记已经初始化完成,完成以后将不能再修改RepluginConfig类中的设置
        sAttached = true;
    }

3:com.qihoo360.replugin.base.IPC

 public static void init(Context context) {
    //通过proc文件获取当前进程名
    sCurrentProcess = SysUtils.getCurrentProcessName();

    //获取当前进程pid
    sCurrentPid = Process.myPid();

    //获取宿主程序包名
    sPackageName = context.getApplicationInfo().packageName;

    // 判断是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程
    //并设置常驻进程名称,默认常驻进程名称是以:GuardService结尾的,可以通过
    //宿主module下的build.gradle的repluginHostConfig{}中设置,很多参数参考宿主生成的RePluginHostConfig类
    if (HostConfigHelper.PERSISTENT_ENABLE) {
        //设置cppn名称为:GuardService
        String cppn = HostConfigHelper.PERSISTENT_NAME;
        if (!TextUtils.isEmpty(cppn)) {
            if (cppn.startsWith(":")) {
                //常驻进程名称为 包名:GuardService
                sPersistentProcessName = sPackageName + cppn;
            } else {
                sPersistentProcessName = cppn;
            }
        }
    } else {
        //如果不使用常驻进程管理插件,则使用当前进程名称
        sPersistentProcessName = sPackageName;
    }
    //判断当前进程是否是主进程
    sIsUIProcess = sCurrentProcess.equals(sPackageName);

    //判断当前线程是不是常驻进程
    sIsPersistentProcess = sCurrentProcess.equals(sPersistentProcessName);
}

这里因为是多进程所以这里会被调用多次,IPC主要保存了当前进程的信息。
4: com.qihoo360.replugin.helper.HostConfigHelper

 static {

        try {
            HOST_CONFIG_CLASS = ReflectUtils.getClass(HOST_CONFIG_FILE_PATH + HOST_CONFIG_FILE_NAME);
        } catch (ClassNotFoundException e) {
            // Ignore, Just use default value
        }

        try {
            PERSISTENT_ENABLE = readField("PERSISTENT_ENABLE");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            PERSISTENT_NAME = readField("PERSISTENT_NAME");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_USE_APPCOMPAT = readField("ACTIVITY_PIT_USE_APPCOMPAT");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_TS_STANDARD = readField("ACTIVITY_PIT_COUNT_TS_STANDARD");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TOP");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TASK");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_NTS_STANDARD = readField("ACTIVITY_PIT_COUNT_NTS_STANDARD");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ACTIVITY_PIT_COUNT_TASK = readField("ACTIVITY_PIT_COUNT_TASK");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ADAPTER_COMPATIBLE_VERSION = readField("ADAPTER_COMPATIBLE_VERSION");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }

        try {
            ADAPTER_CURRENT_VERSION = readField("ADAPTER_CURRENT_VERSION");
        } catch (NoSuchFieldException e) {
            // Ignore, Just use default value
        }
    }
 public static void init() {
        // Nothing, Just init on "static" block
    }

该类就是将宿主gradle配置在编译期生成的 com.qihoo360.replugin.gen.RePluginHostConfig(与BuildConfig同主目录)反射生成变量。接下来真正重要的就是 PMF.init(app)和PMF.callAttach()方法,接下来重点看下这2个方法,这2方法层级较深,分开分析.
5:com.qihoo360.loader2.PMF

public static final void init(Application application) {

    //保持对Application的引用
    setApplicationContext(application);

    //1.这里创建了一个叫Tasks的类,在里面中创建了一个主线程的Hanlder
   //2.通过当前进程的名字判断应该将插件分配到哪个进程中,目前通过sPluginProcessIndex标识
 //3.存储当前进程uid到静态变量PluginManager.sUid
    PluginManager.init(application);

    //PmBase是Replugin中非常重要的对象,它本身和它内部引用的其他对象掌握了Replugin中很多重要的功能
    sPluginMgr = new PmBase(application);
    sPluginMgr.init();

    //将在PmBase构造中创建的PluginCommImpl赋值给Factory.sPluginManager
    Factory.sPluginManager = PMF.getLocal();

    //将在PmBase构造中创建的PluginLibraryInternalProxy赋值给Factory2.sPLProxy
    Factory2.sPLProxy = PMF.getInternal();

    //Replugin唯一hook点 hook系统ClassLoader
    PatchClassLoaderUtils.patch(application);
}

PmBase它本身和它内部引用的其他对象掌握了Replugin中很多重要的功能,例如:分配坑位、初始化插件信息、Clent端连接Server端、加载插件、更新插件、删除插件、等等。
另一个核心是Replugin中的唯一hook点,整个框架只hook了系统的ClassLoader
5.1 com.qihoo360.loader2.PmBase

//PmBase1、构造方法
PmBase(Context context) {

    //引用Application
    mContext = context;

    //判断当前进程类型,ui进程或者插件进程。sPluginProcessIndex 在上面的PluginManager.init中赋值
    if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
        String suffix;

        //如果是ui进程,设置suffix = N1;
        if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
            suffix = "N1";
        } else {
            //PluginProcessHost.PROCESS_COUNT = 3,PluginManager在init方法去计算sPluginProcessIndex //时,会去取PluginManager.PROCESS_INT_MAP,该map在static中初始化。所以
//后缀值目前只可能是 0或1
            suffix = "" + PluginManager.sPluginProcessIndex;
        }

        //CONTAINER_PROVIDER_PART = .loader.p.Provider
        // 结果= 包名.loader.p.ProviderN1 或者 包名.loader.p.Provider0 或者 包名.loader.p.Provider1
        //在set中加入provider的名字
        mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);

        //CONTAINER_SERVICE_PART = .loader.s.Service
        //结果 = 包名.loader.s.ServiceN1 或者 包名.loader.s.Service0 或者包名.loader.s.Service1
        //在set中加入service的名称
        mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
    }

 
  /**2、它是一个Binder对象,它代表了“当前Clent端”,也可以简单的想象成是插件,但并不全是插件.使用它来和Server端进行通信.
这个类的构造中有创建了两个类,一个是PluginContainers,用来管理Activity坑位信息的容器,初始化了多种不同启动模式和样式Activity的坑位信息。
另一个PluginServiceServer类,这个类是Replugin中的一个核心类,主要负责了对Service的提供和调度工作,例如startService、stopService、bindService、unbindService全部都由这个类管理 **/
   
    mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);

    /**3.创建了PluginCommImpl类,负责宿主与插件、插件间的互通,很多对提供方法都经过这里中转或者最终调到这里.可通过插件的Factory直接调用,也可通过RePlugin来跳转**/
    //创建的时候只是引用了Application和PmBase
    mLocal = new PluginCommImpl(context, this);

    //4、PluginLibraryInternalProxy类,
    /** Replugin框架中内部逻辑使用的很多方法都在这里,包括插件中通过“反射”调用的内部逻辑
Replugin框架中内部逻辑使用的很多方法都在这里,包括插件中通过“反射”调用的内部逻辑如PluginActivity类的调用、Factory2等**/
    mInternal = new PluginLibraryInternalProxy(this);
}

该构造方法
a:根据当前进程类型,拼接坑位provider和Service所对应名称并存入不同的HashSet中,PmBase类中处理保存了Provider、Service、Activitiy的坑位信息,这些名字全部都是Replugin在编译的时候在AndroidManifest.xml中声明的坑位名字
b:创建PluginProcessPer、PluginProcessPer、PluginCommImpl、PluginLibraryInternalProxy类。

5.2:com.qihoo360.loader2.PmBase

void init() {

    //判断是否使用常驻进程管理插件,默认是true
    if (HostConfigHelper.PERSISTENT_ENABLE) {

        // (默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client
        //判断当前是否是常驻进程
        if (IPC.isPersistentProcess()) {
            // 初始化“Server”所做工作,
            initForServer();  //插件管理进程
        } else {
            // 连接到Server
            initForClient();  //其他进程
        }

    } else {
        // “UI进程”作为插件管理进程(唯一进程),则UI进程既可以作为Server也可以作为Client
        if (IPC.isUIProcess()) {
            // 1. 尝试初始化Server所做工作,
            initForServer();//插件管理进程

            // 2. 注册该进程信息到“插件管理进程”中
            // 注意:这里无需再做 initForClient,因为不需要再走一次Binder
            PMF.sPluginMgr.attach();

        } else {
            // 其它进程?直接连接到Server即可
            initForClient();/其他进程
        }
    }

    //从mPlugins中将所有插件信息取出,保存到PLUGINS中,PLUGINS是一个HashMap,保存的key是包名或者别名,value是PluginInfo
    PluginTable.initPlugins(mPlugins);

}

这里出现了两种情况,一种是使用常驻进程的执行逻辑,另一种是不使用常驻进程的执行逻辑,但是其实意思是一样的,为什么这么说呢,我们开始就说过,不管是常驻进程还是ui进程其实都是插件的管理进程,
这里也是在判断当前是不是插件管理进程,如果是插件管理进程会执行initForServer(),而如果不是插件管理进程则会执initForClient()方法。
当然如果不使用常驻进程的时候,会多执行一句PMF.sPluginMgr.attach(),最后执PluginTable.initPlugins(mPlugins)初始化最新的插件信息。既然initForServer()和initForClient()在不同进程中绝对会执行其中一个
5.2.1com.qihoo360.loader2.PmBase

/**只在管理进程中执行即守护进程或者ui进程**/
private final void initForServer() {
   
    //继承于IPluginHost.Stub,是一个Binder对象,相当于服务端,AMS的结构和原理
    mHostSvc = new PmHostSvc(mContext, this);

    //将PmHostSvc赋值给PluginProcessMain
    //将PluginManagerServer中的Binder对象Stub赋值给PluginManagerProxy
    PluginProcessMain.installHost(mHostSvc);

    //清理之前的任务
    PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);

    //这个类里封装了各种插件类型的集合,还有生成插件模型信息和删除信息的方法
    mAll = new Builder.PxAll();

    //搜索所有本地插件和V5插件信息,并添加进Builder集合中就是mAll字段,然后删除一些不符合规则的插件信息
    //这里搜索了所以本地插件,也就是放在assest中的插件,是通过插件自动生成的json文件来扫描的
    //v5是通过context.getDir路径来扫描的
    Builder.builder(mContext, mAll);

    //将刚扫描的本地插件封装成Plugin添加进mPlugins中,mPlugins代表所有插件的集合
    refreshPluginMap(mAll.getPlugins());

    try {
        //这里调用的load是远程调用的,最终调用了PluginManagerServer的loadLocked方法
        //这里主要是判断之前安装的插件是否需要更新或删除等操作,然后进行响应的操作并返回处理后的集合,
        //返回的集合是一个副本,这样可以保证信息的安全性
        List<PluginInfo> l = PluginManagerProxy.load();
        if (l != null) {
            //将之前的插件信息也添加进mPlugins中,mPlugins代表所有插件的集合
            refreshPluginMap(l);
        }
    } catch (RemoteException e) {
        if (LOGR) {
            LogRelease.e(PLUGIN_TAG, "lst.p: " + e.getMessage(), e);
        }
    }
}

PmHostSvc为它非常的像AMS的结构和原理,分析下其构造
5.2.1.1 com.qihoo360.loader2.PmHostSvc

class PmHostSvc extends IPluginHost.Stub {

    。。。

    PmHostSvc(Context context, PmBase packm) {
        mContext = context;
        mPluginMgr = packm;
        //创建一个service管理者,这个类,在PmBase的构造中的客户端“PluginProcessPer ”的构造中也创建了一个这个对象
        mServiceMgr = new PluginServiceServer(context);
        //创建一个插件管理者,用来控制插件的安装、卸载、获取等
        mManager = new PluginManagerServer(context);
    }

    。。。
}

5.2.1.2 com.qihoo360.loader2.PluginProcessMain

//直接接收父类型IPluginHost,其实就是PmHostSvc
static final void installHost(IPluginHost host) {

    //持有IPluginHost的引用,也就是外面传入的PmHostSvc        
    sPluginHostLocal = host;

    try {
         // 连接到插件化管理器的服务端,传入PmHostSvc           PluginManagerProxy.connectToServer(sPluginHostLocal);
    } catch (RemoteException e) {
    }
}

5.2.1.2.1 com.qihoo360.replugin.packages.PluginManagerProxy

public static void connectToServer(IPluginHost host) throws RemoteException {

    //sRemote是IPluginManagerServer类型
    if (sRemote != null) {
        return;
    }

    /**这里的host还是上面创建的PmHostSvc,不要晕啊,因为他是个Binder,其实就是调用其 PmHostSvc的fetchManagerServer()方法**/
    sRemote = host.fetchManagerServer();
}

5.2.1.2.1.1 com.qihoo360.loader2.PmHostSvc

 @Override
public IPluginManagerServer fetchManagerServer() throws RemoteException {

    //这个mManagerPmHostSvc构造中创建的PluginManagerServer
    return mManager.getService();
}

5.2.1.2.1.1.1 com.qihoo360.replugin.packages.PluginManagerServer

public class PluginManagerServer {

    。。。

    private IPluginManagerServer mStub;

    public PluginManagerServer(Context context) {
        mContext = context;
        //创建了一个Stub内部类
        mStub = new Stub();
    }

    public IPluginManagerServer getService() {
        return mStub;
    }

    。。。

    /**内部类Stub继承自IPluginManagerServer.Stub,IPluginManagerServer为aidl,安装插件、卸载插件等服务端功能,都在里面**/
    private class Stub extends IPluginManagerServer.Stub {
        。。。
    }
}       

总结下5.2.1initForServer()方法总共做了一下几件事:

a、首先创建了一个PmHostSvc对象,这个类继承IPluginHost.Stub,是一个IPluginHost类型的Binder对象,可以说所有的插件的管理工作都是直接或者间接由它处理的,PmHostSvc它代表了Server端要处理的事情,也就是插件管理进程处理的事情

b、在PmHostSvc的构造方法中又创建了两个对象,一个是PluginServiceServer,这个类是用来管理插件Service的远程Server端,还有一个是PluginManagerServer,这个类在创建的时候在构造中又创建了一个继承自IPluginServiceServer.Stub的Stub对象,Stub也是一个Binder对象,通过后来查看IPluginServiceServer的代码,发现这个类掌管了所有对插件的的操作,例如插件的安装、加载、卸载、更新等等

c、调用PluginProcessMain.installHost(mHostSvc)方法将PmHostSvc对象也就是IPluginHost类型赋值给PluginProcessMain中的字段sPluginHostLocal,这个IPluginHost是Binder对象。接着调用了IPluginHost.fetchManagerServer()方法将PluginManagerServer中的Stub对象,也就是IPluginServiceServer类型的Binder对象赋值给PluginManagerProxy类中的字段sRemote,这个IPluginServiceServer类型的Binder对象掌握了对插件的安装、卸载、更新等等的操作

d、剩下的几行代码全部都是来更新插件信息的。先搜索本地插件和V5插件信息,创建插件模型并添加进Builder.PxAll相应的集合中,然后判断删除一些不符合规则的信息,然后同步所有的插件信息,最后判断是否有插件需要更新或删除,对应的执行一些操作。本地插件的扫描是通过assest目录下的一个叫plugins-builtin.json的文件,这个文件是replugin-host-gradle插件自动生成的。V5插件是通过context.getDir的文件目录遍历

5.2.2 接下来看客户端,com.qihoo360.loader2.PmBase

/**
 * Client(UI进程)的初始化
 *如果使用了常驻进程,ui进程对于常驻进程来说也是Clent
 */
 private final void initForClient() {

    // 1. 先尝试连接
    PluginProcessMain.connectToHostSvc();

    // 2. 然后从常驻进程获取插件列表,判断列表中是否有需要更新的插件,如果有调用Binder对象在Server端更新插件信息,其余和上面在initForServer()执行的更新插件信息差不多了
    refreshPluginsFromHostSvc();
}

5.2.2.1 com.qihoo360.loader2.PluginProcessMain

/**
 * 非常驻进程调用,获取常驻进程的 IPluginHost
 */
static final void connectToHostSvc() {

    Context context = PMF.getApplicationContext();

    //通过判断是哪个进程然后进行远程调用返回Binder,其实返回的就是PmHostSvc对象
    IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);

   。。。

    //通过调用asInterface方法确定是否需要返回远程代理
    sPluginHostRemote = IPluginHost.Stub.asInterface(binder);

   。。。

    /** 连接到插件化管理器的服务端  5.2.1.2 中也调用该方法,即在当前进程中缓存获取到的服务端管理接口 IPluginManagerServer(PmHostSvc中 创建的PluginManagerServer中控制的stub)**/ 
    try {
        PluginManagerProxy.connectToServer(sPluginHostRemote);

        // 将当前进程的"正在运行"列表和常驻做同步
        // TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
        PluginManagerProxy.syncRunningPlugins();
    } catch (RemoteException e) {
        // 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀            
        System.exit(1);
    }

    /** 注册该进程信息到“插件管理进程”中来统一管理
    initForClient中最重要方法,上面的都是获取binder及远程管理器,如果用ui进程作为常驻进程,则不需要走binder,注册客户端时不走 initForClient,只走该方法
***/
    PMF.sPluginMgr.attach();
}

5.2.2.1.1 com.qihoo360.loader2.PluginProviderStub

   //常量SELECTION_MAIN_BINDER = "main_binder"
    return proxyFetchHostBinder(context, SELECTION_MAIN_BINDER);

private static final IBinder proxyFetchHostBinder(Context context, String selection) {

    Cursor cursor = null;
    try {
        //uri 经过拼接后: content://包名.loader.p.main/main
        Uri uri = ProcessPitProviderPersist.URI;

        //provider,通过provider返回Binder
        // 常量PROJECTION_MAIN = "main"
      //seletion 为main_binder或main_pref,该情况下为main_binder
        cursor = context.getContentResolver().query(uri, PROJECTION_MAIN, selection, null, null);
        if (cursor == null) {

            return null;
        }
        while (cursor.moveToNext()) {
            //
        }

        //通过cursor得到Binder对象
        IBinder binder = BinderCursor.getBinder(cursor);

        return binder;
    } finally {
        CloseableUtils.closeQuietly(cursor);
    }
}


其实就是Replugin就是通过Provider传递Binder来实现多进程沟通的
查看下生成的Androidmenifest文件发现

//即 包名 + ".loader.p.main"
<provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderPersist' android:authorities='com.liyuange.liyuange.loader.p.main' android:exported='false' android:process=':GuardService' />

5.2.2.1.1.1 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);
    }

5.2.2.1.1.1.1 com.qihoo360.loader2.stubMain

   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);
        }
      /**此时应该是这情况,查询常驻进程中的binder,selection = SELECTION_MAIN_BINDER = "main_binder"**/
        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;
    }

接下来跟进 com.qihoo360.loader2.BinderCursor

public class BinderCursor extends MatrixCursor {
   public static class BinderParcelable implements Parcelable {

        IBinder mBinder;

        public static final Parcelable.Creator<BinderParcelable> CREATOR = new Parcelable.Creator<BinderParcelable>() {
            @Override
            public BinderParcelable createFromParcel(Parcel source) {
                return new BinderParcelable(source);
            }

            @Override
            public BinderParcelable[] newArray(int size) {
                return new BinderParcelable[size];
            }
        };

        BinderParcelable(IBinder binder) {
            mBinder = binder;
        }

        BinderParcelable() {
            //
        }

        BinderParcelable(Parcel source) {
            mBinder = source.readStrongBinder();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeStrongBinder(mBinder);
        }
    }


public BinderCursor(String[] columnNames, IBinder binder) {
        super(columnNames);

        if (binder != null) {
            Parcelable value = new BinderParcelable(binder);
            mBinderExtra.putParcelable(BINDER_KEY, value);
        }
    }
Bundle mBinderExtra = new Bundle();
    @Override
    public Bundle getExtras() {
        return mBinderExtra;
    }
/**相当于**5.2.2.1.1.1.1.1** 即服务端创建了 个BinderCursor ,里面封装了个BinderParcelable,通过其序列化了binder,返回供插件进程查询用**/
 public static final Cursor queryBinder(IBinder binder) {
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "query binder = " + binder);
        }
        return new BinderCursor(PluginInfo.QUERY_COLUMNS, binder);
    }
  //相当于**5.2.2.1.1.2**客户端 即 插件进程通过BinderCursor获取查询返回的BinderParcelable从而获取binder

    public static final IBinder getBinder(Cursor cursor) {
        Bundle extras = cursor.getExtras();
        extras.setClassLoader(BinderCursor.class.getClassLoader());
        BinderParcelable w = (BinderParcelable) extras.getParcelable(BINDER_KEY);
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "get binder = " + w.mBinder);
        }
        return w.mBinder;
    }   
}

总结下5.2.2,即initForClient()方法主要作用:

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

5.2.3 com.qihoo360.loader2.PluginTable

class PluginTable {
 static final HashMap<String, PluginInfo> PLUGINS = new HashMap<String, PluginInfo>();
...
//其实就是将5.2.1 initForServer()及5.2.2 initForClient()中都调用到的`refreshPluginMap`方法,存放于mPlugins中的 插件信息更新一份到PluginTable中
    static final void initPlugins(Map<String, Plugin> plugins) {
        synchronized (PLUGINS) {
            for (Plugin plugin : plugins.values()) {
                putPluginInfo(plugin.mInfo);
            }
        }
    }
...
}

5.3 PMF.init中最后的PatchClassLoaderUtils.patch(application)
6.0PMF.callAttach()涉及到Replugin唯一Hook住系统的 点classLoader。分别定义了RePluginClassLoader及PluginDexClassLoader,来替代主进程中的classloder及用于加载插件apk。涉及太多将在后面另外分析。

总结

核心框架初始化总结:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容

  • RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提...
    恋猫月亮阅读 32,766评论 29 122
  • 题记 写这篇关于Replugin插件化框架的分析,旨在引导读者去快速的了解RePlugin的大概实现原理,文中会抛...
    Ihesong阅读 1,587评论 0 1
  • 前言 Replugin 已经开源一个月了,最近几天终于抽出时间来研究研究,这里将我的一些心得体会写下来,分享给大家...
    蒋扬海阅读 17,372评论 13 38
  • Part One Composition ...
    冥想音阅读 126评论 0 0
  • 昨日考核结果出来,着实还是吓到了!作为小小负责人还是不希望看到的!虽然早作了准备,没想也台差了!别人都说似乎平时我...
    8fb8be38f05e阅读 119评论 0 0