RePlugin,360開源的全面插件化框架,按照官網說的,其目的是“盡可能多的讓模塊變成插件”,并在很穩定的前提下,盡可能像開發普通App那樣靈活。那么下面就讓我們一起深入♂了解它吧。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )
一、介紹
RePlugin對比其他插件化,它的強大和特色,在于它只Hook住了ClassLoader。One Hook這個堅持,最大程度保證了穩定性、兼容性和可維護性,詳見《全面插件化——RePlugin的使命》。當然,One Hook也極大的提高了實現復雜程度性,其中主要體現在:
- 增加了Gradle插件腳本,實現開發中自動代碼修改與生成。
- 分割了插件庫和宿主庫的代碼實現。
- 代碼中存在很多不少
@deprecated
、TODO
和臨時修改。 - 初始化、加載、啟動等邏輯比較復雜。
本篇將竭盡所能,為各位介紹其流程和內部實現,如果存在一些地方存在紕漏,還請指出。文章篇幅較長,需耐心閱讀,閱讀時可結合圖片源碼,同時歡迎收藏,或選擇感興趣點閱讀,下面主要涉及:
- 二、ClassLoader基礎知識。
- 三、Replugin項目原理和結構分析。
- 四、Replugin的ClassLoader。
- 五、Replugin的相關類介紹。
- 六、Replugin的初始化。
- 七、Replugin啟動Activity。
二、ClassLoader基礎知識
既然Replugin選擇Hook住ClassLoader,那先簡單介紹下ClassLoader的基本知識吧,如熟悉者請略過。
ClassLoader又叫類加載器,是專門處理類加載,一個APP可以存在多個ClassLoader,它使用的是雙親代理模型,如下圖所示,創建一個ClassLoader,需要使用一個已有的ClassLoader對象,作為新建的實例的ParentLoader。
這樣的條件下,一個App中所有的ClassLoader都聯系了起來。當加載類時,如果當前ClassLoader未加載此類,就查詢ParentLoader是否加載過,一直往上查找,如果存在就返回,如果都沒有,就執行該Loader去執行加載工作。這樣避免了類重復加載的浪費。其中常見的Loader有:
- BootClassLoader 是系統啟動時創建的,一般不需要用到。
- PathClassLoader 是應用啟動時創建的,只能加載內部dex。
- DexClassLoader 可以加載外部的dex。
RePlugin中存在兩個主要ClassLoaer:
1、
RePluginClassLoader
: 宿主App中的Loader,繼承PathClassLoader,也是唯一Hook住系統的Loader。2、
PluginDexClassLoader
: 加載插件的Loader,繼承DexClassLoader。用來做一些“更高級”的特性。
三、Replugin項目原理和結構分析
1、基礎原理
簡單來說,其核心是hook住了 ClassLoader
,在Activity啟動前:
- 記錄下目標頁
ActivityA
,替換成已自動注冊在 AndroidManifest 中的坑位ActivityNS
。 - 在
ClassLoader
中攔截ActivityNS
的創建,創建出ActivityA
返回。 - 返回的
ActivityA
占用著ActivityNS
這個坑位,坑位由Gradle編譯時自動生成在AndroidManifest中。
在編譯時,replugin-replugin-library
腳本,會替換代碼中的基礎類和方法。如下圖【官方原理圖】所示,替換的基類里會做一些初始化,所以這一塊稍微有點入侵性。此外,replugin-host-library
會生成AndroidManifest、配置相關信息、打包等,也由Gradle插件自動完成。
打包獨立APK,或者打包為插件,可單可插,這就是RePlugin。
2、項目結構
RePlugin整個項目結構,目前分為四個module,其中又分為兩個gradle插件module,兩個library的java module,詳細如開頭【圖一 Replugin項目結構】,本文主要分析library相關,如果對gradle插件感興趣的,可以查看結尾其他推薦。
2.1、replugin-host-gradle :
對應com.qihoo360.replugin:replugin-host-gradle:xxx
依賴,主要負責在主程序的編譯期中生產各類文件:
根據用戶的配置文件,生成HostBuildConfig類,方便插件框架讀取并自定義其屬性,如:進程數、各類型占位坑的數量、是否使用AppCompat庫、Host版本、pulgins-builtin.json文件名、內置插件文件名等。
自動生成帶 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插件,由插件負責引入,主要負責在插件的編譯期中:配置插件打包相關信息;動態替換插件工程中的繼承基類,如下,修改Activity的繼承、Provider的重定向等。
/* LoaderActivity 替換規則 */
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反射”來調用主程序中RePlugin Host Library的相關接口,并提供“雙向通信”的能力,以及各種基類Activity等
其中的RePlugin
、RePluginInternal
、PluginServiceClient
都是反射宿主App :replugin-host-library
中的 RePlugin
、 RePluginInternal
、PluginServiceClient
類方法。
四、Replugin的ClassLoader。
這里主要介紹,宿主和插件使用的ClassLoader,以及它們的創建和Hook住時機。這是RePlugin唯一的Hook點,而其中插件ClassLoader和宿主ClassLoader是相互關系的,如下圖。
1、宿主的ClassLoader
RePluginClassLoader
,宿主的ClassLoader,繼承 PathClassLoader
,構造方法使用原ClassLoader,和原ClassLoader的Parent生成。其中ParentLoader是因為雙親代理模型,創建ClassLoader所需,而原Loader用于保留在后期使用,如下圖。
如下兩圖,RePluginClassLoader
在創建時,淺拷貝原Loader的資源到 RePluginClassLoader
中,用于欺騙系統還處于原Loader,并且從原Loader中反射出常用方法,用于重載方法中使用。
宿主Loader中,主要是重載了 loadClass
,其中從 PMF
(RePlugin中公開接口類)中查找class,如果存在即返回插件class,如果不存在就從原Loader中加載。從而實現了對加載類的攔截。
這里的 PMF
在加載class時,其實用的是下面【2、插件的ClassLoader 】:PluginDexClassLoader
,這個后面流程會講到。
2、插件的ClassLoader
PluginDexClassLoader
,繼承DexClassLoader,構造時持有了宿主的ClassLoader,從宿主ClassLoader中反射獲取loadClass方法,當自己的loadClass方法找不到類時,從宿主Loader中加載。
3、創建和Hook
創建:上面1、2中兩個Loader,是宿主在初始化時創建的,初始化時可以選擇配置RePluginCallbacks
,callback中提供方法默認創建Loader,你也可以實現自定義的ClassLoader,但是需要繼承以上的Loader,如下圖。
//初始化方式創建
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);
Hook:初始化時,PatchClassLoaderUtils
會在Application的attachBaseContext()
中,通過patch(application)
Hook住宿主的ClassLoader,patch內部如下圖。
五、Replugin的相關類介紹
提前介紹一些功能類,后面就不做詳細介紹。
** 1、RePlugin** :RePlugin的對外入口類,提供install、uninstall、preload、startActivity、fetchPackageInfo、fetchComponentList,fetchClassLoader等等統一的方法入口,用戶操作的主要是它。
2、RePlugin.App:RePlugin中的內部類,針對Application的入口類,所有針對插件Application的調用應從此類開始和初始化,想象成插件的Application吧。
3、PmBase:RePlugin常用mPluginMgr變量表示,可以看作插件管理者。初始化插件、加載插件等一般都是從它開始。
4、PluginContainers:插件容器管理中心。
5、PmLocalImpl:各種本地接口實現,如startActivity,getActivityInfo,loadPluginActivity等。
6、PmInternalImpl:類似Activity的接口實現,內部實現了真正startActivity的邏輯、還有插件Activity生命周期的接口。
六、Replugin的初始化
那就是從 Application 初始化開始看起,枯燥的流程就要開始了,忍住兄弟,我們能贏。首先我們先看下面這流程圖,大致了解啟動流程:
1、attachBaseContext
首先是從 Application 的 attachBaseContext
初始化開始。如下圖,這里主要是配置 RePluginConfig
和 RePluginCallbacks
,然后根據 Config 去初始化插件。值得注意的是,RePluginConfig
中的 RePluginCallbacks
提供了默認方法創建 RePlugin 的 ClassLoader,還記得上面的介紹嗎?
2、插件App.attachBaseContext
繼續上面的流程,進入RePlugin.App.attachBaseContext(this, c)
,如下圖,這里主要是初始化插件相關的進程、配置信息、插件的主框架和接口、根據默認路徑、加載默認插件等。插件的初始化從這里開始,其中主要為 PMF.init()
和 PMF.callAttach()
。
3、主程序接口 PMF.init()/PMF.callAttach()
先進入到 PMF.init()
,如下圖,這里主要實例化了 PmBase
類,并初始化了它,創建了內部使用的 PmLocalImpl
和 PmInternalImp
接口 ,同時Hook住主程序的 ClassLoader,替換為 RePluginClassLoader
,所以接下來的流程,主要是在 PmBase
。
PmBase
,按照項目中的變量名 mPluginMgr
,可以理解為插件的管理者,它管理內部直接或間接的,管理著坑位分配、ClassLoader、插件、進程、啟動\停止頁面的接口等,如下圖。
PmBase
的初始化,也就是插件的初始化,這里會啟動各類進程,初始化各種默認插件集合,為后續加載做準備。其中默認插件和配置文件的位置,一般默認是在 assert 的 plugins-builtin.json
和 "plugins" 文件夾下。
接著PMF.callAttach()
其實就是 PmBase.callAttach()
,如下圖這里開始真正加載插件,初始化插件的 PluginDexClassLoader
、加載插件、初始化插件環境和接口。其中在執行 p.load()
的時候,會通過 Plugind.callAppLocked()
創建插件的 Application,并初始化。
以上是在主APP的初始化,深入 PmBase
中,Plugin.load()
在加載時,會調用PluginDexClassLoader
, 通過類名加載 Entry
類,然后反射出create
方法,執行插件的初始化。其中 Entry
位于Plugin-lib庫中。這里初始化就去到了插件中了,插件中初始化時,會通過反射的到宿主host類的方法。
4、Application的onCreate
這里主要是切換handler到主線程,注冊各種廣播接收監聽,如增加插件、卸載插件、更新插件,可以看出這里設計很多內部進程通信的。
七、Replugin啟動Activity
這里僅描述了Activity啟動的其中一個流程,也是簡化版的,實際代碼邏輯復雜多了,但是萬變不離其宗,這里幫你梳理流程,描述一些關鍵的點,讓你快速理解Activity的啟動流程。
1、startActivity
從上面的流程圖我們知道,啟動插件Activity可以從RePlugin.startActivity
開始,startActivity經歷了 Factory
、 PmLocalImpl
,其實大部分啟動的邏輯其實主要在 PmInternalImpl
中。
具體流程如下圖,這里簡化了實際代碼,關鍵在于 loadPluginActivity
。這里獲取了插件對應的坑位,然后保存了目標Activity的信息,通過系統啟動坑位。
因為已經Hook住了ClassLoader,在 loadClass
時再加載出目標Activity,這樣坑位中承載的,便是繞過系統打開的目標Activity。下面我們進入 loadPluginActivity
。
2、loadPluginActivity
loadPluginActivity
其實是 PmBase
中的 PmLocalImpl
內部方法。如下圖,這里主要是根據獲取到 ActivityInfo
,然后根據坑位去為目標Activity分配坑位。
其中 getActivityInfo
是通過插件名稱,獲得插件對象 Plugin
, Plugin
可能是初始化中已加載的,如果未加載就加載返回,然后根據 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中,如下圖,便是坑位分配的邏輯:
- 如果存在未啟動的坑位,就使用它。
- 如果沒有就找最老的:已經被釋放的、或者時間最老的。
- 如果還不行,那么擠掉最老的一個。
4、PulginActivity
上面的流程總結,是替換目標Activity,加載插件,分配坑位,啟動目標坑位,攔截ClassLoader的loadClass去加載返回目標Activity。
這個時候啟動的Activity還不完整,從模塊框架中我們知道,在編譯時,RePlugin會把繼承的Activity替換為如 PluginActivity
(當前還有AppComPluginActivity等)。這時候加載啟動的目標Activity,其實是繼承了 PluginActivity
。
如下圖, PluginActivity
重載Activity中的一些方法,實現了Activity的補全和自定義操作,如坑位管理,啟動宿主Activity等。
至此,一個插件Activity就啟動起來了,頭暈目眩了沒?為了實現 One Hook 這個信念,RePlugin 實現了復雜的流程,從代碼中可以看出,這些年作者們從中走的的各種坑、各種妥協與堅持、復雜的技術積累、已經經歷了多年的嚴酷考驗。
不知道有多少人能完整看到這,碼字不易,如有疏漏還是多多包涵,由于篇(tou)幅(lan)原因,關于Service等的就不多做敘述了,不知道本文對你是否能有些幫助,歡迎留言討論。
最后說“一”句
為什么要去了解一個庫實現原理呢?學習框架的架構思想?這是一個原因。但是歸根結底,是幫助你在使用庫的過程中,能靠自己解決各種問題。程序員的日常一般都忙于各種工作,各種技術群中的大佬們,大部分時候,沒辦法一一解答你的各種咨詢,所以使用它、了解它、多嘗試靠自己去探索突破吧。