????????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的情況下)
? ? ? ? 由于該activity是繼承AppCompatActivity,所以在AppCompatActivity下面的setContentView實(shí)現(xiàn)有三個(gè)。相信大家也能看到鸡挠,AppCompatActivity不直接操作view辉饱,而是通過一個(gè)叫AppCompatDelegate的類進(jìn)行view的操作。其中g(shù)etDelegate()方法是這樣的
那么拣展,問題就來了彭沼,這個(gè)mDelegate這個(gè)AppCompatDelegate類是干什么的呢?有什么作用备埃?好姓惑,繼續(xù)跟蹤下去,探索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生命周期的抽象方法蚤氏,如下:
????????看到這里,可能大家心里差不多明白了踊兜,這個(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)的。
????????可以小結(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中可以找到
????????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)行分段分析:
可以看到,在最開始梨水,是先獲取了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)造方法中賦值的奕短。
那么繼續(xù)找下去,會(huì)在AppCompatDelegate中的create方法中找到匀钧,如下圖:
那么繼續(xù)跟蹤翎碑,activity.getWindow又是什么東西呢?接下去會(huì)發(fā)現(xiàn)mWindow對(duì)象是定義在Activity里面的一個(gè)全局變量之斯,mWindow賦值是在Activity的attach方法中賦值的日杈。
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)造方法
????????構(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方法
那么類型為DecorView的實(shí)例mDecor是通過generateDecor方法去初始化的
簡(jiǎn)而言之腋妙,就是在這個(gè)方法里面new了一個(gè)DecorView對(duì)象,賦值給mDector讯榕。
還有個(gè)重要的變量骤素,mContentParent這個(gè),是ViewGroup的實(shí)例愚屁,是通過generateLayout方法進(jìn)行初始化的济竹,注意,generateLayout是需要傳入剛剛初始化好的mDecor對(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方法下半部分
由于根據(jù)主題的設(shè)定不一樣耸棒,這里面的subDecor有不同的賦值,不僅僅只是包括上圖幾項(xiàng)报辱。
繼續(xù)看下去与殃,最關(guān)鍵是是mWindow.setContentView(subDecor);
那么在之前大段篇幅講的是mWindow對(duì)象是什么,是從哪里來的幅疼,在哪里定義米奸,在哪里初始化,現(xiàn)在衣屏,在這個(gè)地方躏升,如果已經(jīng)明白了mWindow對(duì)象的來龍去脈,那么這里就不難理解狼忱,我們看下mWindow.setContentView方法膨疏。由于Window唯一抽象類是PhoneWindow,那么需要去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)完成了。
接下去的就好理解了实檀,同樣也是從android.R.id.content這個(gè)控件id找到父容器contentParent惶洲,按照PhoneWindow中的generateLayoutf方法里面來分析,這里的contentParent和PhoneWindow里面的mContentParent指向的是同一個(gè)控件膳犹。
LayoutInflate.from(mContext).inflate(resId, contentParent);
在類LayoutInflate中找到
當(dāng)然恬吕,如果我們最開始的Activity不是繼承自AppCompatActivity的話,而且繼承Activity须床,那么上述的分析流程是否還是成立的铐料?
答案是肯定的,可以看到
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類里面大量出現(xiàn)的DecorView實(shí)例mDecorView和PhoneWindow里面的ViewGroup類型實(shí)例mContentParent的關(guān)系是什么呢拷呆?這個(gè)可以通過PhoneWindow中的方法generateLayout找到答案。
其中有個(gè)int類型的
layoutResource對(duì)象晨横,發(fā)現(xiàn)在各個(gè)判斷中都有賦值洋腮,那么我們隨便選個(gè)layout去看看
不管是哪個(gè)布局箫柳,其中肯定會(huì)定義一個(gè)FrameLayout手形,并且id固定為"content",那么解析去的mDecor.startChanging和mDecor.onResourceLoaded(mLayoutInflater, layoutResource)大家肯定也才猜想得出來悯恍,作用就是去解析layoutResource的布局库糠,并且添加到mDecor中。
因?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方法。