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閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容

  • RePlugin,360開源的全面插件化框架,按照官網說的,其目的是“盡可能多的讓模塊變成插件”,并在很穩定的前提...
    戀貓月亮閱讀 33,331評論 29 122
  • 題記 寫這篇關于Replugin插件化框架的分析,旨在引導讀者去快速的了解RePlugin的大概實現原理,文中會拋...
    Ihesong閱讀 1,724評論 0 1
  • 前言 Replugin 已經開源一個月了,最近幾天終于抽出時間來研究研究,這里將我的一些心得體會寫下來,分享給大家...
    蔣揚海閱讀 17,913評論 13 38
  • Part One Composition ...
    冥想音閱讀 144評論 0 0
  • 昨日考核結果出來,著實還是嚇到了!作為小小負責人還是不希望看到的!雖然早作了準備,沒想也臺差了!別人都說似乎平時我...
    8fb8be38f05e閱讀 135評論 0 0