這篇課程開頭就說(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入手:
可以看到是直接調(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:
判斷了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)以及接收觸屏事件。