Android?資源加載使用偽代碼示例分析
引言
聊到 Android 的 資源加載 ,每一個開發(fā)同學都會非常熟悉,畢竟從使用來說,我們日常都會見到,比如 getText() 等等。
那如果此時問你,你知道 它們到底是怎么被加載的,內部會有什么處理嗎?
為什么同一個drawable界面更改了透明度,其他界面也會生效?
如果你對上述問題依然存疑,那本文可能會對你有所幫助。
介于此,本篇將由淺入深,從源頭理清 Resource.getx() 的那些事,從而為理解 Android資源加載 邁出第一步。故此名: 小試牛刀。
本篇定位中等??,主要通過偽源碼分析的形式,從而探索應用層 Resource.getx 的實現(xiàn)細節(jié)。
Resource是什么?
Resource,在 Android 中,指的是我們開發(fā)中使用到的資源,例如 drawable、String、anim、color 等。其會在開發(fā)階段生成相應的R類以及對應的 資源ID ,以便開發(fā)者在使用時通過傳遞 資源Id ,從而獲取相應類型的資源文件。
比如我們在 Activity,Fragment 中經(jīng)常使用的 getString() , getDrawable() ,內部也都是調用的 resource.getxx 實現(xiàn)。
常見也有 ContextCompat.getDrawable() ,那它與直接調用resource.getDrawable()有什么區(qū)別?
見名知意,其主要是作為兼容使用,目的是解決不同版本之間的差異。
基礎概念
TypedValue
用于保存數(shù)據(jù)的動態(tài)容器,主要用于配合 Resource 保存資源。
具體而言,當我們獲取資源時,底層會調用相應的原生方法將讀取到的資源信息寫入其中,以便后續(xù)的判斷與使用;
AssetsManager
資源管理器,用于讀取打包到 Apk 內部的資源文件。
具體而言,當我們調用 getxxx 時,其最終會去調用相應的原生方法獲取資源信息并寫入 TypedValue ;
ResourcesImpl
Resource 的具體實現(xiàn)類,我們調用的相關 getxxx 方法,最終都是其作為具體實現(xiàn),內部最終會調用 AssetsManager 進行加載資源,并且會處理與之關聯(lián)的所有緩存。
getText
getText(R.string.xx)
用于從資源文件中獲取文本,具體源碼如下:

從源碼中看,我們調用的 getText() 最終實際調用了 ResourcesImpl , 內部會使用 AssetsManager 去從底層獲取相應的文本資源,并將其保存到 TypedValue 中。如果此次獲取的文本資源是字符串類型,則直接從字符串常量池中去取,否則將取到的文本資源轉為字符串后返回。
getDrawable
getDrawable(R.drawable.xxx)
用于從資源文件中獲取可繪制對象,具體偽源碼如下:

當我們調用 getDrawable() 時,內部先會通過 getValueForDensity() 獲取當前密度下相應的資源文件,并將其寫入到 TypeValue 中;
如果不存在資源文件,則直接拋出異常。然后通過 ResourcesImpl.loadDrawable 去加載 Drawable。
繼續(xù)沿著剛才的源碼,我們去看看 loadDrawable 內部到底做了什么,偽代碼如下:

這個方法流程較長,我們將其分為下面幾個步驟:
- 判斷當前要加載的
drawable是否具有緩存; - 判斷當前
drawable是否為顏色drawable; - 如果當前沒有加載
drawable&&當前drawable 已緩存 ,直接返回該drawable; - 從當前緩存中取出當前
drawable對應的 狀態(tài)與數(shù)據(jù)參數(shù)(如果存在緩存) ;
創(chuàng)建新的 drawable 。如果當前存在緩存,則利用緩存的狀態(tài)(Drawable.ConstantState) 構建 Drawable,否則如果是顏色drawable,則直接創(chuàng)建;否則調用 從xml或者資源中加載drawable,具體偽代碼如下圖:

- 處理構建的drawable 主題與參數(shù) ;
- 如果當前drawable 沒有緩存 ,則將添加到緩存中。
小結
當我們調用 getDrawable() 時,內部會先判斷當前資源是否存在,如果不存在則直接拋出異常;接著調用 ResourcesImpl.loadDrawable 去加載具體的 drawable ,內部會根據(jù)要加載的 drawable 的 類型、是否是Color,以及是否存在緩存綜合獲取,如果存在當前屏幕密度的drawable,則使用緩存,否則重新加載。然后根據(jù)要加載的 drawable 文件后綴 決定是 colorDrawable 還是 BitMapDrawable ,或者是其他類型的Drawable,最后將加載完成的 Drawable 的 狀態(tài)與配置參數(shù)(ConstantState) 加入到 緩存 中。
Tips
知道了 Drawable 會被緩存的知識點,此時就不難解釋為什么開發(fā)中會遇到同一個 Drawable 更改了透明度,其他界面用到這個 Drawable 的地方也會受到了影響。
如下示例:

解決辦法就是,在 drawable 更改透明度時,調用 mutate() 即可,原理上也很簡單,重新new了一個狀態(tài):
background.mutate().alpha = 100 復制代碼
例如:

getColor
getColor(R.color.xxx)
用于獲取相應 資源id 關聯(lián)的顏色,具體的源碼如下:

當我們調用 getColor() 時,內部先會通過 getValue() 獲取相應的 color 資源,并將其保存到 TypeValue 中;如果不存在資源文件,則直接拋出異常。然后通過 ResourcesImpl.loadColorStateList() 去加載,最后返回顏色狀態(tài)列表的 默認顯示顏色。
我們繼續(xù)向下看: loadColorStateList()

當調用 loadColorStateList 加載顏色狀態(tài)合集時,內部有兩個分支:
- 如果當前要獲取的顏色類型是 “#xxx” ,則先從預加載數(shù)組中取,如果此時沒有加載,則創(chuàng)新的
ColorStateList,并將其存到預加載數(shù)組中; - 如果當前要獲取的顏色類型是引用類型,則意味著當前可能要從xml中去取。內部先從緩存數(shù)組中去,如果不存在則再去預加載數(shù)組中取,如果依然不存在,則調用
loadComplexColorForCookie()重新初始化。當加載完成后,如果此時正在預加載,將其添加到預加載數(shù)組中,否則將其添加到緩存里。
接著上面的末梢,我們最后再去看一下 loadComplexColorForCookie() ,也即一個全新的color到底是如何從xml中拿到:

該方法里,先判斷資源文件的后綴名,如果非 .xml 類型,則該資源無法讀取,直接拋出異常;否則先調用 loadXmlResourceParser() 拿到該資源文件的 xml解析器 ,再由解析器的 name 判斷具體的資源類型,從而初始化具體的顏色類。
總結
當我們調用 getColor() 獲取某個顏色資源時,內部會先通過 AssetsManager 加載該資源,并將其保存到 TypedValue 中,如果沒有讀到,則拋出異常;否則調用 ResoucesImpl.loadColorStateList() 獲取顏色資源,如果該資源在緩存中存在,則直接取出并返回新的實例,否則根據(jù)當前要加載的類型,如果是 “#xxx” ,則直接初始化并添加到緩存,否則判斷 TypedValue 中保存的資源信息 后綴 是否為 xml ,如果不是則直接拋出異常,證明此時非 .xml 文件,文件無法讀取,否則通過 AssetManager 獲取該資源對應的 xml解析器 ,并判斷解析器的名字,從而決定創(chuàng)建 GradientColor 還是 ColorStateList,然后將結果緩存到 ResourcesImpl 中并返回。
關于 Resource.getx() 相關的底層實現(xiàn)到這里就分析結束了。本篇中,我們以 Kotlin+[裁枝剪葉] 的方式,提供一個較清晰的脈絡,以供更好的讀懂應用層源碼設計,關于更細節(jié)的原生實現(xiàn),并不是本篇所關注的。所謂一眼入森,而不在林,正是如此。
現(xiàn)在讓我們反推上去:
原來我們每次調用 getDrawable() 時,內部都是做了緩存處理(緩存了ConstantState),原來我們獲取的 drawable,無非就三種大的類型:
- .xml 結尾的,
ColorDrawable或者非ColorDrawable; - 非.xml 結尾的,即為
BitmapDrawable。
那他們又是怎么判斷得出的呢?通過 AssetManager 獲取,將其保存到 TypedValue 中,使用時通過判斷 資源文件名后綴 而定。又因為drawable 存在 緩存狀態(tài)復用 ,所以又會導致 一處更新,處處同步 問題。原來 getColor() 內部同樣做了緩存處理等。
至此,關于 Android-Resource 的求知篇正式開始,下一篇我將同大家分析 Resource 的初始化時機以及與 Resource.system() 的區(qū)別。
更多關于Android 資源加載的資料請關注腳本之家其它相關文章!
相關文章
Android5.0以上實現(xiàn)全透明的狀態(tài)欄方法(仿網(wǎng)易云界面)
下面小編就為大家分享一篇Android5.0以上實現(xiàn)全透明的狀態(tài)欄方法(仿網(wǎng)易云界面),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android SwipeRefreshLayout超詳細講解
在android開發(fā)中,使用最多的數(shù)據(jù)刷新方式就是下拉刷新了,而完成此功能我們使用最多的就是第三方的開源庫PullToRefresh?,F(xiàn)如今,google也忍不住推出了自己的下拉組件SwipeRefreshLayout,下面我們通過api文檔和源碼來分析學習如何使用SwipeRefreshLayout2022-11-11
利用Kotlin實現(xiàn)破解Android版的微信小游戲--跳一跳
這篇文章主要給大家介紹了關于利用Kotlin實現(xiàn)破解Android版微信小游戲--跳一跳的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-12-12

