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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yulong0809/article/details/78423529

上一篇:唯一插件化RePlugin源码及原理深度剖析–工程职责

提示:请不要忽略代码注释,由于通畅上下逻辑思维,不太重要的部分跳转代码不会全部进去一行行的看,但是会将注释写出来,所以请务必不要忽略注释,而且最好是
跟着文章一起看源码。

一、Replugin的初始化会参照下面几个类的设置,这样设计可以让接入者更方便的定制Replugin功能,但是必要需要继承Replugin提供的类以保证不影响其核心逻辑,建议在自定制的时候也要阅读源码不要影响本身的核心功能,几个类中都有注释,不再一一的说方法的用处了,只简单说一下几个类主要干什么的

RePluginApplication:Replugin对外提供方便宿主继承的Application类,因为Replugin需要在Application中进行初始化

RePluginCallbacks:主要用来生成宿主和插件的ClassLoader、要打开的插件不存在时的回调、要打开的插件文件过大时回调等。

RePluginEventCallbacks:插件化框架对外事件回调接口集,宿主需继承此类,并复写相应的方法来自定义插件框架的事件处理机制,其中有安装插件成功、失败、启动插件Activity时的一些事件回调等。

RePluginConfig:主要用于对Replugin的一些初始化设置,其中包括:设置是否开启插件的签名校检,当插件中使用某个类不存在时是否使用宿主中的类等。而且还保持了对RepluginCallbacks和RepluginEventCallbacks的引用。     

以上几个类都可以不设置,如果不设置Replugin会创建一个默认的

二.这里先简单了说一下Replugin框架的核心原理或者说是Replugin的底层原理,Replugin的整体框架使用了Binder机制来进行宿主和多插件之间交互通信和数据共享,这里如果了解android四大组件的运行流程的话,看完了Replugin的源码后会感觉非常像简易ServiceManager和AMS的结构。

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的初始化从大面上来说可以分为三个模块:

1)Replugin框架的初始化:Replugin的整个实现的主体,整体框架感觉非常像一个微型的Android系统,对所有插件的控制操作和系统的AMS等一些类非常的相似

2)Hook住系统的ClassLoader:这也是Replugin与其他插件框架众不同之处,唯一的Hook点保证了系统的稳定性和后期的维护成本

3)加载默认插件:在上面两点初始化完成后,进行了默认插件的加载,加载插件并不只是主程序的工作,其实还要初始化插件工程,将主工程和插件工程绑定,后面才可以完美的相互调用。

由于篇幅的缘故,本章先说框架的初始化,其他两块会在后续文章分别分析

三、源码阅读
1、首先Replugin给我们提供了一个RePluginApplication的类,方便宿主之间继承,如果不继承该类必须手动调用初始化相关方法。初始化的代码要在Application的attachBaseContext中执行,为什么要在这个方法中初始化呢?因为在应用启动后Replugin必须要尽量早的初始化框架相关代码才能够保证后续在使用相关功能的时候可以得到正确的执行,例如我们已经知道了Replugin其中的的核心之一是通过hook住了系统ClassLoader来加载未注册组件的,那么就要尽量早的接管系统的类加载过程,而attachBaseContext是ContextWrapper中的方法,这个方法在Application被创建后,调用attach的方法时被调用的,可以说是应用端可以收到最早的回调。

入口位置 : com.qihoo360.replugin.RePluginApplication

 @Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);

    //获取宿主创建的RePluginConfig
    RePluginConfig c = createConfig();
    if (c == null) {
        //如果为空Replugin自己创建一个RePluginConfig,     
        c = new RePluginConfig();
    }

    //获取宿主中的RePluginCallbacks对象,如果为空后面会自动创建一个默认的
    //这个类提供了宿主和插件的ClassLoader
    RePluginCallbacks cb = createCallbacks();
    if (cb != null) {
        //设置给config
        c.setCallbacks(cb);
    }

    //初始化
    RePlugin.App.attachBaseContext(this, c);
}

2、上面的代码是在为了初始化做准备, 初始化时会根据RePluginConfig和RePluginCallbacks来进行Replugin的基本设置,上面已经说过了两个类的主要作用,本文主要分析框架初始化,ClassLoader部分后续会单独分析,初始化调用了RePlugin.App.attachBaseContext方法,下面我们去看一下这个方法

源码位置:com.qihoo360.replugin.RePlugin

//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;
    }

上面初始化重要的地方是初始化多进程通信的辅助类IPC,这个类的多次调用会影响Replugin的整体代码逻辑的执行,还有就是PMF的初始化时创建初始了Replugin需要核心类和hook系统的ClassLoader都在这里。这里的IPC.init非常重要,因为每一个进程创建都会执行一次,会导致后面初始化代码在不同进程中执行不同的逻辑。既然IPC的初始化导致不同逻辑,我们先来看一下IPC.init这个方法

3.看一下IPC初始化:
源码位置: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);
}

4.IPC的代码初始化就这么多,主要就是通过proc文件获取当前进程名、进程id和宿主包名,然后设置常驻进程的名称,最后标记当前进程是否是ui进程和是不是常驻进程,我们知道每一个进程在创建后都会调用该进程的ActivitiThread的main方法,开启消息循环,绑定AMS,创建Application,加载provider,然后回调Application生命周期方法。所以说这里会被调用多次,多次调用导致了IPC中判断当前是否是插件管理进程时的值不同而影响后面的执行逻辑不同。
现在我们返回去看PMF.init(app)方法,我们要记住这一步,我们这一步进去后逻辑会很长,我们还有第2步中的PMF.callAttach()没有没有看。

源码位置: com.qihoo360.loader2.PMF

public static final void init(Application application) {

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

    //1.这里创建了一个叫Tasks的类,在里面中创建了一个主线程的Hanlder,2.通过当前进程的名字判断应该将插件分配到哪个进程中,
    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);
}

5、现在到这里出现了Replugin最重要的两个核心,一个是PmBase的创建和初始化,其实这也是Replugin框架核心初始化的开始,
PmBase它本身和它内部引用的其他对象掌握了Replugin中很多重要的功能,例如:分配坑位、初始化插件信息、Clent端连接Server端、加载插件、更新插件、删除插件、等等。另一个核心是Replugin中的唯一hook点,整个框架只hook了系统的ClassLoader。因为篇幅的缘故,我们将这两个核心逻辑分成两篇文章来将,我们先说框架核心的创建和初始化,Hook和插件的加载下一篇再讲。
我们先去看PmBase的构造,这里我们要记住第4步代码还没看完

源码位置:com.qihoo360.loader2.PmBase;

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

    //引用Application
    mContext = context;

    //判断当前进程类型
    if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
        String suffix;

        //如果是ui进程,设置suffix = N1;
        if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
            suffix = "N1";
        } else {
            //后缀值 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、创建了PluginProcessPer类,代表了当前Clent进程,也可以简单的想象成是插件,但并不全是插件
    //PluginProcessPer这个类创建的时候构造中又创建了两个对象   
    mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);

    //3.创建了PluginCommImpl类,负责宿主与插件、插件间的互通,可通过插件的Factory直接调用,也可通过RePlugin来跳转,
    //创建的时候只是引用了Application和PmBase
    mLocal = new PluginCommImpl(context, this);

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


6、PmBase的构造主要干了4件事件:
1) 根据当前进程类型,拼接坑位provider和Service所对应名称并存入不同的HashSet中,PmBase类中处理保存了Provider、Service、Activitiy的坑位信息,这些名字全部都是Replugin在编译的时候在AndroidManifest.xml中声明的坑位名字
这里写图片描述

这里写图片描述

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

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

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

我们这一篇注重的是Replugin的整体核心框架的初始化,所以我们不在继续跟踪其他分支的创建和初始化了,因为其中每一个涉及的逻辑都很深,这样的话可能导致我们对整体框架的初始化逻辑混淆,所以我们继续返回第4步继续看PmBase的init方法

源码位置: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);

}

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

源码位置:com.qihoo360.loader2.PmBase

private final void initForServer() {

    //这个类有意思了,为什么有意思一会下面说
    //继承于IPluginHost.Stub,是一个Binder对象
    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);
        }
    }
}


8、这里最主要是就是创建了PmHostSvc类,这个类可以理解成是我们的Server端,它直接或间接参与了Server端要做的所有事情,
我在注释上说过了PmHostSvc非常有意思,是因为它非常的像AMS的结构和原理,至于为什么这么说接着看,剩下的其它逻辑都是在初始化
插件的信息,就不去挨个看了,我们还是主要看一下对PmHostSvc的操作

源码位置:com.qihoo360.loader2.PmHostSvc

class PmHostSvc extends IPluginHost.Stub {

    。。。

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

    。。。
}

9、重点注意PmHostSvc这个类它继承自IPluginHost.Stub,一会下面会说到。构造中持有了PmBase的引用,然后又创建了两个类,创建了一个PluginServiceServer,然后还创建了一个PluginManagerServer,这个类暂时先放一小下,马上就会说到它,还有IPluginHost.Stub也暂时放一小下,下面都会有关联,我么现在先返回去接着看第7步中PluginProcessMain.installHost(mHostSvc)这行代码

源码位置:com.qihoo360.loader2.PluginProcessMain

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

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

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

10、上面调用PluginManagerProxy.connectToServer连接Server端,进去看看做了什么
源码位置:com.qihoo360.replugin.packages.PluginManagerProxy

public static void connectToServer(IPluginHost host) throws RemoteException {

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

    //这里的host还是上面创建的PmHostSvc,不要晕啊
    sRemote = host.fetchManagerServer();
}


11、上面调用了fetchManagerServer方法将返回值赋值给sRemote,我们再点进去看看,注意了哦!!

//IPluginHost继承自IInterface
public interface IPluginHost extends android.os.IInterface {

。。。
//这个Stub不就是刚才PmHostSvc继承的IPluginHost.Stub吗! 它继承自Binder
public static abstract class Stub extends android.os.Binder implements com.qihoo360.loader2.IPluginHost {

    。。。。

    //看到asInterface方法了吗?
     public static com.qihoo360.loader2.IPluginHost asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.qihoo360.loader2.IPluginHost))) {
            return ((com.qihoo360.loader2.IPluginHost) iin);
        }
        return new com.qihoo360.loader2.IPluginHost.Stub.Proxy(obj);
    }

    。。。。

}
    。。。

    //上面调用的fetchManagerServer方法
    public com.qihoo360.replugin.packages.IPluginManagerServer fetchManagerServer() throws android.os.RemoteException;

    。。。

}


12、到这里我们应该知道了PmHostSvc它是一个Binder对象,这里如果不明白的建议看我之前写过的Binder机制,因为下面可能会更复杂。
我们接着分析,上面调用了fetchManagerServer这个方法是一个抽象的,现在看完了这个逻辑应该知道去看谁的方法了吧?没错,就是PmHostSvc,上面我们说过要注意PmHostSvc继承自IPluginHost.Stub的,IInterface、Binder、asInterface方法,这不就是典型的aidl或者说是Binder机制,IPluginHost肯定是一个aidl的文件,我们找一下。
这里写图片描述

上面我还说它像AMS,我们再看看系统如何返回给我们AMS的代码结果

//ActivityManagerNative,继承自Binder,实现了IActivityManager的aidl接口,这不就是Stub吗,只不过换了个名字ActivityManagerNative
public abstract class ActivityManagerNative extends Binder implements IActivityManager  
{
        。。。
    //asInterface方法
    static public IActivityManager asInterface(IBinder obj) {

            if (obj == null) {
                return null;
            }           
            IActivityManager in =                   (IActivityManager)obj.queryLocalInterface(descriptor);              
            if (in != null) {
                return in;
            }
            //返回代理类
            return new ActivityManagerProxy(obj);
    }

    。。。
}

//ActivityManagerService继承了ActivityManagerNative,是不是就像PmHostSvc继承了IPluginHost.Stub
public final class ActivityManagerService extends ActivityManagerNative
    implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

    。。。
}

应该都知道AMS和用户应用不存在一个进程中,但是我们可以使用它提供给我们的功能,例如我们想启动一个Activitiy,那么我们直接调用startActivity就可以了,这个Activity之前是不是已经被加载,这个Activity所在的进程是否存在,这些都不需要我们管,全部由AMS来完成,而且并不是只有一个应用程序可以调用startActivity吧,而是所有android应用程序都可以使用,可是AMS只有一个吧,只有这样才能保证系统的所有信息是唯一的,是正确的。如果你想使用这些功能必须通过AMS,因为只有它对外提供了这个功能。

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

那么我们通过举例的方式对比完了AMS和PmHostSvc,是不是觉得他们的结构和原理是一样的呢,虽然代码不同,功能不同,但是原理相通,而且PmHostSvc还不只像AMS,还有点像ServiceManger,因为它管理着其他的Binder对象的获取,这点是不是有点像ServiceManager呢。其实Replugin里不是只有PmHostSvc像AMS,而是这个Replugin框架整体像AMS和ServiceManager的感觉,或者说是微型Android系统

好了,我们接着分析,在第10步中调用了host.fetchManagerServer(),点进去以后是抽象的,但是我们已经知道mHostSvc继承IPluginHost.Stub了,那么我们直接去看mHostSvc中的fetchManagerServer方法

源码位置:com.qihoo360.loader2.PmHostSvc

 @Override
public IPluginManagerServer fetchManagerServer() throws RemoteException {

    //这个mManager还记得吗,第8步PmHostSvc构造中创建的PluginManagerServer
    return mManager.getService();
}

13、这里直接调用了mManager.getService(),这个mManager是在第8步中PmHostSvc构造中创建的PluginManagerServer。
我们去看一下

源码位置:com.qihoo360.replugin.packages.PluginManagerServer

//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
    private class Stub extends IPluginManagerServer.Stub {
        。。。
    }
}       


14、直接返回了构造中创建的内部类Stub,这个Stub继承了IPluginManagerServer.Stub类,看这结构应该还是一个aidl吧,我们去看看是不是,然后看看都有什么方法

源码位置:com.qihoo360.replugin.packages.IPluginManagerServer

//IPluginManagerServer.aidl文件
interface IPluginManagerServer {

    /**
     * 安装一个插件
     * <p>
     * 注意:若为旧插件(p-n开头),则应使用IPluginHost的pluginDownloaded方法
     *
     * @return 安装的插件的PluginInfo对象
     */
    PluginInfo install(String path);

    /**
     * 卸载一个插件
     * <p>
     * 注意:只针对“纯APK”插件方案
     *
     * @param info 插件信息
     * @return 是否成功卸载插件?
     */
    boolean uninstall(in PluginInfo info);

    /**
     * 加载插件列表,方便之后使用
     * <p>
     * TODO 这里只返回"新版插件",供PmBase使用。将来会合并
     *
     * @return PluginInfo的列表
     */
    List<PluginInfo> load();

    /**
     * 更新所有插件列表
     *
     * @return PluginInfo的列表
     */
    List<PluginInfo> updateAll();

    /**
     * 设置isUsed状态,并通知所有进程更新
     *
     * @param pluginName 插件名
     * @param used 是否已经使用
     */
    void updateUsed(String pluginName, boolean used);

    /**
     * 获取正在运行的插件列表
     *
     * @return 正在运行的插件名列表
     */
    PluginRunningList getRunningPlugins();

    /**
     * 插件是否正在运行?
     *
     * @param pluginName 插件名
     * @param process 指定进程名,如为Null则表示查所有
     * @return 是否在运行?
     */
    boolean isPluginRunning(String pluginName, String process);

    /**
     * 当进程启动时,同步正在运行的插件状态到Server端
     *
     * @param list         正在运行的插件名列表
     */
    void syncRunningPlugins(in PluginRunningList list);

    /**
     * 当进程启动时,同步正在运行的插件状态到Server端
     *
     * @param processName  进程名
     * @param pluginName   正在运行的插件名
     */
    void addToRunningPlugins(String processName, int pid, String pluginName);

    /**
     * 获取正在运行此插件的进程名列表
     *
     * @param pluginName 要查询的插件名
     * @return 正在运行此插件的进程名列表。一定不会为Null
     */
    String[] getRunningProcessesByPlugin(String pluginName);
}

15、看到了吧,安装插件、卸载插件等等都在这里吧,也就是说关于插件的所有操作都在Server端吧。好了,我们要回到第7步的逻辑中也就是initForServer()方法的逻辑,剩下的代码都是更新插件信息的逻辑了,就不进去看代码了,直接说流程和结果了,有兴趣的可以自己去看。现在总结一下整个插件管理进程的initForServer()方法做了什么:

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

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

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

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

16、现在我们已经分析完了第6步中插件管理进程执行的initForServer()方法了,现在我们再去看看其他进程执行的initForClient()都干了什么

源码位置:com.qihoo360.loader2.PmBase

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

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

    // 2. 然后从常驻进程获取插件列表,并更新插件列表和上面在initForServer()执行的更新插件信息差不多了
    refreshPluginsFromHostSvc();
}

17、就两行代码,先连接到Server端,也就是插件的管理进程。然后从常驻进程获取插件列表,判断列表中是否有需要更新的插件,如果有调用Binder对象在Server端更新插件信息,后续操作都和上边分析过的initForServer()中更新插件信息类似,我们就主要看一下是怎么连接到Server端的就好了

源码位置: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);

   。。。

    // 连接到插件化管理器的服务端
    try {
        PluginManagerProxy.connectToServer(sPluginHostRemote);

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

    // 注册该进程信息到“插件管理进程”中来统一管理
    PMF.sPluginMgr.attach();
}

18、首先PluginProviderStub.proxyFetchHostBinder(context)获取了一个IBander对象,接着下面通过调用asInterface方法确定是否需要返回远程代理,然后调用PluginManagerProxy.connectToServer(sPluginHostRemote)方法,这个方法在第9步中,PluginProcessMain的installHost也调用过这个方法,它里面调用了IPluginHost.fetchManagerServer()方法将PluginManagerServer中的Stub对象,也就是IPluginServiceServer类型的Binder对象赋值给PluginManagerProxy类中的字段sRemote,这个IPluginServiceServer类型的Binder对象掌握了对插件的安装、卸载、更新等等的操作。最后调用了PMF.sPluginMgr.attach()方法将当前进程信息注册到“插件管理进程”中来统一管理。
我们来看一下PluginProviderStub.proxyFetchHostBinder方法怎么返回的PmHostSvc

源码位置:com.qihoo360.loader2.PluginProviderStub

static final IBinder proxyFetchHostBinder(Context context) {

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

直接调用了proxyFetchHostBinder方法,但是传入的参数多个了”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"
        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);
    }
}

19、文章开头简单说了Replugin的原理是通过Provider传递Binder来实现多进程沟通的,在这里得到的确认,但是Replugin是怎么传递这个Binder过来的呢?我们首先要找个这个Provider吧,那么我们通过它要查询的uri来搜索一下AndroidManifest.xml不就知道了吗,因为如果要使用Provider必须要在AndroidManifest中注册这个Provider,这里不要混淆一个概念,通过Replugin启动的组件虽然没有在AndroidManifest中注册,但是那是因为通过hook了ClassLoader拦截了类加载过程加载了未注册的组件,并不是说不用注册,而是使用了坑位来代替未注册的组件去
欺骗系统而已。
这个Provider可不是一个坑位,而是一个实实在在的在AndroidManifest中注册了的真是Provider,因为这个Provider就代表了插件的管理进程,也就是一直说的Server端。看一下这个Provider的uri是:content://包名.loader.p.main/main,包名每个宿主都不一样,注意要去宿主的AndroidManifest.xml中去搜索,应该能明白为什么吧?还有就是如果你直接打开宿主的AndroidManifest是没有这些信息的,因为他们都是Replugin的gradle插件在编译的时候动态写入的,所以要去build包下的intermediates中去看,搜的时候可能有很多包含了这个.loader.p.main的多个Provider,我们一定要找完全匹配这个后缀的。
这里写图片描述

找到了吧,点进去我们看看这个Provider的query方法
源码位置:com.qihoo360.replugin.component.process.ProcessPitProviderPersist

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

    sInvoked = true;
    //返回了PluginProviderStub.stubMain
    return PluginProviderStub.stubMain(uri, projection, selection, selectionArgs, sortOrder);
}

执行返回了PluginProviderStub.stubMain,我们再点进去

源码位置:com.qihoo360.loader2.PluginProviderStub

 public static final Cursor stubMain(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

    //常量SELECTION_MAIN_BINDER = "main_binder"
    //这个selection也是"main_binder",它不就是在调用查询Provider的时候传入的吗
    if (SELECTION_MAIN_BINDER.equals(selection)) {
        //又直接返回了
        return BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder());
    }

    。。。

    return null;
}

又直接返回了 BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder()),里面传入的参数是PMF.sPluginMgr.getHostBinder(),sPluginMgr是最开始初始化的PmBase,我们看看getHostBinder()返回了什么

源码位置:com.qihoo360.loader2.PmBase

final IBinder getHostBinder() {
    //还有印象吗?
    return mHostSvc;
}

返回了mHostSvc字段,这个字段应该还有印象吧,不就是在插件管理进程执行的方法initForServer()中创建的PmHostSvc,我们再去看BinderCursor.queryBinder方法,传入了PmHostSvc

源码位置:com.qihoo360.loader2.BinderCursor

//这里传过来的IBinder就是PmHostSvc
public static final Cursor queryBinder(IBinder binder) {

    //封装成BinderCursor
    return new BinderCursor(PluginInfo.QUERY_COLUMNS, binder);
}

再往下还有几个跳转,就不去看了,说一下干了什么就好了,这里创建了
BinderCursor返回了,BinderCursor在构造中创建一BinderParcelable并传入了Binder,然后将这个BinderParcelable存入了BinderCursor中的一个Bundle中。现在我们在回到去看一下第18步中,通过Provider得到cursor,然后从cursor中取出的Binder就是PmHostSvc

20、到这里这一趟线的代码到头了,我们要返回去看了,跑了这么远是从第6步开始的也就是PmBase的init()方法,让我们把思路移回去,我们主要的就是对initForServer()和initForClient()方法分别都干了什么进行了分析。
我们简单的总结一下第6步PmBase的init()方法:

1)分进程处理各自进行需要做的事情,这俩有两种情况:

插件管理进程:initForServer()
插件管理进程最主要干的事情就是创建了PmHostSvc这个Binder对象,它其中还管理着另外两个重要的Binder对象一个是用来管理Service的PluginServiceServer,还有一个是来管理插件安装、卸载等的PluginManagerServer。接着从assest的json文件中和v5安装目录中扫描本地的插件信息,将信息添加进一个叫mPlugins的ConcurrentHashMap中,然后再去查看之前是否有已经安装的插件,如果有判断是否需要更新、删除等操作,并做相应的处理后返回处理后的插件信息集合,将集合中的内容也添加进mPlugins中。存入的key是包名或者别名,value是Plugin

其他进程:initForClient()
这个进程干的事情就简单了,就是通过Provider的方式请求插件管理进程返回PmHostSvc这个Binder对象,接着通过PmHostSvc再得到PluginManagerServer这个Binder对象并把当前进程信息注册到Server端,最后通过得到的Binder对象来同步进程信息和更新插件信息,

2)在上面各进程操作完后会遍历mPlugins中的所有插件信息,并将他们存入到一个叫PluginTable类中一个叫PLUGINS的HashMap中,存入的key是包名或者别名,value是PluginInfo,这就是

21、第6步总结完了,其实真正让我们跑了这么远的是PmBase的创建和它的init方法,我们把思路再放回到第4步中的PMF的init()方法,剩下几个赋值的操作就不再分析了,因为现在是初始化,还没有使用场景。在第4步中还有最后一行代码是hook系统的ClassLoader,我们说了将它单独拿出来将,而且会从系统源码的角度去深度剖析。

22、我们再往回看,我们只有一个方法没有去看了,那就是第2步中的PMF.callAttach()方法,上面从第2步一直到现在全部都是在PMF.init()中执行的,最后让我们看看PMF.callAttach()这个方法吧,方法中其实调用的是PmBase中callAttach()方法,我们就直接看PmBase的了

    final void callAttach() {

    //获取ClassLoader
    mClassLoader = PmBase.class.getClassLoader();

    // Plugin代表一个插件.将Context、ClassLoader、和PluginCommImpl绑定到该插件上
    for (Plugin p : mPlugins.values()) {
        p.attach(mContext, mClassLoader, mLocal);
    }

    // 加载默认插件
    if (PluginManager.isPluginProcess()) {

        if (!TextUtils.isEmpty(mDefaultPluginName)) {
            //
            Plugin p = mPlugins.get(mDefaultPluginName);
            Log.e("mDefaultPluginName","p = " + p);
            if (p != null) {
                //真正加载插件是Plugin的 load方法
                boolean rc = p.load(Plugin.LOAD_APP, true);
                if (!rc) {
                    if (LOG) {
                        LogDebug.d(PLUGIN_TAG, "failed to load default plugin=" + mDefaultPluginName);
                    }
                }
                if (rc) {
                    mDefaultPlugin = p;
                    mClient.init(p);
                }
            }
        }
    }
}

23、可以看到上面最后一步是要加载默认插件,我们之前说过了,会将加载插件这一步单独来将,我们将会从插件的安装到加载详细的介绍整个步骤的实现原理,所以这里暂时就不深入了,我们把核心框架的初始化来总结一下。

核心框架初始化总结:

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

下一篇:唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理
将会从系统源码的角度解析其原理。

猜你喜欢

转载自blog.csdn.net/yulong0809/article/details/78423529
今日推荐