詳述Android外掛化原理
本文基於singwhatiwanna的開源DL外掛框架講述,修改並重構了一些內容,任總的blog原理講得比較淺,這裡我基於自己的理解再詳細講一點東西,也算是一個記錄吧~
預備知識:
一. Java ClassLoader
作用:
載入Class檔案到JVM,以供程式使用的。我們知道,java程式可以動態載入類定義,而這個動態載入的機制就是通過ClassLoader來實現的。既然ClassLoader是用來載入類到JVM中的,那麼ClassLoader又是如何被載入呢?難道它不是java的類?沒有錯,在這裡確實有一個ClassLoader不是用
核心classLoader:
1.bootstrapclassLoader(啟動類載入器),載入java核心API,包括使用者自定義的classLoader以及另外兩個核心classLoader;
2.ExtClassLoader,載入java擴充套件API,也就是/lib/ext中的類;
3.AppClassLoader,載入程式設計師自定義類,也就是我們工程classPath下設定的class。
Java程式載入過程:
1.當執行一個程式的時候,JVM啟動;
2.運行bootstrapclassloader,該ClassLoader載入java核心API(ExtClassLoader和AppClassLoader也在此時被載入);
3.調用ExtClassLoader載入擴充套件API;
4.最後AppClassLoader載入CLASSPATH目錄下定義的Class。
二.Android ClassLoader
Android ClassLoader種類: DexClassLoader:可以載入檔案系統上的jar、dex、apk PathClassLoader:可以載入/data/app目錄下的apk,這也意味著,它只能載入已經安裝的apk URLClassLoader:可以載入java中的jar,但是由於dalvik不能直接識別jar,所以此方法在android中無法使用,儘管還有這個類Android開發和普通的java開發不同的地方是把class檔案再重新打包成dex型別的檔案,這種重新打包會對Class檔案內部的各種函式表、變量表等進行優化。dex檔案是一種經過android打包工具優化後的Class檔案,因此載入這樣特殊的Class檔案就需要特殊的類裝載器,所以android中提供了DexClassLoader類。載入流程如下:
1. 通過PacageMangager獲得指定的apk的安裝的目錄,dex的解壓縮目錄,c/c++庫的目錄
2.建立一個DexClassLoader例項
3.載入指定的類返回一個Class
4. 然後使用反射呼叫這個Class
三. Android Resources
應用程式的每一個Activity元件都關聯有一個ContextImpl物件,這個ContextImpl物件就是用來描述Activity元件的執行上下文環境的。Activity元件是從Context類繼承下來的,而ContextImpl同樣是從Context類繼承下來的。我們在Activity元件呼叫的大部分成員函式都是轉發給與它所關聯的一個ContextImpl物件的對應的成員函式來處理的,其中就包括用來訪問應用程式資源的兩個成員函式getResources和getAssets。
ContextImpl類的成員函式getResources返回的是一個Resources物件,有了這個Resources物件之後,我們就可以通過資源ID來訪問那些被編譯過的應用程式資源了。ContextImpl類的成員函式getAssets返回的是一個AssetManager物件,有了這個AssetManager物件之後,我們就可以通過檔名來訪問那些被編譯過或者沒有被編譯過的應用程式資原始檔了。事實上,Resources類也是通過AssetManager類來訪問那些被編譯過的應用程式資原始檔的,不過在訪問之前,它會先根據資源ID查詢得到對應的資原始檔名。
Resources類有一個成員函式getAssets,通過它就可以獲得儲存在Resources類的成員變數mAssets中的AssetManager,例如,ContextImpl類的成員函式getAssets就是通過呼叫其成員變數mResources所指向的一個Resources物件的成員函式getAssets來獲得一個可以用來訪問應用程式的非編譯資原始檔的AssetManager。
我們知道,Android應用程式除了要訪問自己的資源之外,還需要訪問系統的資源。系統的資源打包在/system/framework/framework-res.apk檔案中,它在應用程式程序中是通過一個單獨的Resources物件和一個單獨的AssetManager物件來管理的。這個單獨的Resources物件就儲存在Resources類的靜態成員變數mSystem中,我們可以通過Resources類的靜態成員函式getSystem就可以獲得這個Resources物件,而這個單獨的AssetManager物件就儲存在AssetManager類的靜態成員變數sSystem中,我們可以通過AssetManager類的靜態成員函式getSystem同樣可以獲得這個AssetManager物件。
AssetManager類除了在Java層有一個實現之外,在C++層也有一個對應的實現,而Java層的AssetManager類的功能就是通過C++層的AssetManager類來實現的。Java層的每一個AssetManager物件都有一個型別為int的成員變數mObject,它儲存的便是在C++層對應的AssetManager物件的地址,因此,通過這個成員變數就可以將Java層的AssetManager物件與C++層的AssetManager物件關聯起來。
C++層的AssetManager類有三個重要的成員變數mAssetPaths、mResources和mConfig。其中,mAssetPaths儲存的是資源存放目錄,mResources指向的是一個資源索引表,而mConfig儲存的是裝置的本地配置資訊,例如螢幕密度和大小、國家地區和語言等等配置資訊。有了這三個成員變數之後,C++層的AssetManager類就可以訪問應用程式的資源了。
以上知識是理解安卓外掛化非常重要的原理知識,因為外掛化的本質就是通過安卓的DexClassLoader去載入apk裡的類,再通過AssetManager去載入資原始檔,這兩玩意兒都進入記憶體之後,我們的宿主就可以通過介面的方式來呼叫我們的外掛類和資原始檔了。
外掛載入技術詳細介紹
1. 外掛類的載入
宿主程式會到檔案系統比如SD卡去載入apk或者jar(經過測試,必須是jvm可以解壓的字尾格式),然後通過一個proxyActivity作為殼子,去載入apk中的activity。大致細節如下: 外掛Activity本身無法啟動(生命週期,資源等問題),是通過宿主提供的ProxyActivity來載入的; 當我們發Intent去啟動外掛當Activity時實質啟動的時ProxyActivity; 為了封裝細節所以封裝了DXIntent; 所有外掛實現了IDXPlugin介面; PrxoyActivity接管了所有外掛Activity。2. 獲取AssetsManager
載入的方法是通過反射,通過呼叫AssetManager中的addAssetPath方法,我們可以將一個apk中的資源載入到Resources中,由於addAssetPath是隱藏api我們無法直接呼叫,所以只能通過反射,下面是它的宣告,通過註釋我們可以看出,傳遞的路徑可以是zip檔案也可以是一個資源目錄,而apk就是一個zip,所以直接將apk的路徑傳給它,資源就載入到AssetManager中了,然後再通過AssetManager來建立一個新的Resources物件,這個物件就是我們可以使用的apk中的資源了。3. 外掛載入入記憶體的流程
外掛類分析
DXPluginBean
封裝了每個Plugin也就是apk的資料 維護在DXPluginManger類的Map中DXPluginManager
外掛管理核心類 載入外掛 啟動外掛 外掛維護IDXPlugin
把每個外掛的Activity抽象成一個“外掛” IDXPlugin實現了Activity的主要方法 onAttach方法是外掛專用的回撥方法,當外掛Activity被Proxy載入當時候,把proxy的引用賦值給thatDXIntent
pluginPackgeName 跳轉的Plugin的包名,也就是Manifest裡的packageNamepluginClassName 跳轉的Plugin中指定的ActivityName,可以傳null,則預設時跳轉main Activity
DXPluginBaseActivity / DXPluginBaseFragmentActivity
所有外掛Activity繼承這兩個Activity 該Activity實現IDXPlugin介面 onAttach方法中獲得proxyActivity的引用 所有activity繼承方法中需要對外掛本身啟動還是在宿主中被啟動進行判斷DXProxyActivity / DXProxyFragmentActivity
宿主Activity 在host中呼叫外掛Activity的跳轉,本質就是這兩個Activity之間的跳轉 為外掛提供真正的Context 為減少重複程式碼將外掛的初始化放在DXPluginInitializer類中DXPluginInitializer
修復theme帶來的崩潰問題 啟動外掛Activity流程:1 通過反射獲得外掛Activity的預設建構函式
2 通過反射new出一個外掛並強轉成IDXPlugin
3 回撥onAttach方法傳入Prxoy的引用
4 呼叫onCreate方法調起外掛
DXXMLManager
通過配置檔案可實現直接通過外掛名稱呼叫外掛
Service的部分和Activity差不多的原理,不再贅述~
最後來一張UML看下類關係: