Android插件化之資源動態(tài)加載
Android插件化之資源動態(tài)加載
一.概述
Android插件化的一個重要問題就是插件資源訪問問題,先列出會面對的問題
1.如何加載插件資源
2.如何處理插件資源與宿主資源的處突:插件化資源問題要做到的效果是,如果我們要獲取的資源在插件中找得到,則加載優(yōu)先加載插件的,如果找不到,則到宿主資源中找。這樣能做到動態(tài)更新的效果。
3.如何確保插件和宿主使用到的是被修改過的資源。
二.原理分析
在做一件事之前必須先弄清楚原理,所以,這里先要弄清楚Android的資源體系原理。
1.資源鏈
Context:一個apk里面其context的個數(shù)為application+Activity+service的總和,因?yàn)樗麄兌际抢^承context的,然而context只是一個抽象類,其真正的實(shí)現(xiàn)類是ContextImpl,那。拿Activity來說,在Activity的啟動流程中,會在ActivityThread的performLaunchActivity()方法中調(diào)用Activity的attach方法把ContextImp實(shí)例傳給Activity(即賦值給Activity內(nèi)的成員變量mBase)。
Resources:ContextImpl內(nèi)有一個Resources的成員變量mResources,代表的是應(yīng)用的資源,我們平時(shí)在調(diào)用getResources()方法獲取到的是該Resources。
AssetManager:Resources內(nèi)部的一個重要成員是AssetManager(mAssets),其指向的是apk的資源路徑,資源的獲取最終都是通過它來得到的。這里需要注意的是AssetManager并不是Resources獨(dú)立持有的,也就是說系統(tǒng)在獲取資源的時(shí)候不一定是通過Resources獲取的,有時(shí)候是直接通過AssetManager來獲取,比如TypedArray,之前就踩過這個坑。
2.Android是如何構(gòu)造一個應(yīng)用的資源的,并且是如何傳遞給我們使用的,這個要講的東西非常的多,可以看另一篇文章,這里主要講資源插件化。
三.問題的解決方案
1.加載插件資源
資源的加載最后是通過AssetManager內(nèi)的一個方法addAssetPath(String path)
該方法接收的參數(shù)是插件apk的路徑,內(nèi)部會調(diào)用native方法把插件apk對應(yīng)的資源加載進(jìn)來。然而該方法是hide的,我們不能直接調(diào)用,所有只能通過反射。
這樣就成功構(gòu)造出一個指向插件資源的AssetManager。當(dāng)然這時(shí)候還不能使用,還要調(diào)用AssetManager的ensureStringBlocks()方法來初始化其內(nèi)部參數(shù),同樣得使用反射。
2.如何解決插件資源與宿主資源的處突
如果使用到的資源,插件和宿主都同時(shí)存在,則使用插件的資源;如果使用到的資源只有插件有,則使用插件的;如果使用到的資源只有宿主有的,則使用宿主的。
AssetManager的addAssetPath()方法調(diào)用native層AssetManager對象的addAssetPath()方法,通過查看c++代碼可以知道,該方法可以被調(diào)用多次,每次調(diào)用都會把對應(yīng)資源添加起來,而后來添加的在使用資源是會被首先搜索到??梢栽趺蠢斫猓珻++層的AssetManager有一個存放資源的棧,每次調(diào)用addAssetPath()方法都會把資源對象壓如棧,而在讀取搜索資源時(shí)是從棧頂開始搜索,找不到就往下查。所以我們可以這樣來處理AssetManager并得到Resources
其中dexPath2為宿主apk路徑,dexPath為插件apk路徑,superRes為宿主資源,resources為融合插件與宿主的資源。
3. 如何確保插件和宿主使用到的是被修改過的資源:
這是很重要的一步,之前我們已經(jīng)成功獲取資源并對其進(jìn)行修飾,現(xiàn)在要做的是用它替換掉Android為我們生成的那個資源,這就是hook的思想。
使用到資源的地方歸納起來有兩處,一處是在Java代碼中通過Context.getResources獲取,一處是在xml文件(如布局文件)里指定資源,其實(shí)xml文件里最終也是通過Context來獲取資源的只不過是他一般獲取的是Resources里的AssetManager。所以,我們可以在Context對象被創(chuàng)建后且還未使用時(shí)把它里面的Resources(mResources)替換掉。之前說過,整個應(yīng)用的Context數(shù)目等于Application+Activity+Service的數(shù)目,Context會在這幾個類創(chuàng)建對象的時(shí)候創(chuàng)建并添加進(jìn)去。而這些行為都是在ActivityTHread和Instrumentation里做的。
以Activity為例,步驟如下:
a: Activity對象的創(chuàng)建是在ActivityThread里調(diào)用Instrumentation的newActivity方法
ActivityThread:
Instrumentation:
b: Context對象的創(chuàng)建是在ActivityThread里調(diào)用createBaseContextForActivity方法
ActivityThread:
c: Activity綁定Context是在ActivityThread里調(diào)用Activity對象的attach方法,其中appContext就是上面創(chuàng)建的Context對象
ActivityThread:
d: Activity的onCreate()方法的回調(diào)是在ActivityThread里調(diào)用Instrumentation的callActivityOnCreate()方法
ActivityThread:
替換掉Activity里Context里的Resources最好要早,基于上面的觀察,我們可以在調(diào)用Instrumentation的callActivityOnCreate()方法時(shí)把Resources替換掉。那么問題又來了,我們?nèi)绾慰刂芻allActivityOnCreate()方法的執(zhí)行,這里又得使用hook的思想了,即把ActivityThread里面的Instrumentation對象(mInstrumentation)給替換掉,同樣得使用反射。步驟如下
a: 獲取ActivityThread對象
ActivityThread里面有一個靜態(tài)方法,該方法返回的是ActivityThread對象本身,所以我們可以調(diào)用該方法來獲取ActivityTHread對象
然而ActivityThread是被hide的,所以得通過反射來處理,處理如下:
b: 獲取ActivityThread里的Instrumentation對象
c: 構(gòu)建我們自己的Instrumentation對象,并從寫callActivityOnCreate方法
在callActivityOnCreate方法里要先獲取當(dāng)前Activity對象里的Context(mBase),再獲取Context對象里的Resources(mResources)變量,在把mResources變量指向我們構(gòu)造的Resources對象,做到移花接木。
MyInstrumentation:
d: 最后,使ActivityThread里面的mInstrumentation變量指向我們構(gòu)建的MyInstrumentation對象。
代碼
四.應(yīng)用
資源動態(tài)加載的一個應(yīng)用當(dāng)然就是Android插件化方面的使用。還有一個應(yīng)用就是換膚功能,只需要在在工程里添加這些代碼(當(dāng)然還要處理一些邏輯),然后用戶想要給應(yīng)用換皮膚,主題等,即可從后臺下載插件apk,放在指定文件夾就可以關(guān)系應(yīng)用的資源,起到換膚的效果。當(dāng)然,資源動態(tài)加載還有其他應(yīng)用方法,自己琢磨咯?。?!
五.存在問題
1.兼容性問題,因?yàn)閔ook要使用反射,從而來獲取系統(tǒng)hide或類的私有屬性。把它們隱藏是因?yàn)樗鼈兊牟环€(wěn)定性,如果哪天Google覺得那個變量的名稱起的不吉利給改了,那就報(bào)錯了。當(dāng)然解決方法還是有的,就是為不同的API寫不同的代碼。
2.R方面的問題。當(dāng)我們添加了一個資源(如在String.xml里添加了一個String),則系統(tǒng)會為我們在R里面為該資源生成一個int型的id與之對應(yīng),使用的時(shí)候是根據(jù)該id找到對應(yīng)的資源。資源id是按照資源名稱的字典順序來遞增的。拿String來說。
假如我們的String.xml里聲明了名稱為za,zb的資源
則會在R里面生成相應(yīng)的id
基于上面的觀察,我們會發(fā)現(xiàn)一個問題:舉個例子
宿主資源情況為:存在za(id=0x7f060004) zb(id=0x7f060005)
插件資源情況為:存在za(id=0x7f060004) zab(id=0x7f060005) ab(0x7f060006)
這時(shí)候在宿主里獲取資源zb,則根據(jù)上面所說,會根據(jù)id=0x7f060005先存在插件資源,這時(shí)候得到的是zab而不是zb,這就出錯了。
解決方案有,在插件中如果有添加新的資源,則其命名要安裝字典排序在原有的資源下遞增。當(dāng)然也有其他方案,自己琢磨吧。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android計(jì)時(shí)器,時(shí)間計(jì)算器的實(shí)現(xiàn)方法
android計(jì)時(shí)器,時(shí)間計(jì)算器的實(shí)現(xiàn)方法,需要的朋友可以參考一下2013-03-03Android短信驗(yàn)證碼監(jiān)聽解決onChange多次調(diào)用的方法
本篇文章主要介紹了Android短信驗(yàn)證碼監(jiān)聽解決onChange多次調(diào)用的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Android語音識別技術(shù)詳解及實(shí)例代碼
這篇文章主要介紹了Android語音識別技術(shù)的相關(guān)資料,并附實(shí)例代碼及實(shí)例實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-09-09Android實(shí)現(xiàn)底部對話框BottomDialog彈出實(shí)例代碼
本篇文章主要介紹了Android實(shí)現(xiàn)底部對話框BottomDialog代碼。這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2017-03-03Android Insets相關(guān)知識總結(jié)
這篇文章主要介紹了Android Insets相關(guān)知識總結(jié),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03