底層剖析 Window 慕蔚、Activity丐黄、 View 三者關(guān)系課程

這篇課程開頭就說(shuō)在"接觸 Android 開發(fā)時(shí),我始終認(rèn)為它就是負(fù)責(zé)將 layout 布局中的控件渲染繪制出來(lái)的"孔飒。的確灌闺,對(duì)于layout布局怎么跟Activity關(guān)聯(lián)起來(lái)的,都沒有深入的去探究坏瞄。而這篇課程解答了這一問(wèn)題桂对。

Activity 的 setContentView

從最初的 Activity 的 setContentView入手:

setContentView

可以看到是直接調(diào)用了Window的setContentView方法,顯然 Activity 幾乎什么都沒做鸠匀,將操作直接交給了一個(gè) Window 來(lái)處理蕉斜。getWindow 返回的是 Activity 中的全局變量 mWindow,它是 Window 窗口類型。

而這個(gè) Window 對(duì)象的賦值則是在《startActivity 啟動(dòng)過(guò)程分析課程》中有講到過(guò):

通過(guò)反射創(chuàng)建 Activity 對(duì)象宅此,并執(zhí)行其 attach 方法机错。Window 就是在這個(gè)方法中被創(chuàng)建。

在 Activity 的 attach 方法中將 mWindow 賦值給一個(gè) PhoneWindow 對(duì)象父腕,實(shí)際上整個(gè) Android 系統(tǒng)中 Window 只有一個(gè)實(shí)現(xiàn)類毡熏,就是 PhoneWindow。

接下來(lái)調(diào)用 setWindowManager 方法侣诵,將系統(tǒng) WindowManager 傳給 PhoneWindow痢法,如下所示:

最終,在 PhoneWindow 中持有了一個(gè) WindowManagerImpl 的引用杜顺。

PhoneWindow 的 setContentView

回到 PhoneWindow 的 setContentView:

PhoneWindow 的 setContentView

判斷了mContentParent 是否為 null财搁,不為空則調(diào)用 installDecor() 方法初始化 DecorView 和 mContentParent。然后又調(diào)用了 mLayoutInflater.inflate()方法將布局添加到 mContentParent 中躬络。

可以看出在 PhoneWindow 中默認(rèn)有一個(gè) DecorView(實(shí)際上是一個(gè) FrameLayout)尖奔,在 DecorView 中默認(rèn)自帶一個(gè) mContentParent(實(shí)際上是一個(gè) ViewGroup)。我們自己實(shí)現(xiàn)的布局是被添加到 mContentParent 中的穷当,因此經(jīng)過(guò) setContentView 之后提茁,PhoneWindow 內(nèi)部的 View 關(guān)系如下所示:

目前為止 PhoneWindow 中只是創(chuàng)建出了一個(gè) DecorView,并在 DecorView 中填充了我們?cè)?Activity 中傳入的 layoutId 布局馁菜,可是 DecorView 還沒有跟 Activity 建立任何聯(lián)系茴扁,也沒有被繪制到界面上顯示。

剛接觸 Android汪疮,學(xué)習(xí)生命周期時(shí)峭火,經(jīng)常會(huì)看到相關(guān)文檔介紹 Activity 執(zhí)行到 onCreate 時(shí)并不可見,只有執(zhí)行完 onResume 之后 Activity 中的內(nèi)容才是屏幕可見狀態(tài)智嚷。造成這種現(xiàn)象的原因就是卖丸,onCreate 階段只是初始化了 Activity 需要顯示的內(nèi)容,而在 onResume 階段才會(huì)將 PhoneWindow 中的 DecorView 真正的繪制到屏幕上盏道。

在 ActivityThread 的 handleResumeActivity 中稍浆,會(huì)調(diào)用 WindowManager 的 addView 方法將 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:

WindowManger 的 addView 結(jié)果有兩個(gè):

  • DecorView 被渲染繪制到屏幕上顯示猜嘱;
  • DecorView 可以接收屏幕觸摸事件衅枫。

WindowManager 的 addView

PhoneWindow 只是負(fù)責(zé)處理一些應(yīng)用窗口通用的邏輯(設(shè)置標(biāo)題欄,導(dǎo)航欄等)泉坐。但是真正完成把一個(gè) View 作為窗口添加到 WMS 的過(guò)程是由 WindowManager 來(lái)完成的为鳄。

WindowManager 是接口類型裳仆,上文中我們也了解到它真正的實(shí)現(xiàn)者是 WindowManagerImpl 類腕让,看一下它的 addView 方法如下:

WindowManagerImpl 也是一個(gè)空殼,它調(diào)用了 WindowManagerGlobal 的 addView 方法。

WindowMangerGlobal 是一個(gè)單例纯丸,每一個(gè)進(jìn)程中只有一個(gè)實(shí)例對(duì)象偏形。如上圖紅框中所示,在其 addView 方法中觉鼻,創(chuàng)建了一個(gè)最關(guān)鍵的 ViewRootImpl 對(duì)象俊扭,然后通過(guò) ViewRootImpl 的 setView 方法將 view 添加到 WMS 中。

ViewRootImpl 的 setView

  • 第一個(gè)方法可以看到調(diào)用了 requestLayout() 方法坠陈, requestLayout 是刷新布局的操作萨惑,調(diào)用此方法后 ViewRootImpl 所關(guān)聯(lián)的 View 也執(zhí)行 measure - layout - draw 操作,確保在 View 被添加到 Window 上顯示到屏幕之前仇矾,已經(jīng)完成測(cè)量和繪制操作庸蔼。在自定義VIew中有時(shí)候需要重新測(cè)量和繪制的時(shí)候會(huì)調(diào)用這個(gè)方法。而這里調(diào)用了這個(gè)方法則是開始真正的將DecorView渲染繪制贮匕。

  • 第二個(gè)方法調(diào)用了mWindowSession的addToDisplay方法姐仅,這個(gè)方法將 View 添加到 WMS 中。

WindowSession 是 WindowManagerGlobal 中的單例對(duì)象刻盐,初始化代碼如下:

sWindowSession 實(shí)際上是 IWindowSession 類型掏膏,是一個(gè) Binder 類型,真正的實(shí)現(xiàn)類是 System 進(jìn)程中的 Session敦锌。上圖中紅框中就是用 AIDL 獲取 System 進(jìn)程中 Session 的對(duì)象馒疹。

圖中的 mService 就是 WMS。至此乙墙,Window 已經(jīng)成功的被傳遞給了 WMS行冰。剩下的工作就全部轉(zhuǎn)移到系統(tǒng)進(jìn)程中的 WMS 來(lái)完成最終的添加操作。

再看 Activity 接收觸屏事件

上面提到 addView 成功有一個(gè)標(biāo)志就是能夠接收觸屏事件伶丐,通過(guò)對(duì) setContentView 流程的分析悼做,可以看出添加 View 的操作實(shí)質(zhì)上是 PhoneWindow 在全盤操作,背后負(fù)責(zé)人是 WMS哗魂,反之 Activity 自始至終沒什么參與感肛走。但是我們也知道當(dāng)觸屏事件發(fā)生之后,Touch 事件首先是被傳入到 Activity录别,然后才被下發(fā)到布局中的 ViewGroup 或者 View朽色。那么 Touch 事件是如何傳遞到 Activity 上的呢?

ViewRootImpl 中的 setView 方法中组题,除了調(diào)用 IWindowSession 執(zhí)行跨進(jìn)程添加 View 之外葫男,還有一項(xiàng)重要的操作就是設(shè)置輸入事件的處理:

如上圖紅框中所示,設(shè)置了一系列的輸入通道崔列。一個(gè)觸屏事件的發(fā)生是由屏幕發(fā)起梢褐,然后經(jīng)過(guò)驅(qū)動(dòng)層一系列的優(yōu)化計(jì)算通過(guò) Socket 跨進(jìn)程通知 Android Framework 層(實(shí)際上就是 WMS)旺遮,最終屏幕的觸摸事件會(huì)被發(fā)送到上圖中的輸入管道中。

這些輸入管道實(shí)際上是一個(gè)鏈表結(jié)構(gòu)盈咳,當(dāng)某一個(gè)屏幕觸摸事件到達(dá)其中的 ViewPostImeInputState 時(shí)耿眉,會(huì)經(jīng)過(guò) onProcess 來(lái)處理,如下所示:

可以看到在 onProcess 中最終調(diào)用了一個(gè) mView的dispatchPointerEvent 方法鱼响,mView 實(shí)際上就是 之前PhoneWindow 中addView添加的 DecorView鸣剪,而 dispatchPointerEvent 是被 View.java 實(shí)現(xiàn)的,如下所示:

最終調(diào)用了 PhoneWindow 中 Callback的dispatchTouchEvent 方法丈积,那這個(gè) Callback 是不是 Activity 呢筐骇?

在啟動(dòng) Activity 階段,創(chuàng)建 Activity 對(duì)象并調(diào)用 attach 方法時(shí)江滨,有如下一段代碼:

果然將 Activity 自身傳遞給了 PhoneWindow拥褂,再接著看 Activity的dispatchTouchEvent 方法:

Touch 事件在 Activity 中只是繞了一圈最后還是回到了 PhoneWindow 中的 DecorView 來(lái)處理。剩下的就是從 DecorView 開始將事件層層傳遞給內(nèi)部的子 View 中了:

也就是從這張圖可以理解順序?yàn)椋?br> ViewPostImeInputStage.onProcess -> ViewPostImeInputStage.processPointerEvent -> View.dispatchPointerEvent -> PhoneWindow$DecorView.dispatchTouchEvent -> 內(nèi)部子View

例如下面log:

at com.example.helloworld.MainActivity.dispatchTouchEvent(MainActivity.java:103)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2359)
at android.view.View.dispatchPointerEvent(View.java:8698)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4530)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4388)

總結(jié)

這節(jié)課主要通過(guò) setContentView 的流程牙寞,分析了 Activity饺鹃、Window、View 之間的關(guān)系间雀。整個(gè)過(guò)程 Activity 表面上參與度比較低悔详,大部分 View 的添加操作都被封裝到 Window 中實(shí)現(xiàn)。而 Activity 就相當(dāng)于 Android 提供給開發(fā)人員的一個(gè)管理類惹挟,通過(guò)它能夠更簡(jiǎn)單的實(shí)現(xiàn) Window 和 View 的操作邏輯茄螃。

最后再簡(jiǎn)單列一下整個(gè)流程需要注意的點(diǎn):

  • 一個(gè) Activity 中有一個(gè) window,也就是 PhoneWindow 對(duì)象连锯,在 PhoneWindow 中有一個(gè) DecorView归苍,在 setContentView 中會(huì)將 layout 填充到此 DecorView 中。
  • 一個(gè)應(yīng)用進(jìn)程中只有一個(gè) WindowManagerGlobal 對(duì)象运怖,因?yàn)樵?ViewRootImpl 中它是 static 靜態(tài)類型拼弃。
  • 每一個(gè) PhoneWindow 對(duì)應(yīng)一個(gè) ViewRootImple 對(duì)象。
  • WindowMangerGlobal 通過(guò)調(diào)用 ViewRootImpl 的 setView 方法摇展,完成 window 的添加過(guò)程吻氧。
  • ViewRootImpl 的 setView 方法中主要完成兩件事情:View 渲染(requestLayout)以及接收觸屏事件。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咏连,一起剝皮案震驚了整個(gè)濱河市盯孙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祟滴,老刑警劉巖振惰,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異垄懂,居然都是意外死亡骑晶,警方通過(guò)查閱死者的電腦和手機(jī)痛垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)透罢,“玉大人榜晦,你說(shuō)我怎么就攤上這事冠蒋∮鹌裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抖剿,是天一觀的道長(zhǎng)朽寞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)斩郎,這世上最難降的妖魔是什么脑融? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮缩宜,結(jié)果婚禮上肘迎,老公的妹妹穿的比我還像新娘。我一直安慰自己锻煌,他們只是感情好妓布,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宋梧,像睡著了一般匣沼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捂龄,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天释涛,我揣著相機(jī)與錄音,去河邊找鬼倦沧。 笑死唇撬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的展融。 我是一名探鬼主播局荚,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愈污!你這毒婦竟也來(lái)了耀态?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暂雹,失蹤者是張志新(化名)和其女友劉穎首装,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杭跪,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仙逻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年驰吓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片系奉。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡檬贰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缺亮,到底是詐尸還是另有隱情翁涤,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布萌踱,位于F島的核電站葵礼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏并鸵。R本人自食惡果不足惜鸳粉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望园担。 院中可真熱鬧届谈,春花似錦、人聲如沸弯汰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝙泼。三九已至程剥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汤踏,已是汗流浹背织鲸。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溪胶,地道東北人搂擦。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哗脖,于是被迫代替她去往敵國(guó)和親瀑踢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354