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

  RePlugin,360開源的全面插件化框架,按照官網說的,其目的是「儘量多的讓模塊變成插件」,並在很穩定的前提下,儘量像開發普通App那樣靈活。那麼下面就讓咱們一塊兒深刻♂瞭解它吧。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )java

1、介紹

  RePlugin對比其餘插件化,它的強大和特點,在於它只Hook住了ClassLoader。One Hook這個堅持,最大程度保證了穩定性、兼容性和可維護性,詳見《全面插件化——RePlugin的使命》。固然,One Hook也極大的提升了實現複雜程度性,其中主要體如今:android

  • 增長了Gradle插件腳本,實現開發中自動代碼修改與生成。
  • 分割了插件庫和宿主庫的代碼實現。
  • 代碼中存在不少很多@deprecatedTODO和臨時修改。
  • 初始化、加載、啓動等邏輯比較複雜。

圖一 Replugin項目結構
圖一 Replugin項目結構

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

  • 2、ClassLoader基礎知識。
  • 3、Replugin項目原理和結構分析。
  • 4、Replugin的ClassLoader。
  • 5、Replugin的相關類介紹。
  • 6、Replugin的初始化。
  • 7、Replugin啓動Activity。

此處應有圖
此處應有圖

2、ClassLoader基礎知識

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

  ClassLoader又叫類加載器,是專門處理類加載,一個APP能夠存在多個ClassLoader,它使用的是雙親代理模型,以下圖所示,建立一個ClassLoader,須要使用一個已有的ClassLoader對象,做爲新建的實例的ParentLoader。github

抽象基類ClassLoader
抽象基類ClassLoader

  這樣的條件下,一個App中全部的ClassLoader都聯繫了起來。當加載類時,若是當前ClassLoader未加載此類,就查詢ParentLoader是否加載過,一直往上查找,若是存在就返回,若是都沒有,就執行該Loader去執行加載工做。這樣避免了類重複加載的浪費。其中常見的Loader有:json

  • BootClassLoader 是系統啓動時建立的,通常不須要用到。
  • PathClassLoader 是應用啓動時建立的,只能加載內部dex。
  • DexClassLoader 能夠加載外部的dex。

RePlugin中存在兩個主要ClassLoaer:緩存

  • 一、RePluginClassLoader 宿主App中的Loader,繼承PathClassLoader,也是惟一Hook住系統的Loader。bash

  • 二、PluginDexClassLoader 加載插件的Loader,繼承DexClassLoader。用來作一些「更高級」的特性。架構

3、Replugin項目原理和結構分析

一、基礎原理

  簡單來講,其核心是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插件感興趣的,能夠查看結尾其餘推薦。

2.一、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.二、replugin-host-library:

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

2.三、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.四、replugin-plugin-library:

  對應com.qihoo360.replugin:replugin-plugin-lib:xxx依賴,是一個Java工程,由插件端負責引入,主要提供經過「Java反射」來調用主程序中RePlugin Host Library的相關接口,並提供「雙向通訊」的能力,以及各類基類Activity等
  
  其中的RePluginRePluginInternalPluginServiceClient都是反射宿主App :replugin-host-library 中的 RePluginRePluginInternalPluginServiceClient 類方法。

4、Replugin的ClassLoader。

  這裏主要介紹,宿主和插件使用的ClassLoader,以及它們的建立和Hook住時機。這是RePlugin惟一的Hook點,而其中插件ClassLoader和宿主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,這個後面流程會講到。

二、插件的ClassLoader

  PluginDexClassLoader,繼承DexClassLoader,構造時持有了宿主的ClassLoader,從宿主ClassLoader中反射獲取loadClass方法,當本身的loadClass方法找不到類時,從宿主Loader中加載。

  

三、建立和Hook

  建立:上面一、2中兩個Loader,是宿主在初始化時建立的,初始化時能夠選擇配置RePluginCallbacks,callback中提供方法默認建立Loader,你也能夠實現自定義的ClassLoader,可是須要繼承以上的Loader,以下圖

//初始化方式建立
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);複製代碼

RePluginCallbacks
RePluginCallbacks

  Hook:初始化時,PatchClassLoaderUtils會在Application的attachBaseContext()中,經過patch(application)Hook住宿主的ClassLoader,patch內部以下圖

hook ClassLoader
hook ClassLoader

5、Replugin的相關類介紹

  提早介紹一些功能類,後面就不作詳細介紹。

一、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生命週期的接口。
  
-   

準備好了嗎,騷年
準備好了嗎,騷年

6、Replugin的初始化

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

將就的看吧
將就的看吧

一、attachBaseContext

  首先是從 Application 的 attachBaseContext 初始化開始。以下圖,這裏主要是配置 RePluginConfigRePluginCallbacks ,而後根據 Config 去初始化插件。值得注意的是,RePluginConfig 中的 RePluginCallbacks 提供了默認方法建立 RePlugin 的 ClassLoader,還記得上面的介紹嗎?

看圖看圖
看圖看圖

二、插件App.attachBaseContext

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

繼續看圖看圖
繼續看圖看圖

三、主程序接口 PMF.init()/PMF.callAttach()

  先進入到 PMF.init() ,以下圖,這裏主要實例化了 PmBase 類,並初始化了它,建立了內部使用的 PmLocalImplPmInternalImp 接口 ,同時Hook住主程序的 ClassLoader,替換爲 RePluginClassLoader,因此接下來的流程,主要是在 PmBase

PMF.init(),看圖吧
PMF.init(),看圖吧

  PmBase,按照項目中的變量名 mPluginMgr,能夠理解爲插件的管理者,它管理內部直接或間接的,管理着坑位分配、ClassLoader、插件、進程、啓動\中止頁面的接口等,以下圖。

PmBase建立,仍是看圖
PmBase建立,仍是看圖

  PmBase 的初始化,也就是插件的初始化,這裏會啓動各種進程,初始化各類默認插件集合,爲後續加載作準備。其中默認插件和配置文件的位置,通常默認是在 assert 的 plugins-builtin.json 和 "plugins" 文件夾下。

PmBase.init() 看圖看圖
PmBase.init() 看圖看圖

  接着PMF.callAttach() 其實就是 PmBase.callAttach()以下圖這裏開始真正加載插件,初始化插件的 PluginDexClassLoader 、加載插件、初始化插件環境和接口。其中在執行 p.load() 的時候,會經過 Plugind.callAppLocked() 建立插件的 Application,並初始化。

PMF.callAttach() 看圖唄
PMF.callAttach() 看圖唄

  以上是在主APP的初始化,深刻 PmBase 中,Plugin.load()在加載時,會調用PluginDexClassLoader, 經過類名加載 Entry 類,而後反射出create方法,執行插件的初始化。其中 Entry 位於Plugin-lib庫中。這裏初始化就去到了插件中了,插件中初始化時,會經過反射的到宿主host類的方法。

四、Application的onCreate

  這裏主要是切換handler到主線程,註冊各類廣播接收監聽,如增長插件、卸載插件、更新插件,能夠看出這裏設計不少內部進程通訊的。


   
-      

7、Replugin啓動Activity

  這裏僅描述了Activity啓動的其中一個流程,也是簡化版的,實際代碼邏輯複雜多了,可是萬變不離其宗,這裏幫你梳理流程,描述一些關鍵的點,讓你快速理解Activity的啓動流程。

再將就下吧,看圖
再將就下吧,看圖

一、startActivity

  從上面的流程圖咱們知道,啓動插件Activity能夠從RePlugin.startActivity開始,startActivity經歷了 FactoryPmLocalImpl ,其實大部分啓動的邏輯其實主要在 PmInternalImpl 中。

  具體流程以下圖,這裏簡化了實際代碼,關鍵在於 loadPluginActivity。這裏獲取了插件對應的坑位,而後保存了目標Activity的信息,經過系統啓動坑位。

  由於已經Hook住了ClassLoader,在 loadClass 時再加載出目標Activity,這樣坑位中承載的,即是繞過系統打開的目標Activity。下面咱們進入 loadPluginActivity

說了看圖
說了看圖

二、loadPluginActivity

  loadPluginActivity 實際上是 PmBase 中的 PmLocalImpl 內部方法。以下圖,這裏主要是根據獲取到 ActivityInfo,而後根據坑位去爲目標Activity分配坑位。

  其中 getActivityInfo 是經過插件名稱,得到插件對象 PluginPlugin多是初始化中已加載的,若是未加載就加載返回,而後根據 Plugin 中緩存的坑位信息,返回 ActivityInfo

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

看圖沒?
看圖沒?

二、allocActivityContainer

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

  在 allocActivityContainer 中, 主要邏輯是bindActivity ,以下圖,bindActivity 去找到目標Activity匹配的容器,而後加載目標Activity判斷是否存在,並創建映射,返回容器。而後分配的邏輯,在 PluginContainers.alloc 中。

看我大圖
看我大圖

三、PluginContainers.alloc

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

  • 若是存在未啓動的坑位,就使用它。
  • 若是沒有就找最老的:已經被釋放的、或者時間最老的。
  • 若是還不行,那麼擠掉最老的一個。

看圖說話
看圖說話

四、PulginActivity

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

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

  以下圖PluginActivity 重載Activity中的一些方法,實現了Activity的補全和自定義操做,如坑位管理,啓動宿主Activity等。

  至此,一個插件Activity就啓動起來了,頭暈目眩了沒?爲了實現 One Hook 這個信念,RePlugin 實現了複雜的流程,從代碼中能夠看出,這些年做者們從中走的的各類坑、各類妥協與堅持、複雜的技術積累、已經經歷了多年的嚴酷考驗。

  不知道有多少人能完整看到這,碼字不易,若有疏漏仍是多多包涵,因爲篇(tou)幅(lan)緣由,關於Service等的就很少作敘述了,不知道本文對你是否能有些幫助,歡迎留言討論。

最後說「一」句

  爲何要去了解一個庫實現原理呢?學習框架的架構思想?這是一個緣由。可是歸根結底,是幫助你在使用庫的過程當中,能靠本身解決各類問題。程序員的平常通常都忙於各類工做,各類技術羣中的大佬們,大部分時候,沒辦法一一解答你的各類諮詢,因此使用它、瞭解它、多嘗試靠本身去探索突破吧。

其餘推薦

注意到了嗎?最後的老是我!
注意到了嗎?最後的老是我!
相關文章
相關標籤/搜索