Android中的View加載流程(從源碼角度分析)

????????Android開發(fā)者都知道要想建立一個(gè)頁面蒜哀,最普遍常見的做法就是新建一個(gè)Activity,并且在res/layout中新建一個(gè)Layout布局回官,然后Activity繼承自Activity或者AppCompatActivity之后重寫onCreate方法若治,最后使用setContentView(resId)讓這個(gè)Activity和布局id為resId的布局產(chǎn)生聯(lián)系,這樣跳轉(zhuǎn)到這個(gè)Activity的時(shí)候就可以顯示resId的布局了剧防。

? ? ? ? 那么大家是否想過了,為什么setContentView()這個(gè)方法就可以將一個(gè)xml格式的布局文件顯示在手機(jī)屏幕上辫樱?在它的后面系統(tǒng)究竟做了什么操作峭拘?



? ? ? ? 可以看看setContentView()的源碼是這樣的(注意:這個(gè)是基于Android api26的情況下)


AppCompatActivity的setContentView實(shí)現(xiàn)


? ? ? ? 由于該activity是繼承AppCompatActivity,所以在AppCompatActivity下面的setContentView實(shí)現(xiàn)有三個(gè)。相信大家也能看到鸡挠,AppCompatActivity不直接操作view辉饱,而是通過一個(gè)叫AppCompatDelegate的類進(jìn)行view的操作。其中g(shù)etDelegate()方法是這樣的


AppCompatActivity里面的getDelegate方法


那么拣展,問題就來了彭沼,這個(gè)mDelegate這個(gè)AppCompatDelegate類是干什么的呢?有什么作用备埃?好姓惑,繼續(xù)跟蹤下去,探索AppCompatDelegate.create方法


AppCompatDelegate.create方法


注意瓜喇,create方法是定義在AppCompatDelegate這個(gè)類下面的靜態(tài)方法,一般普通的activity都會(huì)調(diào)用最上面的那個(gè)create(activity,? activity.getWindow(), callback)方法歉糜,本質(zhì)來說最終都會(huì)調(diào)用最下面的需要判斷Build.VERSION.SDK_INT版本的create方法乘寒。那么其中的這些個(gè)AppCompatDelegateImplXX的關(guān)系是這樣的

AppCompatDelegateImplN? ?extends? ?AppCompatDelegateImplV23

AppCompatDelegateImplV23 extends? AppCompatDelegateImplV14

AppCompatDelegateImplV14 extends? AppCompatDelegateImplV11

AppCompatDelegateImplV11 extends? AppCompatDelegateImplV9

AppCompatDelegateImplV9? ?extends? AppCompatDelegateImplBase

那么這個(gè)一步步繼承過來到最后的AppCompatDelegateImplBase又是什么呢?會(huì)發(fā)現(xiàn)匪补,這個(gè)AppCompatDelegateImplBase是繼承AppCompateDelegate的伞辛。那么回過頭來看看AppCompatDelegate這個(gè)類,首先它是個(gè)抽象類夯缺,其次這個(gè)類里面定義了很多activity生命周期的抽象方法蚤氏,如下:


AppCompatDelegate里面定義的部分抽象方法


????????看到這里,可能大家心里差不多明白了踊兜,這個(gè)AppCompatDelegate更像是個(gè)代理的作用竿滨,所有不同版本的activity的生命周期方法也好、setContentView等方法也好捏境,都可以通過繼承AppCompatDelegate這個(gè)類產(chǎn)生不同的操作于游。比如,在api是26的手機(jī)上垫言,進(jìn)行setContentView實(shí)際上是AppCompatDelegate的子類AppCompatImplN的setContentView方法贰剥;那么同理在api是10的手機(jī)上,進(jìn)行setContentView實(shí)際上是AppCompatDelegate的子類AppCompatDelegateImplV9的setContentView方法筷频。從Java語言角度來看蚌成,這是多態(tài)和繼承的典型表現(xiàn)。AppCompatDelegate也是官方實(shí)現(xiàn)夜間模式最好的工具凛捏。

? ? ? ? 繼續(xù)往下走担忧,在眾多的AppCompatDelegateImplBase的實(shí)現(xiàn)類中,除了AppCompatDelegateImplV9這個(gè)實(shí)現(xiàn)類以外坯癣,發(fā)現(xiàn)均沒有重寫setContentView這個(gè)方法涵妥,那么最終activity中的setContentView經(jīng)過一系列的輾轉(zhuǎn),最終是在這里面實(shí)現(xiàn)的。


AppCompatDelegateImplV9中的setContentView方法

????????可以小結(jié)一下蓬网,在繼承自AppCompatActivity的activity中窒所,setContentView方法在系統(tǒng)根據(jù)不同的api版本找到AppCompatDelegate的對(duì)應(yīng)版本的實(shí)現(xiàn)子類,經(jīng)過一系列的繼承帆锋,最終會(huì)在AppCompatDelegateImplV9中進(jìn)行實(shí)現(xiàn)吵取。

? ? ? ? 那么開始分析setContentView方法中僅僅只有五行的代碼:


? 第一個(gè)是ensureSubDecor這個(gè)方法。這個(gè)方法在AppCompatDelegateImplV9中可以找到


AppCompatDelegateImplV9中的ensureSubDecor方法

????????mSubDecorInstalled是個(gè)boolean類型的變量锯厢,這個(gè)變量是用來標(biāo)識(shí)window sub-decor layout布局是否初始化的皮官,在mSubDecor初始化后會(huì)發(fā)現(xiàn)mSubDecorInstalled會(huì)被賦值為true。那么變量mSubDecor是什么東西呢实辑?往前找到定義變量的地方捺氢,會(huì)發(fā)現(xiàn)

private ViewGroup mSubDecor;

那么,這個(gè)mSubDecor是個(gè)ViewGroup剪撬,好摄乒,接下來來看createSubDecor方法,看看這個(gè)ViewGroup類型的mSubDecor是如何創(chuàng)建出來的残黑。

createSubDecor方法比較長(zhǎng)馍佑,進(jìn)行分段分析:


createSubDecor方法的上部分


可以看到,在最開始梨水,是先獲取了AppCompatTheme屬性的TypedArray拭荤,然后我們會(huì)找到一個(gè)經(jīng)常出現(xiàn)的一個(gè)異常

"You need to use a Theme.AppCompat theme (or descendant) with this activity."

也就是經(jīng)常說的使用了AppCompatActivity卻沒有指定Theme.AppCompat主題。

其中疫诽,最關(guān)鍵的一句話:mWindow.getDecorView()舅世。這句話放這里是什么意思呢?通過注釋大概了解到是要確保Window已經(jīng)初始化了該Window的decor奇徒。

那么先來研究一下mWindow.getDecorView這個(gè)方法歇终。首先,這個(gè)mWindow是個(gè)全局變量逼龟,那么它在哪里初始化賦值的呢评凝?我們通過跟蹤,會(huì)發(fā)現(xiàn)mWindow這個(gè)對(duì)象是父類AppCompatDelegateImplBase中的一個(gè)Window類型變量腺律,賦值實(shí)在父類AppCompatDelegateImplBase的構(gòu)造方法中賦值的奕短。


mWindow對(duì)象的定義和賦值


那么繼續(xù)找下去,會(huì)在AppCompatDelegate中的create方法中找到匀钧,如下圖:


在AppCompatDelegate中傳進(jìn)來的activity.getWindow方法


那么繼續(xù)跟蹤翎碑,activity.getWindow又是什么東西呢?接下去會(huì)發(fā)現(xiàn)mWindow對(duì)象是定義在Activity里面的一個(gè)全局變量之斯,mWindow賦值是在Activity的attach方法中賦值的日杈。


在Activity中的定義mWindow對(duì)象


在Activity中賦值mWindow對(duì)象


Activity方法attach這個(gè)是涉及到了Activity的啟動(dòng)流程旗们,它是在啟動(dòng)一個(gè)Activity過程中由android.app.ActivityThread.performLaunchActivity()這個(gè)方法調(diào)用的禀晓,暫時(shí)不去深究箍邮。

????????Window是Android里面的一個(gè)抽象類恋博,而PhoneWindow是Window的唯一的實(shí)現(xiàn)類。去繼續(xù)研究PhoneWindow類涨冀,如果有無法打開PhoneWindow這個(gè)源碼的情況填硕,可以找到本地文件下的android.jar包,復(fù)制到Android studio里面的libs目錄下鹿鳖,添加為依賴包扁眯,就可以打開PhoneWindow源碼了。

PhoneWindow的構(gòu)造方法


PhoneWindow的構(gòu)造方法


????????構(gòu)造方法中很重要的一個(gè)全局變量翅帜,mDecor姻檀,這個(gè)是DecorView類的實(shí)例。那么mDecor = (Decor) preservedWindow.getDecorView()這個(gè)方法是給DecorView類型進(jìn)行賦值的方法涝滴。我們經(jīng)常說的Android最底層的布局是DecorView绣版,那么實(shí)際上DecorView是一個(gè)繼承自FrameLayout的自定義布局。那么如何mDecor是如何初始化的呢狭莱?

繼續(xù)看getDecorView這個(gè)方法僵娃,這個(gè)方法很簡(jiǎn)單概作,就是判斷mDecor為null的話就執(zhí)行installDecor方法


PhoneWindow中的installDecor方法


那么類型為DecorView的實(shí)例mDecor是通過generateDecor方法去初始化的


PhoneWindow下面初始化mDecor

簡(jiǎn)而言之腋妙,就是在這個(gè)方法里面new了一個(gè)DecorView對(duì)象,賦值給mDector讯榕。

還有個(gè)重要的變量骤素,mContentParent這個(gè),是ViewGroup的實(shí)例愚屁,是通過generateLayout方法進(jìn)行初始化的济竹,注意,generateLayout是需要傳入剛剛初始化好的mDecor對(duì)象的霎槐。

著重看下contentParent的初始化


在PhoneWindow類下的generateLayout方法里面的對(duì)contentParent的初始化

有沒有發(fā)現(xiàn)很熟悉送浊,findViewById方法,里面的ID_ANDROID_CONTENT實(shí)際上就是com.android.internal.R.id.content丘跌。所以PhoneWindow里面的mContentParent實(shí)際上是通過findViewById找到控件id為content而來的袭景。

再回過頭來看,之前所說的在AppCompatDelegateImplV9里面的createSubDecor方法里面的ViewGroup類型mSubDecor是如何初始化的呢闭树?

繼續(xù)來看createSubDecor方法下半部分


createSubDecor中的下班部分(省略部分賦值)

由于根據(jù)主題的設(shè)定不一樣耸棒,這里面的subDecor有不同的賦值,不僅僅只是包括上圖幾項(xiàng)报辱。

繼續(xù)看下去与殃,最關(guān)鍵是是mWindow.setContentView(subDecor);


createSubDecor方法里面的mWindow.setContentView方法

那么在之前大段篇幅講的是mWindow對(duì)象是什么,是從哪里來的幅疼,在哪里定義米奸,在哪里初始化,現(xiàn)在衣屏,在這個(gè)地方躏升,如果已經(jīng)明白了mWindow對(duì)象的來龍去脈,那么這里就不難理解狼忱,我們看下mWindow.setContentView方法膨疏。由于Window唯一抽象類是PhoneWindow,那么需要去PhoneWindow里面去找setContentView方法


PhoneWindow里面的setContentView方法

????????不知道大家還記得钻弄,mContentParent是什么佃却?它是一個(gè)ViewGroup對(duì)象,并且是通過findViewById找到id為content的控件來的窘俺。那么我們?cè)贏ppCompatDelegateImplV9里面的setContentView里面的ensureSubDecor里面的createSubDector方法里面饲帅,上半部分已經(jīng)通過mWindow.getDector方法來進(jìn)行g(shù)enerateDector和generateLayout的初始化,即mDector和mContentParent已經(jīng)準(zhǔn)備好了瘤泪,那么在AppCompatDelegateImplV9里面的setContentView里面的ensureSubDecor里面的createSubDector方法里面下半部分的mWindow.setContentView灶泵,最后直接進(jìn)行mContentParent.addView方法,將AppCompatDelegateImplV9里面辛苦創(chuàng)建出來的ViewGroup類型subDecor添加到PhoneWindow對(duì)象里面的父容器mContentParent里面去了对途。

? ? ? ? 那么自此赦邻,最主要最復(fù)雜的ensureSubDecor方法已經(jīng)完成了。


AppCompatDelegateImplV9中的setContentView方法

接下去的就好理解了实檀,同樣也是從android.R.id.content這個(gè)控件id找到父容器contentParent惶洲,按照PhoneWindow中的generateLayoutf方法里面來分析,這里的contentParent和PhoneWindow里面的mContentParent指向的是同一個(gè)控件膳犹。

LayoutInflate.from(mContext).inflate(resId, contentParent);

在類LayoutInflate中找到


在LayoutInflate中的inflate方法



當(dāng)然恬吕,如果我們最開始的Activity不是繼承自AppCompatActivity的話,而且繼承Activity须床,那么上述的分析流程是否還是成立的铐料?

答案是肯定的,可以看到

Activity下面的setContentView

getWindow()方法是返回當(dāng)前的Window對(duì)象豺旬,即mWindow钠惩。那么作為Window唯一的實(shí)現(xiàn)類PhoneWindow,getWindow().setContentView在PhoneWindow中的方法又回到了setContentView方法里面哈垢,所以實(shí)際上是和AppCompatDelegateImplV9里面的createSubDecor里面的mWindow.setContentView一樣的妻柒。Google在推出AppcompatActivity肯定是考慮過與以前版本的Activity兼容的,本質(zhì)上是相通的耘分。

總結(jié)一下举塔,在AppCompatActivity中绑警,系統(tǒng)會(huì)創(chuàng)建一個(gè)類型為ViewGroup的mSubDecor對(duì)象,該對(duì)象是需要根據(jù)主題屬性inflate成一個(gè)ViewGroup對(duì)象(包含是否是懸浮的央渣、是否有ActionBar计盒、是不是OverlayActionMode等等),最終是需要用PhoneWindow對(duì)象mWindow調(diào)用setContentView方法芽丹,將該具有AppCompat主題屬性的mSubDecor當(dāng)作參數(shù)傳過去北启,添加到由PhoneWindow通過findViewById方法找到id為content的控件mContentParent調(diào)用addView方法,將mSubDecor添加到根ViewGroup即mContentParent中去拔第。

?那么相對(duì)的咕村,如果是在Activity中,則會(huì)簡(jiǎn)單很多蚊俺,不會(huì)有AppCompatDelegate對(duì)象懈涛,直接會(huì)調(diào)用mWindow的setContentView方法,殊途同歸泳猬。不過需要注意的一點(diǎn)是批钠,如果直接調(diào)用PhoneWindow里面的setContentView(int resId),那么布局文件的解析工作是需要在這里進(jìn)行的得封;如果是在AppCompatDelegate的createSubDecor方法調(diào)用mWindow.setContentView(View view)埋心,那么在PhoneWindow里面僅僅只是將mSubDecor添加到mContentParent里面而已,布局的解析還是需要在AppCompatDelegateImplV9里面的setContentView里面完成的忙上,可以對(duì)比一下

PhoneWindow里面的setContentView方法對(duì)比


思考:之前在PhoneWindow類里面大量出現(xiàn)的DecorView實(shí)例mDecorView和PhoneWindow里面的ViewGroup類型實(shí)例mContentParent的關(guān)系是什么呢拷呆?這個(gè)可以通過PhoneWindow中的方法generateLayout找到答案。


PhoneWindow類的generateLayout方法節(jié)選


其中有個(gè)int類型的

layoutResource對(duì)象晨横,發(fā)現(xiàn)在各個(gè)判斷中都有賦值洋腮,那么我們隨便選個(gè)layout去看看


系統(tǒng)提供的R.layout.screen_simple

不管是哪個(gè)布局箫柳,其中肯定會(huì)定義一個(gè)FrameLayout手形,并且id固定為"content",那么解析去的mDecor.startChanging和mDecor.onResourceLoaded(mLayoutInflater, layoutResource)大家肯定也才猜想得出來悯恍,作用就是去解析layoutResource的布局库糠,并且添加到mDecor中。


DecorView類里面的onResourcesLoaded方法

因?yàn)镈ecorView本身就是個(gè)FrameLayout涮毫,所以自然而然的可以使用addView方法

到這一步瞬欧,我們心中大概很清楚,mDecor是Activity的底層View罢防,其中有個(gè)固定id為content的控件(實(shí)際上都是FrameLayout)艘虎,PhoneWindow可以通過findViewById直接獲取到該FrameLayout,例如PhoneWindow里面的mContentParent就是這么來的咒吐,然后我們所有的在AppCompatActivity里面也好野建,還是本身就在Activity里面也好属划,所有控件的添加、移除等都是通過mContentParent來進(jìn)行控制操作的候生。

那么自此同眯,setContentView方法已經(jīng)差不多研究完了。其實(shí)還遺留下一個(gè)問題唯鸭,那么就是mDecorView這個(gè)代表是一個(gè)FrameLayout的對(duì)象须蜗,它在Activity被加載進(jìn)來了,那么它是如何顯示在屏幕上的呢目溉?需要借助于ActivityThread的performResumeActivity方法明肮。需要使用WindowManager對(duì)象調(diào)用addView方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缭付,一起剝皮案震驚了整個(gè)濱河市晤愧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛉腌,老刑警劉巖官份,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烙丛,居然都是意外死亡舅巷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門河咽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钠右,“玉大人,你說我怎么就攤上這事忘蟹§浚” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵媚值,是天一觀的道長(zhǎng)狠毯。 經(jīng)常有香客問我,道長(zhǎng)褥芒,這世上最難降的妖魔是什么嚼松? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锰扶,結(jié)果婚禮上献酗,老公的妹妹穿的比我還像新娘。我一直安慰自己坷牛,他們只是感情好罕偎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著京闰,像睡著了一般颜及。 火紅的嫁衣襯著肌膚如雪痴怨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天器予,我揣著相機(jī)與錄音浪藻,去河邊找鬼。 笑死乾翔,一個(gè)胖子當(dāng)著我的面吹牛爱葵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播反浓,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼萌丈,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了雷则?” 一聲冷哼從身側(cè)響起辆雾,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎月劈,沒想到半個(gè)月后度迂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猜揪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年惭墓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而姐。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腊凶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拴念,到底是詐尸還是另有隱情钧萍,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布政鼠,位于F島的核電站风瘦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缔俄。R本人自食惡果不足惜弛秋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一器躏、第九天 我趴在偏房一處隱蔽的房頂上張望俐载。 院中可真熱鬧,春花似錦登失、人聲如沸遏佣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽状婶。三九已至意敛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膛虫,已是汗流浹背草姻。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稍刀,地道東北人撩独。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像账月,于是被迫代替她去往敵國(guó)和親综膀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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