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


mWindow對象的定義和賦值


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


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


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


在Activity中的定義mWindow對象


在Activity中賦值mWindow對象


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è)方法很簡單,就是判斷mDecor為null的話就執(zhí)行installDecor方法


PhoneWindow中的installDecor方法


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


PhoneWindow下面初始化mDecor

簡而言之,就是在這個(gè)方法里面new了一個(gè)DecorView對象,賦值給mDector。

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

著重看下contentParent的初始化


在PhoneWindow類下的generateLayout方法里面的對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對象是什么,是從哪里來的,在哪里定義,在哪里初始化,現(xiàn)在,在這個(gè)地方,如果已經(jīng)明白了mWindow對象的來龍去脈,那么這里就不難理解,我們看下mWindow.setContentView方法。由于Window唯一抽象類是PhoneWindow,那么需要去PhoneWindow里面去找setContentView方法


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)完成了。


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對象,即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里面的setContentView方法對比


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


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


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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容