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

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

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

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


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

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

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

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

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

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

sWindowSession 實際上是 IWindowSession 類型,是一個 Binder 類型,真正的實現類是 System 進程中的 Session。上圖中紅框中就是用 AIDL 獲取 System 進程中 Session 的對象。


圖中的 mService 就是 WMS。至此,Window 已經成功的被傳遞給了 WMS。剩下的工作就全部轉移到系統(tǒng)進程中的 WMS 來完成最終的添加操作。
再看 Activity 接收觸屏事件
上面提到 addView 成功有一個標志就是能夠接收觸屏事件,通過對 setContentView 流程的分析,可以看出添加 View 的操作實質上是 PhoneWindow 在全盤操作,背后負責人是 WMS,反之 Activity 自始至終沒什么參與感。但是我們也知道當觸屏事件發(fā)生之后,Touch 事件首先是被傳入到 Activity,然后才被下發(fā)到布局中的 ViewGroup 或者 View。那么 Touch 事件是如何傳遞到 Activity 上的呢?
ViewRootImpl 中的 setView 方法中,除了調用 IWindowSession 執(zhí)行跨進程添加 View 之外,還有一項重要的操作就是設置輸入事件的處理:

如上圖紅框中所示,設置了一系列的輸入通道。一個觸屏事件的發(fā)生是由屏幕發(fā)起,然后經過驅動層一系列的優(yōu)化計算通過 Socket 跨進程通知 Android Framework 層(實際上就是 WMS),最終屏幕的觸摸事件會被發(fā)送到上圖中的輸入管道中。
這些輸入管道實際上是一個鏈表結構,當某一個屏幕觸摸事件到達其中的 ViewPostImeInputState 時,會經過 onProcess 來處理,如下所示:

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

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

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

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

也就是從這張圖可以理解順序為:
ViewPostImeInputStage.onProcess -> ViewPostImeInputStage.processPointerEvent -> View.dispatchPointerEvent -> PhoneWindow$DecorView.dispatchTouchEvent -> 內部子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é)課主要通過 setContentView 的流程,分析了 Activity、Window、View 之間的關系。整個過程 Activity 表面上參與度比較低,大部分 View 的添加操作都被封裝到 Window 中實現。而 Activity 就相當于 Android 提供給開發(fā)人員的一個管理類,通過它能夠更簡單的實現 Window 和 View 的操作邏輯。
最后再簡單列一下整個流程需要注意的點:
- 一個 Activity 中有一個 window,也就是 PhoneWindow 對象,在 PhoneWindow 中有一個 DecorView,在 setContentView 中會將 layout 填充到此 DecorView 中。
- 一個應用進程中只有一個 WindowManagerGlobal 對象,因為在 ViewRootImpl 中它是 static 靜態(tài)類型。
- 每一個 PhoneWindow 對應一個 ViewRootImple 對象。
- WindowMangerGlobal 通過調用 ViewRootImpl 的 setView 方法,完成 window 的添加過程。
- ViewRootImpl 的 setView 方法中主要完成兩件事情:View 渲染(requestLayout)以及接收觸屏事件。