小米多主题思路分析-重定向资源篇
首先聲明一下,該文章分析參考小米MIUI多主題一文中的介紹:Android系統(tǒng)如何實(shí)現(xiàn)換膚及MIUI
文檔名稱:《MIUI主題風(fēng)格_一種Android系統(tǒng)換膚功能的設(shè)計(jì)》大家可自行搜索下載。
另外考慮到排版的問(wèn)題,在分析源碼的過(guò)程中只會(huì)留下關(guān)鍵的點(diǎn),太長(zhǎng)的源碼會(huì)以“代碼段x.x{}”替代省略。但是主要的邏輯我會(huì)描述清楚。
1 什么是系統(tǒng)主題?
這一段這樣的對(duì)話: 領(lǐng)導(dǎo)問(wèn)我,主題能替換哪些資源啊?
我不加思索的回答:文字,顏色,圖片,恩…… 還有大小。
領(lǐng)導(dǎo)說(shuō): 還有呢?
我思考了一會(huì)兒,內(nèi)心來(lái)了一段對(duì)白: 折騰了這么久,就支持這幾個(gè)? 是不是忽略了哪些地方,但是實(shí)在沒(méi)有想到別的。
我就回答: 是的。
領(lǐng)導(dǎo)走了,剩下我呆呆的看著車載界面,試圖從里面找到我忽略的地方?
不知道多久,突然我反應(yīng)過(guò)來(lái), 界面除了文字,顏色,圖片,大小,還有別的嗎?
恩是的,這就是主題,就是替換系統(tǒng)和應(yīng)用圖片,文字,顏色,大小,使其展示一種新的風(fēng)格。
2 要實(shí)現(xiàn)系統(tǒng)主題應(yīng)該思考哪些問(wèn)題呢?
2.1 資源重定向:什么是資源重定向呢?
簡(jiǎn)單來(lái)說(shuō)就是在應(yīng)用通過(guò)Resources類獲取資源的過(guò)程中,把應(yīng)該返回的圖片A/顏色A ,替換成主題的 圖片B/顏色B。從而實(shí)現(xiàn)界面風(fēng)格的變化。
2.2 即時(shí)刷新:什么是即時(shí)刷新呢?
在切換主題的時(shí)候,設(shè)備的當(dāng)前頁(yè)面以及之前用戶打開(kāi)過(guò)的頁(yè)面,都要刷新一下,保證這些頁(yè)面切換為新的主題風(fēng)格。
因?yàn)椴环奖惴治鲆呀?jīng)實(shí)現(xiàn)的源碼,所以接下來(lái)的兩篇文章都只是分析Android源碼以及思路。先來(lái)看資源重定向的思路。
3 資源重定向:
先看一張重定向流程圖:
其實(shí)思路就是這樣的應(yīng)用通過(guò)Resources類來(lái)獲取資源的時(shí)候攔截一下,返回主題的資源。要想知道如何攔截,必須了解一下Resources類, 通過(guò)它取資源的流程,然后分析重定向的思路。
按照以下4個(gè)方面來(lái)分析:
3.1 Android apk資源的一些理解。
3.2 訪問(wèn)資源Resources類的構(gòu)建。
3.3 通過(guò)Resources獲取資源的流程(以獲取圖片為例)。
3.4 關(guān)于資源重定向思路分析
3.1 Android apk資源的一些理解:
R文件: 應(yīng)用在編譯完成后會(huì)生成一個(gè)R文件,在應(yīng)用中定義的資源基本在R文件中對(duì)應(yīng)一個(gè)Id,使用資源的時(shí)候都是通過(guò)這個(gè)ID來(lái)查找的。每個(gè)ID由三部分組成: packageID+TypeID+EntryID
resources.arsc文件: 這個(gè)文件是一個(gè)資源配置表,通過(guò)R文件中生成的資源ID可以從這個(gè)資源配置表中找到這個(gè)ID對(duì)應(yīng)配置信息。這些配置信息中可以知道ID的資源類型,對(duì)應(yīng)的名稱/資源路徑等等。
這里大概了解這兩點(diǎn)就行了,足以應(yīng)對(duì)主題功能了,其他更多的大家可以取看羅升陽(yáng)的博客關(guān)于資源的介紹。
3.2 訪問(wèn)資源Resources類的構(gòu)建:
應(yīng)用都是通過(guò)Resources類來(lái)訪問(wèn)資源的,而主題就是希望能偷梁換柱替換資源,所以有必要了解一下Resources這個(gè)類。先看一張圖整體了接Resources類相關(guān):
ResourcesManager: 一個(gè)進(jìn)程可以運(yùn)行多個(gè)應(yīng)用,也就是說(shuō)可能存在多個(gè)Resources對(duì)象,所以ResourcesManager負(fù)責(zé)管理當(dāng)前進(jìn)程的所有Resources對(duì)象。
mActiveResources: ResourcesManager的成員變量,保存Resources對(duì)象。
Resources: 其實(shí)它只是對(duì)外提供一個(gè)訪問(wèn)資源的封裝類,封裝了AssertManager對(duì)象。
AssertManager(java層): 他只是封裝了native層的AssertManager對(duì)象。
AssertManager(native層): 實(shí)際訪問(wèn)資源的類,下面是他的三個(gè)成員變量。
AssetPath: 表示資源的路徑,一般包含兩個(gè), 一個(gè)是Framework-res.apk系統(tǒng)資源的路徑(默認(rèn)添加)另一個(gè)是,App所在的路徑。
ResTable: 資源表,第一次獲取資源的時(shí)候,會(huì)根據(jù)AssetPath給定的路徑,讀取路徑中resources.arsc文件。
ResTable_Config: 當(dāng)前設(shè)置的資源配置信息,在獲取資源的過(guò)程中,會(huì)檢查當(dāng)前的配置信息,獲取正確的資源信息。這個(gè)配置信息的值就是,語(yǔ)言,屏幕分辨率,橫豎屏等等。因?yàn)锳ndroid是支持多語(yǔ)言的,多分辨率的,想要找到合適的資源,就需要先設(shè)置這個(gè)Config。
總結(jié)一下:實(shí)際Resources就是構(gòu)建了這三個(gè)成員變量,
其中AssetPath指明了資源從哪里找?
ResTable:找到后放在哪里?
ResTable_Config:如何找到合適當(dāng)前設(shè)備的資源?
這里考慮文章的篇幅,不做源碼分析,如果取分析ResourcesManager的getTopLevelResources函數(shù)你會(huì)發(fā)現(xiàn)實(shí)際上構(gòu)造一個(gè)Resources類,就是為了構(gòu)造Native層AssertManager的那三個(gè)成員變量。
3.3 通過(guò)Resources獲取資源的流程(以獲取圖片為例)。
Resources是用來(lái)獲取資源的,通過(guò)他獲取圖片資源的接口如下:
public Drawable getDrawable(@DrawableRes int id)
這個(gè)函數(shù)只是調(diào)用了另外一個(gè)getDrawable的函數(shù)
3.3.1 獲取Drawable
public Drawable getDrawable(int id, Theme theme){TypedValue value;getValue(id, value, true);final Drawable res = loadDrawable(value, id, theme);return res;}這個(gè)函數(shù)主要做了兩件事情,第一件事情:調(diào)用getValue函數(shù)獲取一個(gè) TypeValue。 第二件事情: 用TypeValue作為參數(shù),調(diào)用loadDrawable函數(shù)獲取Drawable對(duì)象。
3.3.2 getValue函數(shù)
public void getValue(int id, TypedValue outValue, boolean resolveRefs){boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);}mAssets是前面講過(guò)的java層的 AssertManager類的實(shí)例。具體函數(shù)如下final boolean getResourceValue(int ident,int density,TypedValue outValue,boolean resolveRefs){int block = loadResourceValue(ident, (short) density, outValue, resolveRefs);....省略的代碼和獲取圖片資源流程無(wú)關(guān)return false;}這里loadResourceValue實(shí)際上是一個(gè)jni的函數(shù),這個(gè)函數(shù)在android_util_assetmanager.cpp文件中。
3.3.3 loadResourceValue函數(shù):
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,jint ident, jshort density, jobject outValue, jboolean resolve){AssetManager* am = assetManagerForJavaObject(env, clazz);const ResTable& res(am->getResources());Res_value value;ResTable_config config;uint32_t typeSpecFlags;ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);uint32_t ref = ident;if (resolve) {block = res.resolveReference(&value, block, &ref,typeSpecFlags, &config);}if (block >= 0) {return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);}return static_cast<jint>(block); }前面講過(guò)java層的AssetManager是封裝了Native層的AssetManager對(duì)象。實(shí)際上就是Java層的AssetManager對(duì)象的成員變量mObject對(duì)應(yīng)的就是Native層的一個(gè)地址,這個(gè)地址就是Native層的AssetManger對(duì)象。
a: 這里首先調(diào)用assetManagerForJavaObject把mObject轉(zhuǎn)換成Native層的AssetManager
b: 調(diào)用AssetManger的getResources函數(shù)獲取ResTable,前面分析過(guò)ResTab讀取的是resources.arsc的信息,而resources.ars保存了資源的配置信息。
c: 調(diào)用ResTable的getResources函數(shù)獲取C++層的Res_value,這個(gè)就是資源的配置信息,在C++層的表示。
d: 調(diào)用ResTable的resolveReference是處理引用類型的資源。在Android中定義可以資源可以引用另外一個(gè)資源,
f: 最后調(diào)用copyValue函數(shù)把Res_value 復(fù)制到TypeValue中。
分析到這里其實(shí)已經(jīng)關(guān)聯(lián)上了ResTable和TypedValue,具體的如何獲取ResTab,ResTab如何通過(guò)iden和config匹配到合適的資源,如何處理引用類型的資源,這些過(guò)程會(huì)很長(zhǎng),而且分析流程就是為了找到Hook點(diǎn),其實(shí)沒(méi)有必要攔截到下面去,我曾分析過(guò),試圖從上層設(shè)置一個(gè)參數(shù),在底層實(shí)現(xiàn)主題資源切換,但是沒(méi)找到合適的地方。大家感興趣可以繼續(xù)分析。
接下來(lái)就看這個(gè)TypedValue是怎么用的。
3.3.4 loadDrawable()獲取Drawable對(duì)象。
這個(gè)函數(shù)是重點(diǎn)分5個(gè)代碼段來(lái)分析:
代碼段1{}
mColorDrawableCache: 用于緩存顏色資源的。
mDrawableCache: 用于緩存圖片資源
通過(guò)前面獲得的TypedValue的成員變量,assetCookie和data生成一個(gè)key。
這里再分享一個(gè)看代碼的經(jīng)驗(yàn):如果不知道assetCookie和data表達(dá)什么意思,不打緊。主要是他們可以組合成一個(gè)key,通過(guò)分析下面的代碼可以知道這個(gè)key就是用于緩存的。如果一定要和自己死磕看明白這兩個(gè)變量是什么意思,你會(huì)發(fā)現(xiàn),又會(huì)牽扯出大量的信息,信息量越大,越不利于分析代碼邏輯,通常會(huì)大大降低看代碼的效率。
這里還是簡(jiǎn)單說(shuō)一下,不想了解的可以忽略:
assetCookie : 一般AssetManager有兩個(gè)資源表,一個(gè)是Framework-res.apk,另外一個(gè)就是應(yīng)用本身,放在一個(gè)數(shù)組里面。 當(dāng)assetCookie=0 表示的是Framework-res.apk的資源,如果assetCookie = 1, 表示的是應(yīng)用本省的資源。
data: 表示的是一個(gè)位置或者資源的值。當(dāng)前這個(gè)資源在字符串資源池哪個(gè)block的位置。
type: 表示當(dāng)前資源的類型。
當(dāng)type為COLOR時(shí): 為什么只用data為key呢?
原因是,可能在在應(yīng)用定義多個(gè)Color但是他們表達(dá)的顏色是一樣的,這樣應(yīng)用只要緩存一份就可以了,不用緩存多份。
代碼段2:
檢查當(dāng)前資源是否已經(jīng)緩存過(guò),如果緩存過(guò)直接放回。
代碼段3:
sPreloadedColorDrawables:預(yù)加載的Color資源
sPreloadedDrawables:預(yù)加載的圖片資源
Android系統(tǒng)會(huì)預(yù)加載一些常用的公共資源,類等。以提高運(yùn)行效率。這些預(yù)加載的資源是在Zygote進(jìn)程中加載的。而Android應(yīng)用的進(jìn)程都是由zygote進(jìn)程fork出來(lái)的。因?yàn)長(zhǎng)inux的fork特性,這些進(jìn)程fork出來(lái)以后,預(yù)加載的資源就已經(jīng)到內(nèi)存里面了。
這段代碼就是檢查當(dāng)前圖片資源是否預(yù)加載的緩存中。
這段代碼是如果緩存前面的緩存里面存在,就直接取出drawable,如果沒(méi)有就調(diào)用loadDrawableForCookie函數(shù)加載drawable。這個(gè)函數(shù)稍后分析。
//代碼段5{}if (dr != null) {dr.setChangingConfigurations(value.changingConfigurations);cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);}這段代碼主要是把代碼段4獲取的Drawable緩存起來(lái)。保證第二次獲取的時(shí)候就直接從緩存中讀。
return dr;
}
代碼段4{} loadDrawableForCookie函數(shù):
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {final String file = value.string.toString();final Drawable dr;if (file.endsWith(".xml")) {final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, "drawable");dr = Drawable.createFromXml(this, rp, theme);rp.close();} else {final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);dr = Drawable.createFromResourceStream(this, value, is, file, null);is.close();}return dr;}TypedValue的成員變量string: 原來(lái)當(dāng)資源是圖片是,string表示的文件名。
當(dāng)文件名是.xml文件時(shí),調(diào)用另外一個(gè)函數(shù)取解析獲取drawable,實(shí)際上你會(huì)發(fā)現(xiàn),這種情況一班是,shape,selector之類的資源,里面還是Drawable最終還是會(huì)調(diào)用到下面else的流程來(lái)獲取Drawable。
當(dāng)文件名不是以.xml文件時(shí),通過(guò)AssetManager獲取drawable文件的InputStream,然后創(chuàng)建Drwable對(duì)象。
總結(jié)一下: 在圖片資源獲取流程中,首先是獲取該資源的TypedValue,然后通過(guò)TypedValue的成員變量assetCookie和data 生成圖片資源的key, 再通過(guò)key檢查當(dāng)前的圖片資源是否已經(jīng)在緩存中,如果沒(méi)有緩存,就通過(guò)TypedValue的成員變量string找到當(dāng)前圖片資源的文件名。通過(guò)AssertManager對(duì)象找到文件的InputStream創(chuàng)建Drawable對(duì)象。
這個(gè)TypedValue是一個(gè)很重要的變量。大家分析其他資源,比如string,color,dimen,的獲取流程,會(huì)發(fā)現(xiàn)這些資源的值,就保存在他的成員變量data中。
3.4 關(guān)于資源重定向思路分析
前面的分析已經(jīng)大概了解Resources類,以及通過(guò)他獲取資源的流程。而文檔中也給出了,重定向資源,就是在應(yīng)用通過(guò)Resources類來(lái)獲取資源的時(shí)候攔截一下,返回主題的資源。
對(duì)于圖片資源而言,其實(shí)就是在上面的流程中找一個(gè)地方,把代碼段4{} loadDrawableForCookie函數(shù)分析中,通過(guò)AssertManager拿到InputStream創(chuàng)建的Drawable替換掉,具體替換的點(diǎn),大家可以自己選,但是有一個(gè)問(wèn)題應(yīng)該考慮,那就是你也要遵從Android 對(duì)資源的緩存機(jī)制。 或者說(shuō)自己加一套緩存,遵從圖片獲取的流程。
對(duì)于其他資源而言,也分析過(guò),TypedValue的data成員變量,保存了他們值。
4 主題資源的存放格式:
在《MIUI主題風(fēng)格_一種Android系統(tǒng)換膚功能的設(shè)計(jì)》 的文檔里面已經(jīng)說(shuō)的很清除了,他也提到,主題資源需要手動(dòng)解析的?這個(gè)手動(dòng)解析是什么意思呢?
正常我們定義的string,color資源,在aapt編譯的時(shí)候,就把這些資源對(duì)應(yīng)的值已經(jīng)解析出來(lái)了。我們可以通過(guò)Resources類的接口把值讀出來(lái)。
因?yàn)橹黝}的資源是沒(méi)有被系統(tǒng)編譯過(guò)的,所以需要手動(dòng)解析,解析出來(lái)的值,應(yīng)該和aapt編譯string,color,dimen解析出來(lái)的值一樣。
如果大家在手動(dòng)解析過(guò)程中遇到什么問(wèn)題,可以參考aapt是如何編譯string,color,dimen資源的。
歡迎關(guān)注個(gè)人微信公眾號(hào),定期更新個(gè)人工作心得
總結(jié)
以上是生活随笔為你收集整理的小米多主题思路分析-重定向资源篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: StatusCodeError: 400
- 下一篇: ASP.NET 4.0 取消表单危险字符