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


轉自

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

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

一、介紹

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

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

 

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

 

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

  • 二、ClassLoader基礎知識。
  • 三、Replugin項目原理和結構分析。
  • 四、Replugin的ClassLoader。
  • 五、Replugin的相關類介紹。
  • 六、Replugin的初始化。
  • 七、Replugin啟動Activity。

 

此處應有圖此處應有圖

 

二、ClassLoader基礎知識

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

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

 

抽象基類ClassLoader抽象基類ClassLoader

 

  這樣的條件下,一個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等
  
  其中的RePluginRePluginInternalPluginServiceClient都是反射宿主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);

 

RePluginCallbacksRePluginCallbacks

 

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

 

hook ClassLoaderhook ClassLoader

 

五、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 。

 

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類的方法。

 

 

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等的就不多做敘述了,不知道本文對你是否能有些幫助,歡迎留言討論。

最后說“一”句

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

其他推薦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM