插件框架實現思路及原理
一、技術可行性
a) apk的安裝處理流程
i. apk會copy到/data/app;
ii. 解壓apk中的class.dex,並對其進行優化,獲得odex(即JIT)。最後保存到/data/dalvik_cache;
iii. 還有一些權限和包信息,會緩存到/data/system中的packages.list和packages.xml中。
b) 在android上,對apk包的加載邏輯
i. 加載邏輯
Zygote(孵化器)在成功啓動一Android進程後,會根據packages.list的內容(啓動時會加載到system_process中的pakcagemanager中),把odex文件,加載到dalvik中,完成邏輯的加載;
ii. 資源讀取
資源讀取,主要有兩三個類,分別是Resource、AssertManager和LayoutInflater。
當在顯示界面時,就通過這三個類讀取資源。
c) 結論和猜想
i. apk相對於整個android系統而言,其本身就是一種插件形式體現。根據上面關於邏輯和資源的讀取概述,完全是可以靜默實現的。其次,class.dex並沒有包含Android SDK的代碼,只是保留對Android SDK接口的調用。 可以這樣想象,Android SDK即插件框架,而Android OS即爲整個插件的宿主環境。因此這就可以解釋了,爲什麼在1.x編譯的代碼,在2.x甚至3.x都可以運行,因爲只要插件宿主的接口(即Android SDK)不變,插件運行時所調用的接口都可以被找到。
ii. 爲了減少內存佔用,Resource、AssertManager和LayoutInflater必然不會把apk中的所有資源都加載進來,而是用時才加載並緩存,而且還有一些的處理機制(如最不常用清除等)。因此這些類當中,必然存在一個指明資源路徑的字段或者結構。
iii. 要保證兼容性,插件框架公開給插件的接口,必須遵守Open-Close(開發-封閉)原則。另外,一些已經廢棄掉接口,同樣需要保留。比如Service中的setForeground和JDK的中關於Thread的一些接口等。
iv. 可以嘗試通過反射,修改Resource、AssertManager和LayoutInflater中指明資源路徑的字段;另外,還可以查看源碼,查找設置資源路徑的方法。
二、技術實現要點
a) 邏輯加載
i. 針對接口編程。這個是所有插件框架的基本設計模型。
ii. 通過DexClassLoader加載插件所實現的插件接口,詳細可參考PluginManagerImpl中的parserPlugin方法實現,關鍵代碼如下:
b) AssertManager的實現
經查閱Android源碼,發現AssertManager的實例生成,用到兩個隱藏的方法,如下所示:
通過以上代碼,我們就可以得到我們插件的AssertManager了,關鍵代碼如下所示:
c) Resource的實現
有了插件專用的AssertManager,那麼插件的Resource也輕易得到了,關鍵代碼如下所示:
d) LayoutInflater的實現
一般我們要獲取LayoutInflater,都必須通過Context來獲得,即是說LayoutInflater的資源讀取,都是通過Context的getResoure以及getAssert讀取,是直接跟宿主掛勾的。這裏有兩個方法可選選擇:
其一,自己重寫Context類,並把getResoure和getAssert的返回值改爲上面所得的插件資源相關的實例,即包裝法;
其二,考慮到平時我們用LayoutInflater時,主要是用來加載佈局文件(XML),因此可以投機取巧點,只針對inflater進行修改。
目前框架採用的是第二種方法。先看看LayoutInflater的源碼實現,如下所示:
因此,只調用插件的Resources,並調用其第二個方法,並可實現加載插件的佈局問題,爲了方便使用,定義了一個ILayoutInflater接口,封裝實現細節,關鍵代碼如下:
事實上,通過以上的方法,還是無法完成插件XML佈局文件的加載,通過跟蹤源碼,會發生View的生成,還需要因爲利用到當前Context(或Activity)的一個類型Theme的實例。跟蹤過程如下:
l View(Context context, AttributeSet arrts, int defStyle)
l Context.obtainStyledAttributes(AttrobiteSet arrts, int[] attrs, int defStyleAttr, int defStyleRes)
l Context.getTheme()
l ......
而這個Theme類型,是Resource的一個內部類,不單可以直接引用Resource的,還通過Context保存AssertManager的引用。源碼如下:
因此,我們還需要通過反射的方式,把當前Theme的實例,替換成我們插件的。當XML佈局文件解釋成功後,再恢復過來。留意上面代碼中的begin和end方法,就是這個過程的封裝。關鍵代碼如下:
而插件的Theme實例,可通過Resource的newTheme獲得,關鍵代碼如下:
e) 混合資源解析的實現
基本上,上面的有了上面的三個類,就可以完全加載插件的讀取插件的資源和邏輯。但往往事情並不是這麼簡單。
考慮到UI的可重用性,插件裏,往往會很多的用到宿主所提示的UI庫接口。因此,就需要考慮,當在解釋插件的xml佈局時,如何混合使用兩方的資源。從上面的過程可得,無論是Resources、AssertManager還是LayoutInflater,都是同時只能針對一方資源。當在插件的佈局文件中,使用了兩方的文件,必然會因爲找不到資源,解釋出錯。
不過,LayoutInflater,提示了一個setFactory,可以在解釋XML佈局文件時,優先解釋。關鍵代碼如下:
三、待完善的地方
l 在宿主中定義的資源,目前除了自定義view之外,都不可以使用。比如文字、顏色值、樣式等。
l 插件的權限,必須是宿主的子集。
l 插件中包含so的加載邏輯,還沒有實現。
l 目前插件的加載,都是加載dex,而不是odex。還需要查閱源碼,手工實現這個優化過程。