RePlugin,360開源的全面插件化框架,按照官網說的,其目的是「儘量多的讓模塊變成插件」,並在很穩定的前提下,儘量像開發普通App那樣靈活。那麼下面就讓咱們一塊兒深刻♂瞭解它吧。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )java
RePlugin對比其餘插件化,它的強大和特點,在於它只Hook住了ClassLoader。One Hook這個堅持,最大程度保證了穩定性、兼容性和可維護性,詳見《全面插件化——RePlugin的使命》。固然,One Hook也極大的提升了實現複雜程度性,其中主要體如今:android
@deprecated
、TODO
和臨時修改。本篇將竭盡所能,爲各位介紹其流程和內部實現,若是存在一些地方存在紕漏,還請指出。文章篇幅較長,需耐心閱讀,閱讀時可結合圖片源碼,同時歡迎收藏,或選擇感興趣點閱讀,下面主要涉及:git
既然Replugin選擇Hook住ClassLoader,那先簡單介紹下ClassLoader的基本知識吧,如熟悉者請略過。程序員
ClassLoader又叫類加載器,是專門處理類加載,一個APP能夠存在多個ClassLoader,它使用的是雙親代理模型,以下圖所示,建立一個ClassLoader,須要使用一個已有的ClassLoader對象,做爲新建的實例的ParentLoader。github
這樣的條件下,一個App中全部的ClassLoader都聯繫了起來。當加載類時,若是當前ClassLoader未加載此類,就查詢ParentLoader是否加載過,一直往上查找,若是存在就返回,若是都沒有,就執行該Loader去執行加載工做。這樣避免了類重複加載的浪費。其中常見的Loader有:json
RePlugin中存在兩個主要ClassLoaer:緩存
一、RePluginClassLoader
: 宿主App中的Loader,繼承PathClassLoader,也是惟一Hook住系統的Loader。bash
二、PluginDexClassLoader
: 加載插件的Loader,繼承DexClassLoader。用來作一些「更高級」的特性。架構
簡單來講,其核心是hook住了 ClassLoader
,在Activity啓動前:app
ActivityA
,替換成已自動註冊在 AndroidManifest 中的坑位 ActivityNS
。ClassLoader
中攔截ActivityNS
的建立,建立出ActivityA
返回。ActivityA
佔用着 ActivityNS
這個坑位,坑位由Gradle編譯時自動生成在AndroidManifest中。 在編譯時,replugin-replugin-library
腳本,會替換代碼中的基礎類和方法。以下圖【官方原理圖】所示,替換的基類裏會作一些初始化,因此這一塊稍微有點入侵性。此外,replugin-host-library
會生成AndroidManifest、配置相關信息、打包等,也由Gradle插件自動完成。
打包獨立APK,或者打包爲插件,可單可插,這就是RePlugin。
RePlugin整個項目結構,目前分爲四個module,其中又分爲兩個gradle插件module,兩個library的java module,詳細如開頭【圖一 Replugin項目結構】,本文主要分析library相關,若是對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"
/>複製代碼
對應com.qihoo360.replugin:replugin-host-lib:xxx
依賴,是一個Java工程,由主程序負責引入,是RePlugin的核心工程,負責初始化、加載、啓動、管理插件等。
對應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'
]複製代碼
對應com.qihoo360.replugin:replugin-plugin-lib:xxx
依賴,是一個Java工程,由插件端負責引入,主要提供經過「Java反射」來調用主程序中RePlugin Host Library的相關接口,並提供「雙向通訊」的能力,以及各類基類Activity等
其中的RePlugin
、RePluginInternal
、PluginServiceClient
都是反射宿主App :replugin-host-library
中的 RePlugin
、 RePluginInternal
、PluginServiceClient
類方法。
這裏主要介紹,宿主和插件使用的ClassLoader,以及它們的建立和Hook住時機。這是RePlugin惟一的Hook點,而其中插件ClassLoader和宿主ClassLoader是相互關係的,以下圖。
RePluginClassLoader
,宿主的ClassLoader,繼承 PathClassLoader
,構造方法使用原ClassLoader,和原ClassLoader的Parent生成。其中ParentLoader是由於雙親代理模型,建立ClassLoader所需,而原Loader用於保留在後期使用,以下圖。
以下兩圖,RePluginClassLoader
在建立時,淺拷貝原Loader的資源到 RePluginClassLoader
中,用於欺騙系統還處於原Loader,而且從原Loader中反射出經常使用方法,用於重載方法中使用。
宿主Loader中,主要是重載了 loadClass
,其中從 PMF
(RePlugin中公開接口類)中查找class,若是存在即返回插件class,若是不存在就從原Loader中加載。從而實現了對加載類的攔截。
這裏的 PMF
在加載class時,其實用的是下面【二、插件的ClassLoader 】:PluginDexClassLoader
,這個後面流程會講到。
PluginDexClassLoader
,繼承DexClassLoader,構造時持有了宿主的ClassLoader,從宿主ClassLoader中反射獲取loadClass方法,當本身的loadClass方法找不到類時,從宿主Loader中加載。
建立:上面一、2中兩個Loader,是宿主在初始化時建立的,初始化時能夠選擇配置RePluginCallbacks
,callback中提供方法默認建立Loader,你也能夠實現自定義的ClassLoader,可是須要繼承以上的Loader,以下圖。
//初始化方式建立
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);複製代碼
Hook:初始化時,PatchClassLoaderUtils
會在Application的attachBaseContext()
中,經過patch(application)
Hook住宿主的ClassLoader,patch內部以下圖。
提早介紹一些功能類,後面就不作詳細介紹。
一、RePlugin :RePlugin的對外入口類,提供install、uninstall、preload、startActivity、fetchPackageInfo、fetchComponentList,fetchClassLoader等等統一的方法入口,用戶操做的主要是它。
二、RePlugin.App:RePlugin中的內部類,針對Application的入口類,全部針對插件Application的調用應今後類開始和初始化,想象成插件的Application吧。
三、PmBase:RePlugin經常使用mPluginMgr變量表示,能夠看做插件管理者。初始化插件、加載插件等通常都是從它開始。
四、PluginContainers:插件容器管理中心。
五、PmLocalImpl:各類本地接口實現,如startActivity,getActivityInfo,loadPluginActivity等。
六、PmInternalImpl:相似Activity的接口實現,內部實現了真正startActivity的邏輯、還有插件Activity生命週期的接口。
-
那就是從 Application 初始化開始看起,枯燥的流程就要開始了,忍住兄弟,咱們能贏。首先咱們先看下面這流程圖,大體瞭解啓動流程:
首先是從 Application 的 attachBaseContext
初始化開始。以下圖,這裏主要是配置 RePluginConfig
和 RePluginCallbacks
,而後根據 Config 去初始化插件。值得注意的是,RePluginConfig
中的 RePluginCallbacks
提供了默認方法建立 RePlugin 的 ClassLoader,還記得上面的介紹嗎?
繼續上面的流程,進入RePlugin.App.attachBaseContext(this, c)
,以下圖,這裏主要是初始化插件相關的進程、配置信息、插件的主框架和接口、根據默認路徑、加載默認插件等。插件的初始化從這裏開始,其中主要爲 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類的方法。
這裏主要是切換handler到主線程,註冊各類廣播接收監聽,如增長插件、卸載插件、更新插件,能夠看出這裏設計不少內部進程通訊的。
這裏僅描述了Activity啓動的其中一個流程,也是簡化版的,實際代碼邏輯複雜多了,可是萬變不離其宗,這裏幫你梳理流程,描述一些關鍵的點,讓你快速理解Activity的啓動流程。
從上面的流程圖咱們知道,啓動插件Activity能夠從RePlugin.startActivity
開始,startActivity經歷了 Factory
、 PmLocalImpl
,其實大部分啓動的邏輯其實主要在 PmInternalImpl
中。
具體流程以下圖,這裏簡化了實際代碼,關鍵在於 loadPluginActivity
。這裏獲取了插件對應的坑位,而後保存了目標Activity的信息,經過系統啓動坑位。
由於已經Hook住了ClassLoader,在 loadClass
時再加載出目標Activity,這樣坑位中承載的,即是繞過系統打開的目標Activity。下面咱們進入 loadPluginActivity
。
loadPluginActivity
實際上是 PmBase
中的 PmLocalImpl
內部方法。以下圖,這裏主要是根據獲取到 ActivityInfo
,而後根據坑位去爲目標Activity分配坑位。
其中 getActivityInfo
是經過插件名稱,得到插件對象 Plugin
, Plugin
多是初始化中已加載的,若是未加載就加載返回,而後根據 Plugin
中緩存的坑位信息,返回 ActivityInfo
。
下面進入 allocActivityContainer
看坑位的分配,只有分配到坑位,插件的Activity才能夠啓動,這是一個IPC過程。
allocActivityContainer
在類 PluginProcessPer
中,還記得咱們在 PmBase.init()
時初始化過它麼? 分配坑位也是RePlugin的核心之一。
在 allocActivityContainer
中, 主要邏輯是bindActivity
,以下圖,bindActivity
去找到目標Activity匹配的容器,而後加載目標Activity判斷是否存在,並創建映射,返回容器。而後分配的邏輯,在 PluginContainers.alloc
中。
alloc
/ alloc2
方法分配坑位,最後都是到了 allocLocked
方法中,其實RePlugin中,以下圖,即是坑位分配的邏輯:
上面的流程總結,是替換目標Activity,加載插件,分配坑位,啓動目標坑位,攔截ClassLoader的loadClass去加載返回目標Activity。
這個時候啓動的Activity還不完整,從模塊框架中咱們知道,在編譯時,RePlugin會把繼承的Activity替換爲如 PluginActivity
(當前還有AppComPluginActivity等)。這時候加載啓動的目標Activity,實際上是繼承了 PluginActivity
。
以下圖, PluginActivity
重載Activity中的一些方法,實現了Activity的補全和自定義操做,如坑位管理,啓動宿主Activity等。
至此,一個插件Activity就啓動起來了,頭暈目眩了沒?爲了實現 One Hook 這個信念,RePlugin 實現了複雜的流程,從代碼中能夠看出,這些年做者們從中走的的各類坑、各類妥協與堅持、複雜的技術積累、已經經歷了多年的嚴酷考驗。
不知道有多少人能完整看到這,碼字不易,若有疏漏仍是多多包涵,因爲篇(tou)幅(lan)緣由,關於Service等的就很少作敘述了,不知道本文對你是否能有些幫助,歡迎留言討論。
爲何要去了解一個庫實現原理呢?學習框架的架構思想?這是一個緣由。可是歸根結底,是幫助你在使用庫的過程當中,能靠本身解決各類問題。程序員的平常通常都忙於各類工做,各類技術羣中的大佬們,大部分時候,沒辦法一一解答你的各類諮詢,因此使用它、瞭解它、多嘗試靠本身去探索突破吧。