小(Small)插件化框架那些事

需要研究技術:類動態加載,資源動態加載,組件動態注冊

如果需要更新插件,或者新增老插件的協議,還需要更新宿主插件聲明信息(bundle.json)。

要做到以上幾點,需要處理文章開頭說得三個技術問題

動態加載類

這部分主要是通過向BaseDexClassLoader中的DexPathList添加插件類實現的。有很多文章解釋這部分的原理,這里就不贅述了。

前面提到的插件so包可以像一個apk文件被解壓,但不能獨立運行。Small就是從這些插件so文件中加載Element,并添加到DexPathList中的。

動態加載資源

Android是通過AssetManager來加載資源的,默認情況下只會添加

/framework/base.apk" - Android基本資源

/data/app/*.apk" - 應用程序資源

編譯過程中aapt會為資源分配id,并存儲為PPTTNNNN的16進制整數

PP代表包信息

TT代表資源類型

NNNN定位具體的資源

Small是通過修改PP字段來避免模塊間的資源沖突的。在后面Small的編譯過程中會介紹這部分的邏輯。

動態注冊組件

Activty受Instrumentation監控,都需要通過Instrumentation#execStartActivity來啟動并激活聲明周期

而Activity的實例則是在ActivityThread中,通過Instrumentation#newActivty來構建的。

因此要動態注冊Activity,需要在宿主的Manifest中注冊一系列的假Activity,來獲取Activity的聲明周期。

再通過反射的方式修改系統的Instrumentation,在系統啟動假Activity之前,將Activity信息替換為真正的Activity。這部分會在Small運行階段中詳細介紹。

Small項目的編譯過程

了解完Small的基本原理后,就該Gradle出場了。因為沒有插件so包,Small是沒法運行的。

下面介紹Small插件的幾個方面:

Gradle插件的初始化過程

如何為每個模塊分配資源ID

打出的so包到底是什么

我們這里只介紹與此相關的Gradle知識,如需補充更多Gradle基礎知識可參考最后的幾篇文章。對Small源碼的分析也僅限于此。

Gradle插件的初始化過程

Small的Smaple目錄下的build.gradle中有apply plugin: 'net.wequick.small'和classpath 'net.wequick.tools.build:gradle-small:1.1.0-beta3'。前者是指定編譯插件,后者是編譯插件在maven倉庫中的位置。

而在DevSmaple目錄中,我們沒有指定classpath也能使用這個插件。這是因為DevSmaple下有一個buildSrc工程,Sample中使用的腳本正是來自buildSrc。

buildSrc下是一個默認的Groovy工程,專門用于存放相對比較復雜的Groovy腳本,避免build.gradle過大里面主要包括一些properties和Groovy源文件。

net.wequick.small.properties中指定apply plugin: 'net.wequick.small'時需要運行的Groovy類implementation-class=net.wequick.gradle.RootPlugin,apply是所有Plugin被調用的入口。


這里可以看到RootPlugin主要做了3件事:創建Extension,配置Project,創建Task。

創建Task


這里我們定義了之前用到的buildLib和buildTask任務,我們才能在Android Studio中看到這些任務。當然Small中用到的任務遠遠不止這四個,而且他們之間有很強的依賴關系。

small_tasks.png

創建Extention

Groovy中定義的Extention就是在Gradle中可以直接配置的一些擴展信息。Android插件的Extention中一定包括compileSdkVersion和buildToolsVersion兩個字段,我們才能在Gradle中使用

android {? ? compileSdkVersion22buildToolsVersion"22.0.1"}

Small中的Gradle配置和Groovy的RootExtention也是對應的


配置Project

這里只截取了configureProject,這里可以看到,根據項目子模塊的類型,還會繼續加載對應的Plugin。


如何為每個模塊分配資源ID

修改PP字段就是在AppPlugin中實現的,LibraryPlugin也是繼承AppPlugin,所以這個策略對公共庫插件生效。


可以看到通過一個sPackageIds保存每個插件對應的PP值。

打出的so包到底是什么

這里主要關注LibraryPlugin <- AppPlugin <- BundlePlugin,三者是繼承關系。

LibraryPlugin:會將庫工程轉換為普通工程,從而能生成apk包。除此之外,這里還將生成的資源id都保存在一個public.txt文件中

AppPlugin:分配PP字段,合并Manifest,合并R文件

將apk文件命名為so,并放到正確的位置

Small項目的運行

Small.preSetUp

這里主要是Small框架的初始化,主要注冊了三種Launcher

這里主要關注ApkBundleLauncher#onCreate的一小部分,這里通過反射完成兩件事

獲取ActivityThread#mInstrumentation,并修改為ApkBundleLauncher#InstrumentationWrapper:這是為了在啟動Activity時,將Activity改為Manifest中聲明的假Activity,從而能通過Instrumentation的檢查。

獲取ActivityThread#mCallback,并修改為ApkBundleLauncher#ActivityThreadHandlerCallback:這是在ActivityThread創建Activity對象前,將ActivityInfo改為真正的Activity。


Small.setUp和Small.openUri

Small.setUp是根據bundle.json去加載插件模塊。插件的實際加載過程,在ApkBundleLauncher中完成。

解析apk文件

加載apk中的資源和類

調用模塊Application#onCreate

除此之外,Small.setUp還做了更新檢測,這里不再贅述。

Small.openUri更簡單,主要是根據uri去匹配對應的Activity

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容