需要研究技術:類動態加載,資源動態加載,組件動態注冊
如果需要更新插件,或者新增老插件的協議,還需要更新宿主插件聲明信息(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