RePlugin左电,360開源的全面插件化框架,按照官網(wǎng)說的页响,其目的是“盡可能多的讓模塊變成插件”篓足,并在很穩(wěn)定的前提下,盡可能像開發(fā)普通App那樣靈活闰蚕。那么下面就讓我們一起深入♂了解它吧栈拖。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )
一、介紹
RePlugin對比其他插件化没陡,它的強(qiáng)大和特色涩哟,在于它只Hook住了ClassLoader。One Hook這個(gè)堅(jiān)持诗鸭,最大程度保證了穩(wěn)定性染簇、兼容性和可維護(hù)性,詳見《全面插件化——RePlugin的使命》强岸。當(dāng)然锻弓,One Hook也極大的提高了實(shí)現(xiàn)復(fù)雜程度性,其中主要體現(xiàn)在:
- 增加了Gradle插件腳本蝌箍,實(shí)現(xiàn)開發(fā)中自動代碼修改與生成青灼。
- 分割了插件庫和宿主庫的代碼實(shí)現(xiàn)。
- 代碼中存在很多不少
@deprecated
妓盲、TODO
和臨時(shí)修改杂拨。 - 初始化、加載悯衬、啟動等邏輯比較復(fù)雜弹沽。
本篇將竭盡所能,為各位介紹其流程和內(nèi)部實(shí)現(xiàn)筋粗,如果存在一些地方存在紕漏策橘,還請指出。文章篇幅較長娜亿,需耐心閱讀丽已,閱讀時(shí)可結(jié)合圖片源碼,同時(shí)歡迎收藏买决,或選擇感興趣點(diǎn)閱讀沛婴,下面主要涉及:
- 二吼畏、ClassLoader基礎(chǔ)知識。
- 三嘁灯、Replugin項(xiàng)目原理和結(jié)構(gòu)分析泻蚊。
- 四、Replugin的ClassLoader旁仿。
- 五藕夫、Replugin的相關(guān)類介紹。
- 六枯冈、Replugin的初始化毅贮。
- 七、Replugin啟動Activity尘奏。
二滩褥、ClassLoader基礎(chǔ)知識
既然Replugin選擇Hook住ClassLoader,那先簡單介紹下ClassLoader的基本知識吧炫加,如熟悉者請略過瑰煎。
ClassLoader又叫類加載器,是專門處理類加載俗孝,一個(gè)APP可以存在多個(gè)ClassLoader酒甸,它使用的是雙親代理模型,如下圖所示赋铝,創(chuàng)建一個(gè)ClassLoader插勤,需要使用一個(gè)已有的ClassLoader對象,作為新建的實(shí)例的ParentLoader革骨。
這樣的條件下农尖,一個(gè)App中所有的ClassLoader都聯(lián)系了起來。當(dāng)加載類時(shí)良哲,如果當(dāng)前ClassLoader未加載此類盛卡,就查詢ParentLoader是否加載過,一直往上查找筑凫,如果存在就返回滑沧,如果都沒有,就執(zhí)行該Loader去執(zhí)行加載工作巍实。這樣避免了類重復(fù)加載的浪費(fèi)滓技。其中常見的Loader有:
- BootClassLoader 是系統(tǒng)啟動時(shí)創(chuàng)建的,一般不需要用到蔫浆。
- PathClassLoader 是應(yīng)用啟動時(shí)創(chuàng)建的殖属,只能加載內(nèi)部dex姐叁。
- DexClassLoader 可以加載外部的dex瓦盛。
RePlugin中存在兩個(gè)主要ClassLoaer:
1洗显、
RePluginClassLoader
: 宿主App中的Loader,繼承PathClassLoader原环,也是唯一Hook住系統(tǒng)的Loader挠唆。2、
PluginDexClassLoader
: 加載插件的Loader嘱吗,繼承DexClassLoader玄组。用來做一些“更高級”的特性。
三谒麦、Replugin項(xiàng)目原理和結(jié)構(gòu)分析
1俄讹、基礎(chǔ)原理
簡單來說,其核心是hook住了 ClassLoader
绕德,在Activity啟動前:
- 記錄下目標(biāo)頁
ActivityA
患膛,替換成已自動注冊在 AndroidManifest 中的坑位ActivityNS
。 - 在
ClassLoader
中攔截ActivityNS
的創(chuàng)建耻蛇,創(chuàng)建出ActivityA
返回踪蹬。 - 返回的
ActivityA
占用著ActivityNS
這個(gè)坑位,坑位由Gradle編譯時(shí)自動生成在AndroidManifest中臣咖。
在編譯時(shí)跃捣,replugin-replugin-library
腳本,會替換代碼中的基礎(chǔ)類和方法夺蛇。如下圖【官方原理圖】所示疚漆,替換的基類里會做一些初始化,所以這一塊稍微有點(diǎn)入侵性蚊惯。此外愿卸,replugin-host-library
會生成AndroidManifest、配置相關(guān)信息截型、打包等趴荸,也由Gradle插件自動完成。
打包獨(dú)立APK宦焦,或者打包為插件发钝,可單可插,這就是RePlugin波闹。
2酝豪、項(xiàng)目結(jié)構(gòu)
RePlugin整個(gè)項(xiàng)目結(jié)構(gòu),目前分為四個(gè)module精堕,其中又分為兩個(gè)gradle插件module孵淘,兩個(gè)library的java module,詳細(xì)如開頭【圖一 Replugin項(xiàng)目結(jié)構(gòu)】歹篓,本文主要分析library相關(guān)瘫证,如果對gradle插件感興趣的揉阎,可以查看結(jié)尾其他推薦。
2.1背捌、replugin-host-gradle :
對應(yīng)com.qihoo360.replugin:replugin-host-gradle:xxx
依賴毙籽,主要負(fù)責(zé)在主程序的編譯期中生產(chǎn)各類文件:
根據(jù)用戶的配置文件,生成HostBuildConfig類毡庆,方便插件框架讀取并自定義其屬性坑赡,如:進(jìn)程數(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:
對應(yīng)com.qihoo360.replugin:replugin-host-lib:xxx
依賴仰迁,是一個(gè)Java工程,由主程序負(fù)責(zé)引入顽分,是RePlugin的核心工程徐许,負(fù)責(zé)初始化、加載卒蘸、啟動雌隅、管理插件等。
2.3缸沃、replugin-plugin-gradle:
對應(yīng)com.qihoo360.replugin:replugin-plugin-gradle:xxx
恰起,是一個(gè)Gradle插件,由插件負(fù)責(zé)引入趾牧,主要負(fù)責(zé)在插件的編譯期中:配置插件打包相關(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:
對應(yīng)com.qihoo360.replugin:replugin-plugin-lib:xxx
依賴诗轻,是一個(gè)Java工程贮聂,由插件端負(fù)責(zé)引入,主要提供通過“Java反射”來調(diào)用主程序中RePlugin Host Library的相關(guān)接口认臊,并提供“雙向通信”的能力圃庭,以及各種基類Activity等
其中的RePlugin
、RePluginInternal
、PluginServiceClient
都是反射宿主App :replugin-host-library
中的 RePlugin
剧腻、 RePluginInternal
斟薇、PluginServiceClient
類方法。
四恕酸、Replugin的ClassLoader。
這里主要介紹胯陋,宿主和插件使用的ClassLoader蕊温,以及它們的創(chuàng)建和Hook住時(shí)機(jī)。這是RePlugin唯一的Hook點(diǎn)遏乔,而其中插件ClassLoader和宿主ClassLoader是相互關(guān)系的义矛,如下圖。
1盟萨、宿主的ClassLoader
RePluginClassLoader
凉翻,宿主的ClassLoader,繼承 PathClassLoader
捻激,構(gòu)造方法使用原ClassLoader制轰,和原ClassLoader的Parent生成。其中ParentLoader是因?yàn)?strong>雙親代理模型胞谭,創(chuàng)建ClassLoader所需垃杖,而原Loader用于保留在后期使用,如下圖丈屹。
如下兩圖调俘,RePluginClassLoader
在創(chuàng)建時(shí),淺拷貝原Loader的資源到 RePluginClassLoader
中旺垒,用于欺騙系統(tǒng)還處于原Loader彩库,并且從原Loader中反射出常用方法,用于重載方法中使用先蒋。
宿主Loader中骇钦,主要是重載了 loadClass
,其中從 PMF
(RePlugin中公開接口類)中查找class竞漾,如果存在即返回插件class司忱,如果不存在就從原Loader中加載。從而實(shí)現(xiàn)了對加載類的攔截畴蹭。
這里的 PMF
在加載class時(shí)坦仍,其實(shí)用的是下面【2、插件的ClassLoader 】:PluginDexClassLoader
叨襟,這個(gè)后面流程會講到繁扎。
2、插件的ClassLoader
PluginDexClassLoader
,繼承DexClassLoader梳玫,構(gòu)造時(shí)持有了宿主的ClassLoader爹梁,從宿主ClassLoader中反射獲取loadClass方法,當(dāng)自己的loadClass方法找不到類時(shí)提澎,從宿主Loader中加載姚垃。
3、創(chuàng)建和Hook
創(chuàng)建:上面1盼忌、2中兩個(gè)Loader积糯,是宿主在初始化時(shí)創(chuàng)建的,初始化時(shí)可以選擇配置RePluginCallbacks
谦纱,callback中提供方法默認(rèn)創(chuàng)建Loader看成,你也可以實(shí)現(xiàn)自定義的ClassLoader,但是需要繼承以上的Loader跨嘉,如下圖川慌。
//初始化方式創(chuàng)建
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);
Hook:初始化時(shí),PatchClassLoaderUtils
會在Application的attachBaseContext()
中祠乃,通過patch(application)
Hook住宿主的ClassLoader梦重,patch內(nèi)部如下圖。
五亮瓷、Replugin的相關(guān)類介紹
提前介紹一些功能類忍饰,后面就不做詳細(xì)介紹。
** 1寺庄、RePlugin** :RePlugin的對外入口類艾蓝,提供install、uninstall斗塘、preload赢织、startActivity、fetchPackageInfo馍盟、fetchComponentList于置,fetchClassLoader等等統(tǒng)一的方法入口,用戶操作的主要是它贞岭。
2八毯、RePlugin.App:RePlugin中的內(nèi)部類,針對Application的入口類瞄桨,所有針對插件Application的調(diào)用應(yīng)從此類開始和初始化话速,想象成插件的Application吧。
3芯侥、PmBase:RePlugin常用mPluginMgr變量表示泊交,可以看作插件管理者乳讥。初始化插件、加載插件等一般都是從它開始廓俭。
4云石、PluginContainers:插件容器管理中心。
5研乒、PmLocalImpl:各種本地接口實(shí)現(xiàn)汹忠,如startActivity,getActivityInfo雹熬,loadPluginActivity等宽菜。
6、PmInternalImpl:類似Activity的接口實(shí)現(xiàn)橄唬,內(nèi)部實(shí)現(xiàn)了真正startActivity的邏輯、還有插件Activity生命周期的接口参歹。
六、Replugin的初始化
那就是從 Application 初始化開始看起犬庇,枯燥的流程就要開始了僧界,忍住兄弟,我們能贏臭挽。首先我們先看下面這流程圖捂襟,大致了解啟動流程:
1、attachBaseContext
首先是從 Application 的 attachBaseContext
初始化開始欢峰。如下圖葬荷,這里主要是配置 RePluginConfig
和 RePluginCallbacks
,然后根據(jù) Config 去初始化插件纽帖。值得注意的是宠漩,RePluginConfig
中的 RePluginCallbacks
提供了默認(rèn)方法創(chuàng)建 RePlugin 的 ClassLoader,還記得上面的介紹嗎懊直?
2扒吁、插件App.attachBaseContext
繼續(xù)上面的流程,進(jìn)入RePlugin.App.attachBaseContext(this, c)
室囊,如下圖雕崩,這里主要是初始化插件相關(guān)的進(jìn)程、配置信息融撞、插件的主框架和接口盼铁、根據(jù)默認(rèn)路徑、加載默認(rèn)插件等尝偎。插件的初始化從這里開始捉貌,其中主要為 PMF.init()
和 PMF.callAttach()
。
3、主程序接口 PMF.init()/PMF.callAttach()
先進(jìn)入到 PMF.init()
趁窃,如下圖牧挣,這里主要實(shí)例化了 PmBase
類,并初始化了它醒陆,創(chuàng)建了內(nèi)部使用的 PmLocalImpl
和 PmInternalImp
接口 瀑构,同時(shí)Hook住主程序的 ClassLoader,替換為 RePluginClassLoader
刨摩,所以接下來的流程寺晌,主要是在 PmBase
。
PmBase
呻征,按照項(xiàng)目中的變量名 mPluginMgr
,可以理解為插件的管理者罢浇,它管理內(nèi)部直接或間接的陆赋,管理著坑位分配、ClassLoader嚷闭、插件攒岛、進(jìn)程、啟動\停止頁面的接口等胞锰,如下圖灾锯。
PmBase
的初始化嗅榕,也就是插件的初始化顺饮,這里會啟動各類進(jìn)程,初始化各種默認(rèn)插件集合凌那,為后續(xù)加載做準(zhǔn)備领突。其中默認(rèn)插件和配置文件的位置,一般默認(rèn)是在 assert 的 plugins-builtin.json
和 "plugins" 文件夾下案怯。
接著PMF.callAttach()
其實(shí)就是 PmBase.callAttach()
君旦,如下圖這里開始真正加載插件,初始化插件的 PluginDexClassLoader
嘲碱、加載插件金砍、初始化插件環(huán)境和接口。其中在執(zhí)行 p.load()
的時(shí)候麦锯,會通過 Plugind.callAppLocked()
創(chuàng)建插件的 Application恕稠,并初始化。
以上是在主APP的初始化扶欣,深入 PmBase
中鹅巍,Plugin.load()
在加載時(shí)千扶,會調(diào)用PluginDexClassLoader
, 通過類名加載 Entry
類骆捧,然后反射出create
方法澎羞,執(zhí)行插件的初始化。其中 Entry
位于Plugin-lib庫中敛苇。這里初始化就去到了插件中了妆绞,插件中初始化時(shí),會通過反射的到宿主host類的方法枫攀。
4括饶、Application的onCreate
這里主要是切換handler到主線程,注冊各種廣播接收監(jiān)聽来涨,如增加插件图焰、卸載插件、更新插件蹦掐,可以看出這里設(shè)計(jì)很多內(nèi)部進(jìn)程通信的技羔。
七、Replugin啟動Activity
這里僅描述了Activity啟動的其中一個(gè)流程笤闯,也是簡化版的堕阔,實(shí)際代碼邏輯復(fù)雜多了棍厂,但是萬變不離其宗颗味,這里幫你梳理流程,描述一些關(guān)鍵的點(diǎn)牺弹,讓你快速理解Activity的啟動流程浦马。
1张漂、startActivity
從上面的流程圖我們知道晶默,啟動插件Activity可以從RePlugin.startActivity
開始,startActivity經(jīng)歷了 Factory
航攒、 PmLocalImpl
磺陡,其實(shí)大部分啟動的邏輯其實(shí)主要在 PmInternalImpl
中。
具體流程如下圖漠畜,這里簡化了實(shí)際代碼币他,關(guān)鍵在于 loadPluginActivity
。這里獲取了插件對應(yīng)的坑位憔狞,然后保存了目標(biāo)Activity的信息蝴悉,通過系統(tǒng)啟動坑位。
因?yàn)橐呀?jīng)Hook住了ClassLoader瘾敢,在 loadClass
時(shí)再加載出目標(biāo)Activity拍冠,這樣坑位中承載的尿这,便是繞過系統(tǒng)打開的目標(biāo)Activity。下面我們進(jìn)入 loadPluginActivity
庆杜。
2射众、loadPluginActivity
loadPluginActivity
其實(shí)是 PmBase
中的 PmLocalImpl
內(nèi)部方法。如下圖欣福,這里主要是根據(jù)獲取到 ActivityInfo
责球,然后根據(jù)坑位去為目標(biāo)Activity分配坑位。
其中 getActivityInfo
是通過插件名稱拓劝,獲得插件對象 Plugin
雏逾, Plugin
可能是初始化中已加載的,如果未加載就加載返回郑临,然后根據(jù) Plugin
中緩存的坑位信息栖博,返回 ActivityInfo
。
下面進(jìn)入 allocActivityContainer
看坑位的分配厢洞,只有分配到坑位仇让,插件的Activity才可以啟動,這是一個(gè)IPC過程躺翻。
2、allocActivityContainer
allocActivityContainer
在類 PluginProcessPer
中公你,還記得我們在 PmBase.init()
時(shí)初始化過它么踊淳? 分配坑位也是RePlugin的核心之一。
在 allocActivityContainer
中陕靠, 主要邏輯是bindActivity
迂尝,如下圖,bindActivity
去找到目標(biāo)Activity匹配的容器剪芥,然后加載目標(biāo)Activity判斷是否存在垄开,并建立映射,返回容器税肪。然后分配的邏輯溉躲,在 PluginContainers.alloc
中。
3益兄、PluginContainers.alloc
alloc
/ alloc2
方法分配坑位锻梳,最后都是到了 allocLocked
方法中,其實(shí)RePlugin中偏塞,如下圖唱蒸,便是坑位分配的邏輯:
- 如果存在未啟動的坑位,就使用它灸叼。
- 如果沒有就找最老的:已經(jīng)被釋放的神汹、或者時(shí)間最老的庆捺。
- 如果還不行,那么擠掉最老的一個(gè)屁魏。
4滔以、PulginActivity
上面的流程總結(jié),是替換目標(biāo)Activity氓拼,加載插件你画,分配坑位,啟動目標(biāo)坑位桃漾,攔截ClassLoader的loadClass去加載返回目標(biāo)Activity坏匪。
這個(gè)時(shí)候啟動的Activity還不完整,從模塊框架中我們知道撬统,在編譯時(shí)适滓,RePlugin會把繼承的Activity替換為如 PluginActivity
(當(dāng)前還有AppComPluginActivity等)。這時(shí)候加載啟動的目標(biāo)Activity恋追,其實(shí)是繼承了 PluginActivity
凭迹。
如下圖, PluginActivity
重載Activity中的一些方法苦囱,實(shí)現(xiàn)了Activity的補(bǔ)全和自定義操作嗅绸,如坑位管理,啟動宿主Activity等撕彤。
至此鱼鸠,一個(gè)插件Activity就啟動起來了,頭暈?zāi)垦A藳]喉刘?為了實(shí)現(xiàn) One Hook 這個(gè)信念瞧柔,RePlugin 實(shí)現(xiàn)了復(fù)雜的流程漆弄,從代碼中可以看出睦裳,這些年作者們從中走的的各種坑、各種妥協(xié)與堅(jiān)持撼唾、復(fù)雜的技術(shù)積累廉邑、已經(jīng)經(jīng)歷了多年的嚴(yán)酷考驗(yàn)。
不知道有多少人能完整看到這倒谷,碼字不易蛛蒙,如有疏漏還是多多包涵,由于篇(tou)幅(lan)原因渤愁,關(guān)于Service等的就不多做敘述了牵祟,不知道本文對你是否能有些幫助,歡迎留言討論抖格。
最后說“一”句
為什么要去了解一個(gè)庫實(shí)現(xiàn)原理呢诺苹?學(xué)習(xí)框架的架構(gòu)思想咕晋?這是一個(gè)原因。但是歸根結(jié)底收奔,是幫助你在使用庫的過程中掌呜,能靠自己解決各種問題。程序員的日常一般都忙于各種工作坪哄,各種技術(shù)群中的大佬們质蕉,大部分時(shí)候,沒辦法一一解答你的各種咨詢翩肌,所以使用它模暗、了解它、多嘗試靠自己去探索突破吧念祭。