插件框架實現思路及原理

插件框架實現思路及原理

一、技術可行性

a) apk的安裝處理流程

i. apkcopy/data/app

ii. 解壓apk中的class.dex,並對其進行優化,獲得odex(即JIT)。最後保存到/data/dalvik_cache

iii. 還有一些權限和包信息,會緩存到/data/system中的packages.listpackages.xml中。


b) android上,對apk包的加載邏輯

i. 加載邏輯

 Zygote(孵化器)在成功啓動一Android進程後,會根據packages.list的內容(啓動時會加載到system_process中的pakcagemanager中),把odex文件,加載到dalvik中,完成邏輯的加載;


ii. 資源讀取

資源讀取,主要有兩三個類,分別是ResourceAssertManagerLayoutInflater

當在顯示界面時,就通過這三個類讀取資源。


c) 結論和猜想

i. apk相對於整個android系統而言,其本身就是一種插件形式體現。根據上面關於邏輯和資源的讀取概述,完全是可以靜默實現的。其次,class.dex並沒有包含Android SDK的代碼,只是保留對Android SDK接口的調用。 可以這樣想象,Android SDK即插件框架,而Android OS即爲整個插件的宿主環境。因此這就可以解釋了,爲什麼在1.x編譯的代碼,在2.x甚至3.x都可以運行,因爲只要插件宿主的接口(Android SDK)不變,插件運行時所調用的接口都可以被找到。


ii. 爲了減少內存佔用,ResourceAssertManagerLayoutInflater必然不會把apk中的所有資源都加載進來,而是用時才加載並緩存,而且還有一些的處理機制(如最不常用清除等)。因此這些類當中,必然存在一個指明資源路徑的字段或者結構。


iii. 要保證兼容性,插件框架公開給插件的接口,必須遵守Open-Close(開發-封閉)原則。另外,一些已經廢棄掉接口,同樣需要保留。比如Service中的setForegroundJDK的中關於Thread的一些接口等。


iv. 可以嘗試通過反射,修改ResourceAssertManagerLayoutInflater中指明資源路徑的字段;另外,還可以查看源碼,查找設置資源路徑的方法。



二、技術實現要點

a) 邏輯加載

i. 針對接口編程。這個是所有插件框架的基本設計模型。

ii. 通過DexClassLoader加載插件所實現的插件接口,詳細可參考PluginManagerImpl中的parserPlugin方法實現,關鍵代碼如下:




b) AssertManager的實現

經查閱Android源碼,發現AssertManager的實例生成,用到兩個隱藏的方法,如下所示:






通過以上代碼,我們就可以得到我們插件的AssertManager了,關鍵代碼如下所示:




c) Resource的實現

有了插件專用的AssertManager,那麼插件的Resource也輕易得到了,關鍵代碼如下所示:



d) LayoutInflater的實現

一般我們要獲取LayoutInflater,都必須通過Context來獲得,即是說LayoutInflater的資源讀取,都是通過ContextgetResoure以及getAssert讀取,是直接跟宿主掛勾的。這裏有兩個方法可選選擇:

其一,自己重寫Context類,並把getResouregetAssert的返回值改爲上面所得的插件資源相關的實例,即包裝法;

其二,考慮到平時我們用LayoutInflater時,主要是用來加載佈局文件(XML),因此可以投機取巧點,只針對inflater進行修改。

目前框架採用的是第二種方法。先看看LayoutInflater的源碼實現,如下所示:





因此,只調用插件的Resources,並調用其第二個方法,並可實現加載插件的佈局問題,爲了方便使用,定義了一個ILayoutInflater接口,封裝實現細節,關鍵代碼如下:




事實上,通過以上的方法,還是無法完成插件XML佈局文件的加載,通過跟蹤源碼,會發生View的生成,還需要因爲利用到當前Context(Activity)的一個類型Theme的實例。跟蹤過程如下:

View(Context context, AttributeSet arrts, int defStyle)

Context.obtainStyledAttributes(AttrobiteSet arrts, int[]  attrs, int defStyleAttr, int defStyleRes)

Context.getTheme()

......

而這個Theme類型,是Resource的一個內部類,不單可以直接引用Resource的,還通過Context保存AssertManager的引用。源碼如下:




因此,我們還需要通過反射的方式,把當前Theme的實例,替換成我們插件的。當XML佈局文件解釋成功後,再恢復過來。留意上面代碼中的beginend方法,就是這個過程的封裝。關鍵代碼如下:





而插件的Theme實例,可通過ResourcenewTheme獲得,關鍵代碼如下:




e) 混合資源解析的實現

基本上,上面的有了上面的三個類,就可以完全加載插件的讀取插件的資源和邏輯。但往往事情並不是這麼簡單。

考慮到UI的可重用性,插件裏,往往會很多的用到宿主所提示的UI庫接口。因此,就需要考慮,當在解釋插件的xml佈局時,如何混合使用兩方的資源。從上面的過程可得,無論是ResourcesAssertManager還是LayoutInflater,都是同時只能針對一方資源。當在插件的佈局文件中,使用了兩方的文件,必然會因爲找不到資源,解釋出錯。

不過,LayoutInflater,提示了一個setFactory,可以在解釋XML佈局文件時,優先解釋。關鍵代碼如下:







三、待完善的地方

在宿主中定義的資源,目前除了自定義view之外,都不可以使用。比如文字、顏色值、樣式等。

插件的權限,必須是宿主的子集。

插件中包含so的加載邏輯,還沒有實現。

目前插件的加載,都是加載dex,而不是odex。還需要查閱源碼,手工實現這個優化過程。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章