Android?資源加載使用偽代碼示例分析
引言
聊到 Android 的 資源加載 ,每一個(gè)開(kāi)發(fā)同學(xué)都會(huì)非常熟悉,畢竟從使用來(lái)說(shuō),我們?nèi)粘6紩?huì)見(jiàn)到,比如 getText()
等等。
那如果此時(shí)問(wèn)你,你知道 它們到底是怎么被加載的,內(nèi)部會(huì)有什么處理嗎?
為什么同一個(gè)drawable界面更改了透明度,其他界面也會(huì)生效?
如果你對(duì)上述問(wèn)題依然存疑,那本文可能會(huì)對(duì)你有所幫助。
介于此,本篇將由淺入深,從源頭理清 Resource.getx()
的那些事,從而為理解 Android資源加載 邁出第一步。故此名: 小試牛刀。
本篇定位中等??,主要通過(guò)偽源碼分析的形式,從而探索應(yīng)用層 Resource.getx 的實(shí)現(xiàn)細(xì)節(jié)。
Resource是什么?
Resource
,在 Android 中,指的是我們開(kāi)發(fā)中使用到的資源,例如 drawable
、String
、anim
、color
等。其會(huì)在開(kāi)發(fā)階段生成相應(yīng)的R類(lèi)以及對(duì)應(yīng)的 資源ID ,以便開(kāi)發(fā)者在使用時(shí)通過(guò)傳遞 資源Id ,從而獲取相應(yīng)類(lèi)型的資源文件。
比如我們?cè)?Activity
,Fragment
中經(jīng)常使用的 getString() , getDrawable() ,內(nèi)部也都是調(diào)用的 resource.getxx
實(shí)現(xiàn)。
常見(jiàn)也有 ContextCompat.getDrawable() ,那它與直接調(diào)用resource.getDrawable()有什么區(qū)別?
見(jiàn)名知意,其主要是作為兼容使用,目的是解決不同版本之間的差異。
基礎(chǔ)概念
TypedValue
用于保存數(shù)據(jù)的動(dòng)態(tài)容器,主要用于配合 Resource
保存資源。
具體而言,當(dāng)我們獲取資源時(shí),底層會(huì)調(diào)用相應(yīng)的原生方法將讀取到的資源信息寫(xiě)入其中,以便后續(xù)的判斷與使用;
AssetsManager
資源管理器,用于讀取打包到 Apk
內(nèi)部的資源文件。
具體而言,當(dāng)我們調(diào)用 getxxx
時(shí),其最終會(huì)去調(diào)用相應(yīng)的原生方法獲取資源信息并寫(xiě)入 TypedValue
;
ResourcesImpl
Resource
的具體實(shí)現(xiàn)類(lèi),我們調(diào)用的相關(guān) getxxx
方法,最終都是其作為具體實(shí)現(xiàn),內(nèi)部最終會(huì)調(diào)用 AssetsManager
進(jìn)行加載資源,并且會(huì)處理與之關(guān)聯(lián)的所有緩存。
getText
getText(R.string.xx)
用于從資源文件中獲取文本,具體源碼如下:
從源碼中看,我們調(diào)用的 getText()
最終實(shí)際調(diào)用了 ResourcesImpl
, 內(nèi)部會(huì)使用 AssetsManager
去從底層獲取相應(yīng)的文本資源,并將其保存到 TypedValue
中。如果此次獲取的文本資源是字符串類(lèi)型,則直接從字符串常量池中去取,否則將取到的文本資源轉(zhuǎn)為字符串后返回。
getDrawable
getDrawable(R.drawable.xxx)
用于從資源文件中獲取可繪制對(duì)象,具體偽源碼如下:
當(dāng)我們調(diào)用 getDrawable()
時(shí),內(nèi)部先會(huì)通過(guò) getValueForDensity()
獲取當(dāng)前密度下相應(yīng)的資源文件,并將其寫(xiě)入到 TypeValue
中;
如果不存在資源文件,則直接拋出異常。然后通過(guò) ResourcesImpl.loadDrawable
去加載 Drawable
。
繼續(xù)沿著剛才的源碼,我們?nèi)タ纯?loadDrawable
內(nèi)部到底做了什么,偽代碼如下:
這個(gè)方法流程較長(zhǎng),我們將其分為下面幾個(gè)步驟:
- 判斷當(dāng)前要加載的
drawable
是否具有緩存; - 判斷當(dāng)前
drawable
是否為顏色drawable; - 如果當(dāng)前沒(méi)有加載
drawable
&&當(dāng)前drawable 已緩存 ,直接返回該drawable; - 從當(dāng)前緩存中取出當(dāng)前
drawable
對(duì)應(yīng)的 狀態(tài)與數(shù)據(jù)參數(shù)(如果存在緩存) ;
創(chuàng)建新的 drawable
。如果當(dāng)前存在緩存,則利用緩存的狀態(tài)(Drawable.ConstantState) 構(gòu)建 Drawable
,否則如果是顏色drawable,則直接創(chuàng)建;否則調(diào)用 從xml或者資源中加載drawable,具體偽代碼如下圖:
- 處理構(gòu)建的drawable 主題與參數(shù) ;
- 如果當(dāng)前drawable 沒(méi)有緩存 ,則將添加到緩存中。
小結(jié)
當(dāng)我們調(diào)用 getDrawable()
時(shí),內(nèi)部會(huì)先判斷當(dāng)前資源是否存在,如果不存在則直接拋出異常;接著調(diào)用 ResourcesImpl.loadDrawable
去加載具體的 drawable
,內(nèi)部會(huì)根據(jù)要加載的 drawable
的 類(lèi)型、是否是Color,以及是否存在緩存綜合獲取,如果存在當(dāng)前屏幕密度的drawable,則使用緩存,否則重新加載。然后根據(jù)要加載的 drawable
文件后綴 決定是 colorDrawable
還是 BitMapDrawable
,或者是其他類(lèi)型的Drawable,最后將加載完成的 Drawable
的 狀態(tài)與配置參數(shù)(ConstantState) 加入到 緩存 中。
Tips
知道了 Drawable
會(huì)被緩存的知識(shí)點(diǎn),此時(shí)就不難解釋為什么開(kāi)發(fā)中會(huì)遇到同一個(gè) Drawable
更改了透明度,其他界面用到這個(gè) Drawable
的地方也會(huì)受到了影響。
如下示例:
解決辦法就是,在 drawable
更改透明度時(shí),調(diào)用 mutate()
即可,原理上也很簡(jiǎn)單,重新new了一個(gè)狀態(tài):
background.mutate().alpha = 100 復(fù)制代碼
例如:
getColor
getColor(R.color.xxx)
用于獲取相應(yīng) 資源id
關(guān)聯(lián)的顏色,具體的源碼如下:
當(dāng)我們調(diào)用 getColor()
時(shí),內(nèi)部先會(huì)通過(guò) getValue()
獲取相應(yīng)的 color
資源,并將其保存到 TypeValue
中;如果不存在資源文件,則直接拋出異常。然后通過(guò) ResourcesImpl.loadColorStateList()
去加載,最后返回顏色狀態(tài)列表的 默認(rèn)顯示顏色。
我們繼續(xù)向下看: loadColorStateList()
當(dāng)調(diào)用 loadColorStateList
加載顏色狀態(tài)合集時(shí),內(nèi)部有兩個(gè)分支:
- 如果當(dāng)前要獲取的顏色類(lèi)型是 “#xxx” ,則先從預(yù)加載數(shù)組中取,如果此時(shí)沒(méi)有加載,則創(chuàng)新的
ColorStateList
,并將其存到預(yù)加載數(shù)組中; - 如果當(dāng)前要獲取的顏色類(lèi)型是引用類(lèi)型,則意味著當(dāng)前可能要從xml中去取。內(nèi)部先從緩存數(shù)組中去,如果不存在則再去預(yù)加載數(shù)組中取,如果依然不存在,則調(diào)用
loadComplexColorForCookie()
重新初始化。當(dāng)加載完成后,如果此時(shí)正在預(yù)加載,將其添加到預(yù)加載數(shù)組中,否則將其添加到緩存里。
接著上面的末梢,我們最后再去看一下 loadComplexColorForCookie()
,也即一個(gè)全新的color到底是如何從xml中拿到:
該方法里,先判斷資源文件的后綴名,如果非 .xml
類(lèi)型,則該資源無(wú)法讀取,直接拋出異常;否則先調(diào)用 loadXmlResourceParser()
拿到該資源文件的 xml解析器 ,再由解析器的 name
判斷具體的資源類(lèi)型,從而初始化具體的顏色類(lèi)。
總結(jié)
當(dāng)我們調(diào)用 getColor()
獲取某個(gè)顏色資源時(shí),內(nèi)部會(huì)先通過(guò) AssetsManager
加載該資源,并將其保存到 TypedValue
中,如果沒(méi)有讀到,則拋出異常;否則調(diào)用 ResoucesImpl.loadColorStateList()
獲取顏色資源,如果該資源在緩存中存在,則直接取出并返回新的實(shí)例,否則根據(jù)當(dāng)前要加載的類(lèi)型,如果是 “#xxx” ,則直接初始化并添加到緩存,否則判斷 TypedValue
中保存的資源信息 后綴 是否為 xml
,如果不是則直接拋出異常,證明此時(shí)非 .xml 文件,文件無(wú)法讀取,否則通過(guò) AssetManager
獲取該資源對(duì)應(yīng)的 xml解析器 ,并判斷解析器的名字,從而決定創(chuàng)建 GradientColor
還是 ColorStateList
,然后將結(jié)果緩存到 ResourcesImpl
中并返回。
關(guān)于 Resource.getx()
相關(guān)的底層實(shí)現(xiàn)到這里就分析結(jié)束了。本篇中,我們以 Kotlin+[裁枝剪葉] 的方式,提供一個(gè)較清晰的脈絡(luò),以供更好的讀懂應(yīng)用層源碼設(shè)計(jì),關(guān)于更細(xì)節(jié)的原生實(shí)現(xiàn),并不是本篇所關(guān)注的。所謂一眼入森,而不在林,正是如此。
現(xiàn)在讓我們反推上去:
原來(lái)我們每次調(diào)用 getDrawable()
時(shí),內(nèi)部都是做了緩存處理(緩存了ConstantState
),原來(lái)我們獲取的 drawable
,無(wú)非就三種大的類(lèi)型:
- .xml 結(jié)尾的,
ColorDrawable
或者非ColorDrawable
; - 非.xml 結(jié)尾的,即為
BitmapDrawable
。
那他們又是怎么判斷得出的呢?通過(guò) AssetManager
獲取,將其保存到 TypedValue
中,使用時(shí)通過(guò)判斷 資源文件名后綴 而定。又因?yàn)?code>drawable 存在 緩存狀態(tài)復(fù)用 ,所以又會(huì)導(dǎo)致 一處更新,處處同步 問(wèn)題。原來(lái) getColor()
內(nèi)部同樣做了緩存處理等。
至此,關(guān)于 Android-Resource
的求知篇正式開(kāi)始,下一篇我將同大家分析 Resource
的初始化時(shí)機(jī)以及與 Resource.system()
的區(qū)別。
更多關(guān)于Android 資源加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android5.0以上實(shí)現(xiàn)全透明的狀態(tài)欄方法(仿網(wǎng)易云界面)
下面小編就為大家分享一篇Android5.0以上實(shí)現(xiàn)全透明的狀態(tài)欄方法(仿網(wǎng)易云界面),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Android抓取CSDN首頁(yè)極客頭條內(nèi)容完整實(shí)例
這篇文章主要介紹了Android抓取CSDN首頁(yè)極客頭條內(nèi)容完整實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Android實(shí)現(xiàn)音樂(lè)播放器歌詞顯示效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)音樂(lè)播放器歌詞顯示效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06Android SwipeRefreshLayout超詳細(xì)講解
在android開(kāi)發(fā)中,使用最多的數(shù)據(jù)刷新方式就是下拉刷新了,而完成此功能我們使用最多的就是第三方的開(kāi)源庫(kù)PullToRefresh?,F(xiàn)如今,google也忍不住推出了自己的下拉組件SwipeRefreshLayout,下面我們通過(guò)api文檔和源碼來(lái)分析學(xué)習(xí)如何使用SwipeRefreshLayout2022-11-11Android自定義控件之開(kāi)關(guān)按鈕學(xué)習(xí)筆記分享
這篇文章主要為大家分享了Android自定義開(kāi)關(guān)按鈕的學(xué)習(xí)筆記,內(nèi)容豐富,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05android實(shí)現(xiàn)百度地圖自定義彈出窗口功能
這篇文章主要介紹了android實(shí)現(xiàn)百度地圖自定義彈出窗口的功能,大家參考使用吧2013-11-11Android進(jìn)階教程之ViewGroup自定義布局
這篇文章主要給大家介紹了關(guān)于Android進(jìn)階教程之ViewGroup自定義布局的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06利用Kotlin實(shí)現(xiàn)破解Android版的微信小游戲--跳一跳
這篇文章主要給大家介紹了關(guān)于利用Kotlin實(shí)現(xiàn)破解Android版微信小游戲--跳一跳的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12