這篇課程開頭就說在"接觸 Android 開發(fā)時,我始終認(rèn)為它就是負(fù)責(zé)將 layout 布局中的控件渲染繪制出來的"。的確,對于layout布局怎么跟Activity關(guān)聯(lián)起來的,都沒有深入的去探究。而這篇課程解答了這一問題。
Activity 的 setContentView
從最初的 Activity 的 setContentView入手:

可以看到是直接調(diào)用了Window的setContentView方法,顯然 Activity 幾乎什么都沒做,將操作直接交給了一個 Window 來處理。getWindow 返回的是 Activity 中的全局變量 mWindow,它是 Window 窗口類型。
而這個 Window 對象的賦值則是在《startActivity 啟動過程分析課程》中有講到過:

通過反射創(chuàng)建 Activity 對象,并執(zhí)行其 attach 方法。Window 就是在這個方法中被創(chuàng)建。

在 Activity 的 attach 方法中將 mWindow 賦值給一個 PhoneWindow 對象,實(shí)際上整個 Android 系統(tǒng)中 Window 只有一個實(shí)現(xiàn)類,就是 PhoneWindow。
接下來調(diào)用 setWindowManager 方法,將系統(tǒng) WindowManager 傳給 PhoneWindow,如下所示:


最終,在 PhoneWindow 中持有了一個 WindowManagerImpl 的引用。
PhoneWindow 的 setContentView
回到 PhoneWindow 的 setContentView:

判斷了mContentParent 是否為 null,不為空則調(diào)用 installDecor() 方法初始化 DecorView 和 mContentParent。然后又調(diào)用了 mLayoutInflater.inflate()方法將布局添加到 mContentParent 中。
可以看出在 PhoneWindow 中默認(rèn)有一個 DecorView(實(shí)際上是一個 FrameLayout),在 DecorView 中默認(rèn)自帶一個 mContentParent(實(shí)際上是一個 ViewGroup)。我們自己實(shí)現(xiàn)的布局是被添加到 mContentParent 中的,因此經(jīng)過 setContentView 之后,PhoneWindow 內(nèi)部的 View 關(guān)系如下所示:

目前為止 PhoneWindow 中只是創(chuàng)建出了一個 DecorView,并在 DecorView 中填充了我們在 Activity 中傳入的 layoutId 布局,可是 DecorView 還沒有跟 Activity 建立任何聯(lián)系,也沒有被繪制到界面上顯示。
剛接觸 Android,學(xué)習(xí)生命周期時,經(jīng)常會看到相關(guān)文檔介紹 Activity 執(zhí)行到 onCreate 時并不可見,只有執(zhí)行完 onResume 之后 Activity 中的內(nèi)容才是屏幕可見狀態(tài)。造成這種現(xiàn)象的原因就是,onCreate 階段只是初始化了 Activity 需要顯示的內(nèi)容,而在 onResume 階段才會將 PhoneWindow 中的 DecorView 真正的繪制到屏幕上。
在 ActivityThread 的 handleResumeActivity 中,會調(diào)用 WindowManager 的 addView 方法將 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:

WindowManger 的 addView 結(jié)果有兩個:
- DecorView 被渲染繪制到屏幕上顯示;
- DecorView 可以接收屏幕觸摸事件。
WindowManager 的 addView
PhoneWindow 只是負(fù)責(zé)處理一些應(yīng)用窗口通用的邏輯(設(shè)置標(biāo)題欄,導(dǎo)航欄等)。但是真正完成把一個 View 作為窗口添加到 WMS 的過程是由 WindowManager 來完成的。
WindowManager 是接口類型,上文中我們也了解到它真正的實(shí)現(xiàn)者是 WindowManagerImpl 類,看一下它的 addView 方法如下:

WindowManagerImpl 也是一個空殼,它調(diào)用了 WindowManagerGlobal 的 addView 方法。
WindowMangerGlobal 是一個單例,每一個進(jìn)程中只有一個實(shí)例對象。如上圖紅框中所示,在其 addView 方法中,創(chuàng)建了一個最關(guān)鍵的 ViewRootImpl 對象,然后通過 ViewRootImpl 的 setView 方法將 view 添加到 WMS 中。
ViewRootImpl 的 setView

第一個方法可以看到調(diào)用了 requestLayout() 方法, requestLayout 是刷新布局的操作,調(diào)用此方法后 ViewRootImpl 所關(guān)聯(lián)的 View 也執(zhí)行 measure - layout - draw 操作,確保在 View 被添加到 Window 上顯示到屏幕之前,已經(jīng)完成測量和繪制操作。在自定義VIew中有時候需要重新測量和繪制的時候會調(diào)用這個方法。而這里調(diào)用了這個方法則是開始真正的將DecorView渲染繪制。
第二個方法調(diào)用了mWindowSession的addToDisplay方法,這個方法將 View 添加到 WMS 中。
WindowSession 是 WindowManagerGlobal 中的單例對象,初始化代碼如下:

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


圖中的 mService 就是 WMS。至此,Window 已經(jīng)成功的被傳遞給了 WMS。剩下的工作就全部轉(zhuǎn)移到系統(tǒng)進(jìn)程中的 WMS 來完成最終的添加操作。
再看 Activity 接收觸屏事件
上面提到 addView 成功有一個標(biāo)志就是能夠接收觸屏事件,通過對 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è)置了一系列的輸入通道。一個觸屏事件的發(fā)生是由屏幕發(fā)起,然后經(jīng)過驅(qū)動層一系列的優(yōu)化計(jì)算通過 Socket 跨進(jìn)程通知 Android Framework 層(實(shí)際上就是 WMS),最終屏幕的觸摸事件會被發(fā)送到上圖中的輸入管道中。
這些輸入管道實(shí)際上是一個鏈表結(jié)構(gòu),當(dāng)某一個屏幕觸摸事件到達(dá)其中的 ViewPostImeInputState 時,會經(jīng)過 onProcess 來處理,如下所示:

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

最終調(diào)用了 PhoneWindow 中 Callback的dispatchTouchEvent 方法,那這個 Callback 是不是 Activity 呢?
在啟動 Activity 階段,創(chuàng)建 Activity 對象并調(diào)用 attach 方法時,有如下一段代碼:

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

Touch 事件在 Activity 中只是繞了一圈最后還是回到了 PhoneWindow 中的 DecorView 來處理。剩下的就是從 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é)課主要通過 setContentView 的流程,分析了 Activity、Window、View 之間的關(guān)系。整個過程 Activity 表面上參與度比較低,大部分 View 的添加操作都被封裝到 Window 中實(shí)現(xiàn)。而 Activity 就相當(dāng)于 Android 提供給開發(fā)人員的一個管理類,通過它能夠更簡單的實(shí)現(xiàn) Window 和 View 的操作邏輯。
最后再簡單列一下整個流程需要注意的點(diǎn):
- 一個 Activity 中有一個 window,也就是 PhoneWindow 對象,在 PhoneWindow 中有一個 DecorView,在 setContentView 中會將 layout 填充到此 DecorView 中。
- 一個應(yīng)用進(jìn)程中只有一個 WindowManagerGlobal 對象,因?yàn)樵?ViewRootImpl 中它是 static 靜態(tài)類型。
- 每一個 PhoneWindow 對應(yīng)一個 ViewRootImple 對象。
- WindowMangerGlobal 通過調(diào)用 ViewRootImpl 的 setView 方法,完成 window 的添加過程。
- ViewRootImpl 的 setView 方法中主要完成兩件事情:View 渲染(requestLayout)以及接收觸屏事件。