Android視圖加載優(yōu)化——Factory2設(shè)置方法

前言

Factory2是直接繼承于Factory根蟹,繼續(xù)跟蹤下Factory的源碼璧榄,比Factory的功能更加強(qiáng)大漫贞。 當(dāng)我們新建 Activity 的時候督笆,大部分情況是繼承 AppCompatActivity 处渣。提供了向后兼容性伶贰。

本文將深入探索 AppCompatActivity視圖加載,探索將 xml 布局文件中的 TextView 替換成 AppCompatTextView 的全過程罐栈,并由淺入深介紹了Factory2 的一些奇技淫巧幕袱,幫助各位Android 開發(fā)者簡化開發(fā),提高效率悠瞬。


一们豌、Factory2

在 Android 中涯捻,我們經(jīng)常在 xml 文件中書寫布局买决。這些文件被打包進(jìn) app(因為性能原因由 aapt/2 轉(zhuǎn)換為二進(jìn)制 xml)俭尖,并且在運(yùn)行時由 LayoutInflater 加載坦仍。

LayoutInflater 中有兩個方法 setFactorysetFactory2 苞也,文檔中是這樣描述的:

當(dāng)使用 LayoutInflater 創(chuàng)建 View 的時候乐疆,綁定一個自定義的 factory 實例记靡。不能為 null刘绣,并且只能設(shè)置一次然磷,設(shè)置之后無法修改摄欲,當(dāng) xml 中每一個元素名字被解析的時候調(diào)用轿亮。若 factory 返回一個 View,將被添加到視圖層級中胸墙;若返回 null我注,factory 的下一個默認(rèn)方法 onCreateView(View, String, AttributeSet) 將被調(diào)用。

注意迟隅,Factory2 implements Factory 但骨,所以對于 Api 11+ 的應(yīng)用來說,應(yīng)該使用 setFactory2 智袭。這就相當(dāng)于給了我們介入 xml 中每一個 View 元素的創(chuàng)建過程的機(jī)會奔缠。讓我們看一個實際使用:

上面的代碼中,我們僅僅為當(dāng)前 ContextLayoutInflater 設(shè)置了一個 Factory2 吼野。這樣只要發(fā)現(xiàn)了 TextView 校哎,都會被替換為我們自己的實現(xiàn)類 RedTextView

RedTextViewTextView 的子類瞳步,提供了 setBackgroundColor 方法贬蛙,將背景置為紅色:

布局文件 factory.xml 是這樣的:

運(yùn)行應(yīng)用并使用 Layout Inspector,我們發(fā)現(xiàn)所有的 TextView 都變成了 RedTextView 谚攒。棒極了阳准!

二、Appcompat Activity 和 Factory2

如果把上面的 FactoryActivity 修改為繼承 AppCompatActivity 馏臭,我們會看到 TextView 確實變成了 RedTextView 野蝇。但是我們添加的 Button 仍然是 Button,并沒有變成 AppCompatButton 括儒,這是為什么绕沈?

AppCompatActivityonCreate 方法的前兩行是:

getDelegate() 根據(jù) api 版本的不同返回對應(yīng)的代理類(AppCompatDelegateImplV14, AppCompatDelegateImplV23, AppCompatDelegateImplN 等等)。

下一行代碼 delegate.installViewFactory() 帮寻,當(dāng) layoutInflater.getFactory() 為空的時候乍狐,會調(diào)用 setFactory2。如果不為空固逗,什么都不會做浅蚪。

所以 Button 沒有發(fā)生變化的原因是藕帜,已經(jīng)設(shè)置過了 Factory,導(dǎo)致 AppcompatActivity 自己的 factory 沒有被 install惜傲。

注意洽故,FactoryActivitysetFactory2() 方法是在 super.onCreate 之前調(diào)用的。如果不是的話盗誊,當(dāng)父類是 AppcompatActivity 时甚,setFactory2 會拋出異常。因為 AppCompatActivity 設(shè)置了自己的 Factory 哈踱。文檔中是這樣描述的:它不能為空荒适,且只能被設(shè)置一次;在設(shè)置之后开镣,你不能對 Factory 進(jìn)行改變 刀诬。

三、如何兼容 AppCompatActivity 的 Factory2

如何既能使用自己的 Factory2哑子,又能讓 AppCompatActivity 保留自己的 Facotory 呢?下面給出幾種解決方法肌割。

(一)代理給 AppCompatDelegate

AppCompatDelegate 內(nèi)部有一個 createView 方法卧蜓,不要和 FactoryFactory2onCreateView 混淆把敞。

我們僅僅只需要修改 setFactory2 弥奸,將不需要處理的情況代理給 AppCompatDelegate

運(yùn)行一下,TextView 變成了 RedTextView 奋早,Button 變成了 AppCompatButton 盛霎,成功!

(二)重寫 viewInflaterClass

我們看一下 AppCompatDelegatecreateView 方法耽装,當(dāng) AppCompatViewInflater 沒有初始化時愤炸,會通過反射創(chuàng)建。要初始化的類由 R.styleable.AppCompatTheme_viewInflaterClass 指定掉奄,默認(rèn)就是 AppCompatViewInflater 规个。

FactoryActivity 的 theme 進(jìn)行如下修改:

就可以讓 AppCompatDelegate 使用我們自定義的 AppCompatViewInflater 的子類 CustomViewInflater :

Google 的 Material Design Components 實際上就是使用這種方法來將 Button 修改為對應(yīng)的 MaterialButton ,在 這里 可以看到 姓建。

這個方法很強(qiáng)大诞仓,它可以讓你的 App 使用 Material Design Components 這樣的類庫,卻僅僅只需要設(shè)置合適的主題速兔。

注意 AppCompatViewInflater 還提供了一個可以被重寫的 createView() 方法墅拭,用來處理默認(rèn)情況下沒有被處理的新的組件。當(dāng) AppCompatViewInflater 沒有處理特定的組件類型涣狗,就可以使用這個方法谍婉。

(三)自定義 LayoutInflater

第三種方法是重寫 ActivityattachBaseContext 舒憾,改寫 ContextThemeWrappergetSystemService 方法,返回自定義的 LayoutInflater 屡萤。自定義的 LayoutInflater 可以重寫 setFactory2 方法珍剑,加入自己的處理邏輯。這個方法是我從 ViewPump 學(xué)到的死陆。

四招拙、一些小細(xì)節(jié)

下面介紹了 AppCompatDelegate 在進(jìn)行視圖加載過程中的幾個小細(xì)節(jié)。

(一)onCreateView

我們希望 Factory2onCreateView 方法直接調(diào)用 createView (代理給 AppCompatDelegate 那一小節(jié)中提到過) 措译。事實上别凤,的確也是這么做的。但是代碼中還多了一點(diǎn)東西 - 調(diào)用了 callActivityOnCreateView 领虹。在 AppCompatDelegateImplV14 中是這樣的:

看一下 LayoutInflater源碼 , createViewFromTag 嘗試通過 factory 獲取 view 规哪。如果沒有獲取到,會使用 mPrivateFactory 塌衰。如果依舊沒有獲取到诉稍,會通過視圖標(biāo)簽去創(chuàng)建 view 。mPrivateFactory 是在 Activity 中設(shè)置的最疆。

有意思的是杯巨, mPrivateFactory 的作用是解析 fragment 標(biāo)簽。

在 API 14 之前努酸,LayoutInflater 并沒有提供 mPrivateFactory 讓 Activity 可以有個兜底方案來創(chuàng)建 View 服爷。因此,callActivityOnCreateView 在低版本中提供了這一功能获诈。但這現(xiàn)在都沒有關(guān)系了仍源,反正 AppCompat 目前只兼容 Api 14+ 。

另一個有意思的知識點(diǎn)是 Window.Callback 舔涎。Window.Callback 是一個回調(diào)笼踩,讓調(diào)用者可以攔截 key 的分發(fā),面板亡嫌,菜單等等戳表。它讓 AppCompatActivity 可以處理一些特定時間,例如菜單鍵昼伴,返回鍵等匾旭。

(二)createView

總的來說,AppCompatDelegateImplV9 做了兩件事圃郊。首先价涝,創(chuàng)建了 AppCompatViewInflater 或者在 theme 中指定的其他子類。第二持舆,通過 inflater 創(chuàng)建 View 色瘩。

AppCompatViewInflatercreateView 使用了正確的 Context (考慮到支持 app:themeandroid:theme伪窖,需要對 Context 進(jìn)行包裝),根據(jù)組件名稱創(chuàng)建對應(yīng)的 AppCompat 組件(例如居兆,如果是 TextView覆山,就調(diào)用 createTextView 方法返回 AppCompatTextView)。

(三)支持 app:theme

從 Android 5.0 開始泥栖,可以給 View 設(shè)置 app:theme 以覆蓋特定 View 及其子類的屬性簇宽。AppCompat 通過繼承父 View 的 context 在 Android 5.0 之前復(fù)制這一行為。

在 AppCompat 加載 View 之前吧享,它先拿到父 View 的 Context魏割,然后嘗試創(chuàng)建一個 ContextThemeWrapper(android:theme 或者 app:theme),保證使用正確的 context 來加載組件钢颂。

另外钞它,如果開發(fā)者明確聲明需要在資源中使用矢量圖,AppCompat 在 Android 5.0 之前還提供了 TintContextWrapper 來包裝 Context 殊鞭。

(四)View 的創(chuàng)建和兜底

通過這些信息遭垛,系統(tǒng)已經(jīng)準(zhǔn)備好如何創(chuàng)建 View 了。

遍歷支持的組件列表操灿,對于通用的 View锯仪,如 TextView, ImageView ,直接生成對應(yīng)的 AppCompat 子類牲尺。如果是未知類型的 View卵酪,將使用正確的 Context 調(diào)用 createView幌蚊,默認(rèn)返回 null谤碳,但一般會被 AppCompatViewInflater 的子類重寫。

如果這時候 view 仍然是 null溢豆,會檢查 view 的原始 context 是否和父 View 的 context 一致蜒简。這種情況會發(fā)生在子 View 的 android:theme 和 父 View 不一致。

在檢查 android:onClick 之后漩仙,view 就被返回了搓茬。

五、總結(jié)和使用實例

總結(jié)一下队他,AppCompatActivity 通過給 LayoutInflater 設(shè)置 Factory2 來介入 View 的創(chuàng)建過程卷仑,以提供向后兼容性(為組件提供 tint,處理 android:theme 等)麸折。它也保證了可擴(kuò)展性锡凝,開發(fā)者可以進(jìn)行一些定制處理。

除了 Appcompat垢啼,這一技巧被用來完成了更多有意思的事情窜锯。Probe (現(xiàn)已廢棄) 提供了 OvermeasureInterceptor 來記錄 View 的測量次數(shù)张肾,LayoutBoundsInterceptor 來高亮 View 的邊界。

Calligraphy 使用這一技巧方便的為 TextView 添加字體锚扎。它使用了ViewPump 庫吞瞪,在 wiki 中提供了一些可能的使用方式。

最后驾孔,Google 的 Material Components for Android 通過自定義 AppCompatViewInflaterButton 替換為 MaterialButton 芍秆。

原文作者:ahmed el-helw

原文鏈接:AppCompat View Inflation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市助币,隨后出現(xiàn)的幾起案子浪听,更是在濱河造成了極大的恐慌,老刑警劉巖眉菱,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迹栓,死亡現(xiàn)場離奇詭異,居然都是意外死亡俭缓,警方通過查閱死者的電腦和手機(jī)克伊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來华坦,“玉大人愿吹,你說我怎么就攤上這事∠Ы悖” “怎么了犁跪?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長歹袁。 經(jīng)常有香客問我坷衍,道長,這世上最難降的妖魔是什么条舔? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任枫耳,我火速辦了婚禮,結(jié)果婚禮上孟抗,老公的妹妹穿的比我還像新娘迁杨。我一直安慰自己,他們只是感情好凄硼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布铅协。 她就那樣靜靜地躺著,像睡著了一般摊沉。 火紅的嫁衣襯著肌膚如雪狐史。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音预皇,去河邊找鬼侈玄。 笑死,一個胖子當(dāng)著我的面吹牛吟温,可吹牛的內(nèi)容都是我干的序仙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鲁豪,長吁一口氣:“原來是場噩夢啊……” “哼潘悼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起爬橡,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤治唤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后糙申,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宾添,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年柜裸,在試婚紗的時候發(fā)現(xiàn)自己被綠了缕陕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡疙挺,死狀恐怖扛邑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铐然,我是刑警寧澤蔬崩,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站搀暑,受9級特大地震影響沥阳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜险掀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一沪袭、第九天 我趴在偏房一處隱蔽的房頂上張望湾宙。 院中可真熱鬧樟氢,春花似錦、人聲如沸侠鳄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伟恶。三九已至碴开,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背潦牛。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工眶掌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巴碗。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓朴爬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親橡淆。 傳聞我的和親對象是個殘疾皇子召噩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容