Android全面插件化RePlugin流程與源碼解析

RePlugin,360開源的全面插件化框架,按照官網(wǎng)說的,其目的是“盡可能多的讓模塊變成插件”,并在很穩(wěn)定的前提下,盡可能像開發(fā)普通App那樣靈活。那么下面就讓我們一起深入♂了解它吧。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )

一、介紹

RePlugin對比其他插件化,它的強大和特色,在于它只Hook住了ClassLoader。One Hook這個堅持,最大程度保證了穩(wěn)定性、兼容性和可維護性,詳見《全面插件化——RePlugin的使命》。當然,One Hook也極大的提高了實現(xiàn)復雜程度性,其中主要體現(xiàn)在:

  • 增加了Gradle插件腳本,實現(xiàn)開發(fā)中自動代碼修改與生成。
  • 分割了插件庫和宿主庫的代碼實現(xiàn)。
  • 代碼中存在很多不少@deprecatedTODO和臨時修改。
  • 初始化、加載、啟動等邏輯比較復雜。
圖一 Replugin項目結(jié)構(gòu)

本篇將竭盡所能,為各位介紹其流程和內(nèi)部實現(xiàn),如果存在一些地方存在紕漏,還請指出。文章篇幅較長,需耐心閱讀,閱讀時可結(jié)合圖片源碼,同時歡迎收藏,或選擇感興趣點閱讀,下面主要涉及:

  • 二、ClassLoader基礎(chǔ)知識。
  • 三、Replugin項目原理和結(jié)構(gòu)分析。
  • 四、Replugin的ClassLoader。
  • 五、Replugin的相關(guān)類介紹。
  • 六、Replugin的初始化。
  • 七、Replugin啟動Activity。
此處應有圖

二、ClassLoader基礎(chǔ)知識

既然Replugin選擇Hook住ClassLoader,那先簡單介紹下ClassLoader的基本知識吧,如熟悉者請略過。

ClassLoader又叫類加載器,是專門處理類加載,一個APP可以存在多個ClassLoader,它使用的是雙親代理模型,如下圖所示,創(chuàng)建一個ClassLoader,需要使用一個已有的ClassLoader對象,作為新建的實例的ParentLoader。

抽象基類ClassLoader

這樣的條件下,一個App中所有的ClassLoader都聯(lián)系了起來。當加載類時,如果當前ClassLoader未加載此類,就查詢ParentLoader是否加載過,一直往上查找,如果存在就返回,如果都沒有,就執(zhí)行該Loader去執(zhí)行加載工作。這樣避免了類重復加載的浪費。其中常見的Loader有:

  • BootClassLoader 是系統(tǒng)啟動時創(chuàng)建的,一般不需要用到。
  • PathClassLoader 是應用啟動時創(chuàng)建的,只能加載內(nèi)部dex。
  • DexClassLoader 可以加載外部的dex。

RePlugin中存在兩個主要ClassLoaer:

  • 1、RePluginClassLoader 宿主App中的Loader,繼承PathClassLoader,也是唯一Hook住系統(tǒng)的Loader。

  • 2、PluginDexClassLoader 加載插件的Loader,繼承DexClassLoader。用來做一些“更高級”的特性。

三、Replugin項目原理和結(jié)構(gòu)分析

1、基礎(chǔ)原理

簡單來說,其核心是hook住了 ClassLoader,在Activity啟動前:

  • 記錄下目標頁 ActivityA,替換成已自動注冊在 AndroidManifest 中的坑位 ActivityNS
  • ClassLoader 中攔截ActivityNS的創(chuàng)建,創(chuàng)建出ActivityA返回。
  • 返回的ActivityA占用著 ActivityNS 這個坑位,坑位由Gradle編譯時自動生成在AndroidManifest中。

在編譯時,replugin-replugin-library腳本,會替換代碼中的基礎(chǔ)類和方法。如下圖【官方原理圖】所示,替換的基類里會做一些初始化,所以這一塊稍微有點入侵性。此外,replugin-host-library生成AndroidManifest配置相關(guān)信息打包等,也由Gradle插件自動完成。

打包獨立APK,或者打包為插件,可單可插,這就是RePlugin。

官方原理圖

2、項目結(jié)構(gòu)

RePlugin整個項目結(jié)構(gòu),目前分為四個module,其中又分為兩個gradle插件module,兩個library的java module,詳細如開頭【圖一 Replugin項目結(jié)構(gòu)】,本文主要分析library相關(guān),如果對gradle插件感興趣的,可以查看結(jié)尾其他推薦。

2.1、replugin-host-gradle :

對應com.qihoo360.replugin:replugin-host-gradle:xxx依賴,主要負責在主程序的編譯期中生產(chǎn)各類文件:

  • 根據(jù)用戶的配置文件,生成HostBuildConfig類,方便插件框架讀取并自定義其屬性,如:進程數(shù)、各類型占位坑的數(shù)量、是否使用AppCompat庫、Host版本、pulgins-builtin.json文件名、內(nèi)置插件文件名等。

  • 自動生成帶 RePlugin 插件坑位的 AndroidManifest.xml文件,文件中帶有如:

<activity 
    android:theme="@style/Theme.AppCompat" 
    android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1STTS0"
    android:exported="false" 
    android:screenOrientation="portrait"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize" 
/>
2.2、replugin-host-library:

對應com.qihoo360.replugin:replugin-host-lib:xxx依賴,是一個Java工程,由主程序負責引入,是RePlugin的核心工程,負責初始化、加載、啟動、管理插件等。

2.3、replugin-plugin-gradle:

對應com.qihoo360.replugin:replugin-plugin-gradle:xxx ,是一個Gradle插件,由插件負責引入,主要負責在插件的編譯期中:配置插件打包相關(guān)信息;動態(tài)替換插件工程中的繼承基類,如下,修改Activity的繼承、Provider的重定向等。

    /* LoaderActivity 替換規(guī)則 */
    def private static loaderActivityRules = [
            'android.app.Activity'                    : 'com.qihoo360.replugin.loader.a.PluginActivity',
            'android.app.TabActivity'                 : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
            'android.app.ListActivity'                : 'com.qihoo360.replugin.loader.a.PluginListActivity',
            'android.app.ActivityGroup'               : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
            'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
            'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
            'android.preference.PreferenceActivity'   : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
            'android.app.ExpandableListActivity'      : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
    ]
2.4、replugin-plugin-library:

對應com.qihoo360.replugin:replugin-plugin-lib:xxx依賴,是一個Java工程,由插件端負責引入,主要提供通過“Java反射”來調(diào)用主程序中RePlugin Host Library的相關(guān)接口,并提供“雙向通信”的能力,以及各種基類Activity等
  
  其中的RePluginRePluginInternalPluginServiceClient都是反射宿主App :replugin-host-library 中的 RePluginRePluginInternalPluginServiceClient 類方法。

四、Replugin的ClassLoader。

這里主要介紹,宿主和插件使用的ClassLoader,以及它們的創(chuàng)建和Hook住時機。這是RePlugin唯一的Hook點,而其中插件ClassLoader和宿主ClassLoader是相互關(guān)系的,如下圖

將就的圖
1、宿主的ClassLoader

RePluginClassLoader,宿主的ClassLoader,繼承 PathClassLoader,構(gòu)造方法使用原ClassLoader,和原ClassLoader的Parent生成。其中ParentLoader是因為雙親代理模型,創(chuàng)建ClassLoader所需,而原Loader用于保留在后期使用,如下圖

如下兩圖RePluginClassLoader 在創(chuàng)建時,淺拷貝原Loader的資源到 RePluginClassLoader 中,用于欺騙系統(tǒng)還處于原Loader,并且從原Loader中反射出常用方法,用于重載方法中使用。

拷貝資源
方式方法

宿主Loader中,主要是重載了 loadClass,其中從 PMF(RePlugin中公開接口類)中查找class,如果存在即返回插件class,如果不存在就從原Loader中加載。從而實現(xiàn)了對加載類的攔截。

這里的 PMF 在加載class時,其實用的是下面【2、插件的ClassLoader 】:PluginDexClassLoader,這個后面流程會講到。

2、插件的ClassLoader

PluginDexClassLoader,繼承DexClassLoader,構(gòu)造時持有了宿主的ClassLoader,從宿主ClassLoader中反射獲取loadClass方法,當自己的loadClass方法找不到類時,從宿主Loader中加載。

3、創(chuàng)建和Hook

創(chuàng)建:上面1、2中兩個Loader,是宿主在初始化時創(chuàng)建的,初始化時可以選擇配置RePluginCallbacks,callback中提供方法默認創(chuàng)建Loader,你也可以實現(xiàn)自定義的ClassLoader,但是需要繼承以上的Loader,如下圖

//初始化方式創(chuàng)建
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);
RePluginCallbacks

Hook:初始化時,PatchClassLoaderUtils會在Application的attachBaseContext()中,通過patch(application)Hook住宿主的ClassLoader,patch內(nèi)部如下圖

hook ClassLoader

五、Replugin的相關(guān)類介紹

提前介紹一些功能類,后面就不做詳細介紹。

** 1、RePlugin** :RePlugin的對外入口類,提供install、uninstall、preload、startActivity、fetchPackageInfo、fetchComponentList,fetchClassLoader等等統(tǒng)一的方法入口,用戶操作的主要是它。
  
2、RePlugin.App:RePlugin中的內(nèi)部類,針對Application的入口類,所有針對插件Application的調(diào)用應從此類開始和初始化,想象成插件的Application吧。

3、PmBase:RePlugin常用mPluginMgr變量表示,可以看作插件管理者。初始化插件、加載插件等一般都是從它開始。

4、PluginContainers:插件容器管理中心。

5、PmLocalImpl:各種本地接口實現(xiàn),如startActivity,getActivityInfo,loadPluginActivity等。

6、PmInternalImpl:類似Activity的接口實現(xiàn),內(nèi)部實現(xiàn)了真正startActivity的邏輯、還有插件Activity生命周期的接口。

準備好了嗎,騷年

六、Replugin的初始化

那就是從 Application 初始化開始看起,枯燥的流程就要開始了,忍住兄弟,我們能贏。首先我們先看下面這流程圖,大致了解啟動流程:

將就的看吧
1、attachBaseContext

首先是從 Application 的 attachBaseContext 初始化開始。如下圖,這里主要是配置 RePluginConfigRePluginCallbacks ,然后根據(jù) Config 去初始化插件。值得注意的是,RePluginConfig 中的 RePluginCallbacks 提供了默認方法創(chuàng)建 RePlugin 的 ClassLoader,還記得上面的介紹嗎?

看圖看圖
2、插件App.attachBaseContext

繼續(xù)上面的流程,進入RePlugin.App.attachBaseContext(this, c),如下圖,這里主要是初始化插件相關(guān)的進程、配置信息、插件的主框架和接口、根據(jù)默認路徑、加載默認插件等。插件的初始化從這里開始,其中主要為 PMF.init()PMF.callAttach()

繼續(xù)看圖看圖
3、主程序接口 PMF.init()/PMF.callAttach()

先進入到 PMF.init() ,如下圖,這里主要實例化了 PmBase 類,并初始化了它,創(chuàng)建了內(nèi)部使用的 PmLocalImplPmInternalImp 接口 ,同時Hook住主程序的 ClassLoader,替換為 RePluginClassLoader,所以接下來的流程,主要是在 PmBase

PMF.init(),看圖吧

PmBase,按照項目中的變量名 mPluginMgr,可以理解為插件的管理者,它管理內(nèi)部直接或間接的,管理著坑位分配、ClassLoader、插件、進程、啟動\停止頁面的接口等,如下圖。

PmBase創(chuàng)建,還是看圖

PmBase 的初始化,也就是插件的初始化,這里會啟動各類進程,初始化各種默認插件集合,為后續(xù)加載做準備。其中默認插件和配置文件的位置,一般默認是在 assert 的 plugins-builtin.json 和 "plugins" 文件夾下。

PmBase.init() 看圖看圖

接著PMF.callAttach() 其實就是 PmBase.callAttach()如下圖這里開始真正加載插件,初始化插件的 PluginDexClassLoader 、加載插件、初始化插件環(huán)境和接口。其中在執(zhí)行 p.load() 的時候,會通過 Plugind.callAppLocked() 創(chuàng)建插件的 Application,并初始化。

PMF.callAttach() 看圖唄

以上是在主APP的初始化,深入 PmBase 中,Plugin.load()在加載時,會調(diào)用PluginDexClassLoader, 通過類名加載 Entry 類,然后反射出create方法,執(zhí)行插件的初始化。其中 Entry 位于Plugin-lib庫中。這里初始化就去到了插件中了,插件中初始化時,會通過反射的到宿主host類的方法。

4、Application的onCreate

這里主要是切換handler到主線程,注冊各種廣播接收監(jiān)聽,如增加插件、卸載插件、更新插件,可以看出這里設(shè)計很多內(nèi)部進程通信的。

七、Replugin啟動Activity

這里僅描述了Activity啟動的其中一個流程,也是簡化版的,實際代碼邏輯復雜多了,但是萬變不離其宗,這里幫你梳理流程,描述一些關(guān)鍵的點,讓你快速理解Activity的啟動流程。

再將就下吧,看圖
1、startActivity

從上面的流程圖我們知道,啟動插件Activity可以從RePlugin.startActivity開始,startActivity經(jīng)歷了 FactoryPmLocalImpl ,其實大部分啟動的邏輯其實主要在 PmInternalImpl 中。

具體流程如下圖,這里簡化了實際代碼,關(guān)鍵在于 loadPluginActivity。這里獲取了插件對應的坑位,然后保存了目標Activity的信息,通過系統(tǒng)啟動坑位。

因為已經(jīng)Hook住了ClassLoader,在 loadClass 時再加載出目標Activity,這樣坑位中承載的,便是繞過系統(tǒng)打開的目標Activity。下面我們進入 loadPluginActivity

說了看圖
2、loadPluginActivity

loadPluginActivity 其實是 PmBase 中的 PmLocalImpl 內(nèi)部方法。如下圖,這里主要是根據(jù)獲取到 ActivityInfo,然后根據(jù)坑位去為目標Activity分配坑位。

其中 getActivityInfo 是通過插件名稱,獲得插件對象 PluginPlugin可能是初始化中已加載的,如果未加載就加載返回,然后根據(jù) Plugin 中緩存的坑位信息,返回 ActivityInfo

下面進入 allocActivityContainer 看坑位的分配,只有分配到坑位,插件的Activity才可以啟動,這是一個IPC過程。

看圖沒?
2、allocActivityContainer

allocActivityContainer 在類 PluginProcessPer 中,還記得我們在 PmBase.init() 時初始化過它么? 分配坑位也是RePlugin的核心之一。

allocActivityContainer 中, 主要邏輯是bindActivity ,如下圖,bindActivity 去找到目標Activity匹配的容器,然后加載目標Activity判斷是否存在,并建立映射,返回容器。然后分配的邏輯,在 PluginContainers.alloc 中。

看我大圖
3、PluginContainers.alloc

alloc / alloc2 方法分配坑位,最后都是到了 allocLocked 方法中,其實RePlugin中,如下圖,便是坑位分配的邏輯:

  • 如果存在未啟動的坑位,就使用它。
  • 如果沒有就找最老的:已經(jīng)被釋放的、或者時間最老的。
  • 如果還不行,那么擠掉最老的一個。
看圖說話
4、PulginActivity

上面的流程總結(jié),是替換目標Activity,加載插件,分配坑位,啟動目標坑位,攔截ClassLoader的loadClass去加載返回目標Activity。

這個時候啟動的Activity還不完整,從模塊框架中我們知道,在編譯時,RePlugin會把繼承的Activity替換為如 PluginActivity(當前還有AppComPluginActivity等)。這時候加載啟動的目標Activity,其實是繼承了 PluginActivity

如下圖PluginActivity 重載Activity中的一些方法,實現(xiàn)了Activity的補全和自定義操作,如坑位管理,啟動宿主Activity等。

至此,一個插件Activity就啟動起來了,頭暈目眩了沒?為了實現(xiàn) One Hook 這個信念,RePlugin 實現(xiàn)了復雜的流程,從代碼中可以看出,這些年作者們從中走的的各種坑、各種妥協(xié)與堅持、復雜的技術(shù)積累、已經(jīng)經(jīng)歷了多年的嚴酷考驗。

不知道有多少人能完整看到這,碼字不易,如有疏漏還是多多包涵,由于篇(tou)幅(lan)原因,關(guān)于Service等的就不多做敘述了,不知道本文對你是否能有些幫助,歡迎留言討論。

最后說“一”句

為什么要去了解一個庫實現(xiàn)原理呢?學習框架的架構(gòu)思想?這是一個原因。但是歸根結(jié)底,是幫助你在使用庫的過程中,能靠自己解決各種問題。程序員的日常一般都忙于各種工作,各種技術(shù)群中的大佬們,大部分時候,沒辦法一一解答你的各種咨詢,所以使用它、了解它、多嘗試靠自己去探索突破吧。

其他推薦

注意到了嗎?最后的總是我!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容