手把手教你讀懂源碼,View的加載流程詳細剖析

最近想要理清我們的View是如何加載到界面中的,最好的方式就是分析源代碼,這里一同分享給有需要的朋友們。內容較多,需要一定的耐心,請斟酌學習!

我們都知道,在開發Android應用程序時,經常會在Activity的onCreate方法里調用setContentView方法,將布局文件或者View對象傳入,但是很多人并沒有去分析后續是如何加載到面并顯示出來的,接下來就順藤摸瓜將其摘下來,查看的是Android 7.1源碼。

1、從setContentView 方法開始摸索

就簡單從HelloWorld工程的onCreate方法開始吧:

查看其父類Activity的setContentView方法,代碼如下:

可以看出這里先得到一個 Window 對象,然后調用 Window 對象的setContentView方法。

這樣分析Activity中的 setContentView 方法可以看到,界面繪制并不是由 Activity 完成的,是調用了 Window 類的 setContentView 來實現的。

所以我們繼續查看 Window 類的代碼:

發現Window 類其實是一個抽象類,且 setContentView 是一個抽象方法。所以其具體實現是由 Window類的實現類來完成的(后面我們會知道該實現類是PhoneWindow)。

2、分析Window類的實現類

為了找出Window類的具體實現類,回到Activity的setContentView方法,然后進入getWindow方法:

getWindow 方法很簡單,只是返回一個Window對象,那么Window對象到底是在哪兒實例化的呢?接著我們繼續尋找Window對象的實例化代碼,最終確認在Activity類的attach方法(attach的調用后續再分析),查看attach方法的源代碼:

由此可見上面的Window對象就是PhoneWindow對象,所以我們從Activity的setContentView方法定位到了PhoneWindow的setContentView方法。

PhoneWindow的構造方法非常簡單,就是獲取了LayoutInflater對象,為了后續加載xml布局只有文件:

3、繼續分析PhoneWindow類的setContentView方法

繼續進入PhoneWindow類,查看setContentView方法源代碼:

從源碼可以知道,這里主要包括三個步驟:

如果父容器為空則初始化父容器,否則移除所有子視圖;

調用LayoutInflater類的inflate方法將xml布局文件加載到父容器;

回調Callback通知ContentView發生改變,其中的Callback可能由Activity實現。

后面兩步比較簡單,這里主要來看第一步的父容器初始化流程,進入PhoneWindow類的installDecor方法:

這個方法的代碼有點兒長,這里只截取重要部分,主要操作包括三部分:

調用generateDecor()創建出mDecor,即DecorView對象;

generateLayout(mDecor)傳入mDecor對象,生成mContentParent ;

設置標題欄信息。

首先查看generateDecor方法,源代碼如下:

這個方法非常簡單,就是創建了一個DecorView對象,并返回出去。關于DecorView的具體內容可以查看其構造方法:

這里也是比較簡單的,從這里知道了DecorView是一個FrameLayout。DecorView是Activity的頂級View,一般來說它內部包含標題欄和內容欄(加載布局文件layout.xml,即mContentParent)。內容欄是一定存在的,并且具體的id是‘content’。因此這個時候創建出的DecorView還是一個空白的FrameLayout。先不要急,這是怎么知道的,后面會繼續分析。

繼續回到installDecor方法中調用的generateLayout方法:

這個方法代碼非常多,我們只需要關注重點即可。

首先獲取Application android:theme=/, Activity/節點指定的themes或者代碼;

然后獲取窗口Features, 設置相應的修飾布局文件,這些xml文件位于frameworks/base/core/res/res/layout下;

接著調用了DecorView的onResourcesLoaded方法將上面選定的布局文件inflate為View,添加到DecorView中;

找到id為content的framlayout賦給mContentParent,由于已經將屏幕View加為mDecor的子View,因此mContentParent也是mDecor的子View;

設置mDecor的背景和標題。

這里我們先隨便找一個布局文件,如screen_simple.xml:

就會驚奇的發現此LinearLayout就是Activity的界面,由兩部分組成 ActionBar + content。從布局文件就可以認證上述所說的content,源碼中id為@android:id/content的FrameLayout就是內容區域,其會賦值給PhoneWindow類中的屬性mContentParent,分析后續代碼后再來細看。

回到generateLayout方法,查看調用的onResourcesLoaded方法:

主要就是將適配的布局文件加載進來生成root視圖,調用addView方法添加到DecorView視圖。

繼續回到generateLayout方法,將窗口修飾布局文件中id=@android:id/content的View賦值給mContentParent, 后續自定義的view和layout都將是其子View。處理完成后就將mContentParent返回。

再回到PhoneWindow的setContentView方法中, 繼續調用了mLayoutInflater.inflate(layoutResID, mContentParent),在這里就是把我們寫的布局文件通過inflater加入到mContentParent中。這樣我們寫的布局文件成功的添加到DecorView中的mContentParent。

現在只是完成了DecorView的創建并初始化,我們還需要把這個創建并初始化完DecorView添加并顯示到屏幕上,這里我們就需要用到WindowManager。

4、Activity入口

很多同學會有疑問,上面的attach方法是在哪里調用的?然后View又是如何顯示出來的?我們知道,Activity的入口就是ActivityThread類,我們找到其中的handleMessage處理的代碼:

這里的代碼非常多,但是仔細去查看,會發現很多有用的消息,會根據不同的message調用對應的方法,如handleLaunchActivity()、handleResumeActivity()、handlePauseActivity()、handleStopActivity()等,從方法名就能大概猜出來起用途。這里我們來分別查看handleLaunchActivity()和handleResumeActivity()方法,其他方法類似。

5、performLaunchActivity

首先來看handleLaunchActivity方法,源碼如下:

主要調用了performLaunchActivity方法,繼續查看performLaunchActivity源代碼:

這里通過Activity的類名構建一個Activity對象,可以查看Instrumentation類的newActivity方法:

繼續回到performLaunchActivity的源代碼:

這里是不是看到了非常熟悉的方法,就是我們前面看到的Activity類的attach()方法。就是在attach方法里面初始化PhoneWindow對象的。

后面調用了Instrumentation類的callActivityOnCreate方法,源代碼如下:

這里主要通過Instrumentation對象執行Activity的onCreate()方法,Activity的生命周期方法都是由Instrumentation對象來調用的,這里不再詳細深入。

到目前為止,View只是加載到了Activity,并沒有顯示出來,繼續研究ActivityThread的handleResumeActivity方法。

6、performResumeActivity

首先來看handleResumeActivity方法:

這里首先調用了performResumeActivity方法,查看performResumeActivity源代碼:

繼續調用了Activity的performResume方法,繼續深入源代碼:

可以看到仍然是通過Instrumentation類調用了Activity的onResume()方法。

然后回到handleResumeActivity方法,找到下面的wm.addView()方法:

這個方法非常關鍵,wm是上面a.getWindowManager()獲取到的mWindowManger對象,而這個對象是WindowManagerImpl。

7、addView

繼續進入WindowManagerImpl類的addView方法:

其實WindowManagerImpl類的方法大部分都是代理的WindowManagerGlobal的方法。繼續進入WindowManagerGlobal類的addView方法:

從上面的代碼可以看出,addView方法中,創建了一個ViewRootImpl對象,然后調用ViewRootImpl.setView()方法,繼續查看setView()方法。

該方法首先將傳進來的參數view賦值給mView,mView將是這個對象所認識的root節點,也是整個Activity的root的節點,即DecorView。

接著調用了requestLayout()方法,首次調度執行 layout,這里會觸發 onAttachToWindow 和 創建 Surface方法。深入查看ViewRootImpl中requestLayout()方法:

該方法首先檢查了是否在主線程,然后就執行了scheduleTraversals()方法。

這里需要注意的就是Runnable對象,繼續往后看:

這個Runnable的run()方法中,調用了doTraversal()方法:

可以看到doTraversal()方法又調用了performTraversals()方法:

這個方法非常長,內部邏輯也很復雜,但是主體邏輯很清晰。其執行的過程可簡單的概括為:是否需要重新計算視圖的大小(measure)、是否需要重新布局視圖的位置(layout),以及是否需要重繪(Draw)。也就是我們常說的View的繪制流程,由于這里涉及的內容實在太多,關于View的繪制后續再分享。

回到ViewRootImpl類的setView()方法,繼續查看源碼:

從這里可以看到view的父親注冊為自己,于是mDecor知道了自己父親是誰,即整個Activity設置了一個根節點,在此之前調用setContentView()只是將自己的layout布局add到PhoneWindow.mContentParent,但是mDecor并不知道自己的parent是誰,現在整個view的樹形結構中有了根節點,也就是ViewRootImpl,那么requestLayout()就有效了,就可以進行后面的measure、layout、draw三步操作了。


總結

那么最后再來一個精簡的總結,加深理解。

用戶在Activity中調用setContentView,然后調用Window的setContentView,這時會檢查DecorView是否存在,如果不存在則創建DecorView對象,然后把我們自己的 View 添加到 DecorView 中。

這里可以用一個Activity層次關系圖來表示,會更加直觀清晰。

如果把以上這些流程梳理通透了,那么在開發中可以為我們節省不少時間了,也便于一些框架設計,也可以方便在系統的理解上實現出來多種定制任務。而且在一些中高級開發面試的時候,也會經常被問及到這方面的內容。如果還有疑問的童鞋,歡迎留言繼續討論。

今天就先分享到這里,后續將推出更多精彩內容,歡迎一起探討學習進步。

此文章版權為微信公眾號分享達人秀(ShareExpert)——鑫鱻所有,若轉載請備注出處,特此聲明!


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容