????????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的對應(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方法比較長,進(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è)對象是父類AppCompatDelegateImplBase中的一個(gè)Window類型變量,賦值實(shí)在父類AppCompatDelegateImplBase的構(gòu)造方法中賦值的。

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

那么繼續(xù)跟蹤,activity.getWindow又是什么東西呢?接下去會(huì)發(fā)現(xiàn)mWindow對象是定義在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è)方法很簡單,就是判斷mDecor為null的話就執(zhí)行installDecor方法

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

簡而言之,就是在這個(gè)方法里面new了一個(gè)DecorView對象,賦值給mDector。
還有個(gè)重要的變量,mContentParent這個(gè),是ViewGroup的實(shí)例,是通過generateLayout方法進(jìn)行初始化的,注意,generateLayout是需要傳入剛剛初始化好的mDecor對象的。
著重看下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對象是什么,是從哪里來的,在哪里定義,在哪里初始化,現(xiàn)在,在這個(gè)地方,如果已經(jīng)明白了mWindow對象的來龍去脈,那么這里就不難理解,我們看下mWindow.setContentView方法。由于Window唯一抽象類是PhoneWindow,那么需要去PhoneWindow里面去找setContentView方法

????????不知道大家還記得,mContentParent是什么?它是一個(gè)ViewGroup對象,并且是通過findViewById找到id為content的控件來的。那么我們在AppCompatDelegateImplV9里面的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對象里面的父容器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對象,即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對象,該對象是需要根據(jù)主題屬性inflate成一個(gè)ViewGroup對象(包含是否是懸浮的、是否有ActionBar、是不是OverlayActionMode等等),最終是需要用PhoneWindow對象mWindow調(diào)用setContentView方法,將該具有AppCompat主題屬性的mSubDecor當(dāng)作參數(shù)傳過去,添加到由PhoneWindow通過findViewById方法找到id為content的控件mContentParent調(diào)用addView方法,將mSubDecor添加到根ViewGroup即mContentParent中去。
?那么相對的,如果是在Activity中,則會(huì)簡單很多,不會(huì)有AppCompatDelegate對象,直接會(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里面完成的,可以對比一下

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

其中有個(gè)int類型的
layoutResource對象,發(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的對象,它在Activity被加載進(jìn)來了,那么它是如何顯示在屏幕上的呢?需要借助于ActivityThread的performResumeActivity方法。需要使用WindowManager對象調(diào)用addView方法。