天天看點

源碼閱讀分析 - Window底層原理與系統架構

做了一段的時間的 android 我們就開始聽到有人說 AMS、WMS、Window、WindowManager、WindowManagerService等等這些詞彙,可能了解但是腦海裡未必有架構圖, 這次我們就從源碼的角度來了解一下。在閱讀本文之前希望你可以花點時間了解下面幾篇文章:

1. 插件式換膚架構搭建 - setContentView源碼閱讀

2. Android程序間的通信 - IPC(機制)Binder的原理和源碼閱讀

2. Android插件化架構 - Activity的啟動流程分析

3. 源碼解析 - View的繪制流程

上面幾篇文章和我今天的這篇文章有着千絲萬縷的聯系,我為什麼可以把他們分開來?有很重要的一點,我們是需要用到才會去看源碼,打個比方我想做一個皮膚切換的功能,那麼我肯定需要去了解布局資源的加載過程,而這個你僅僅從網上看看别人寫好的文章或者 copy 幾行代碼相信你應該很難做到,當然去 github 上面選個 demo 用用也行,但你心裡不覺得少了點什麼嗎?還有我們最好能夠點到即止,帶着疑問,隻需要清楚我們想要了解什麼,今天的很多源碼或多或少都會涉及到上面幾篇文章的知識點但我們不用管。相信我,隻要你能夠多對着别人的文章自己打開源碼看看,随着時間的推移當我們再看系統源碼的時候就會得心應手,解決問題再也不是靠蒙和試(如果你在開發過程中有時候靠蒙可以在文章末尾刷個贊),而且如果心中有整個 android 應用層的源碼架構圖,對于開發和解決問題方面屢試不爽(如果你能看懂 native 層的源碼更好)。

我們隻是知道 setContentView 方法可以設定顯示我們的布局,這篇 插件式換膚架構搭建 - setContentView源碼閱讀 文章隻是帶大家了解了布局的層次結構,但你了解 Window 和 WindowManager 都幹了些什麼嗎?又或者當我們觸摸一個 EditText 的時候會彈出一個系統的鍵盤,為什麼彈出鍵盤我們的 Activity 布局會自動做調整?我們彈一個 Toast 但就算我們退出 Activity 或是整個應用 Toast 還是會存在?

我們以 Activity 的 setContentView 作為入口可以看到這麼兩行代碼:

getWindow().setContentView(layoutResID) 這行代碼隻是去解析我們的布局,沒幹其他任何事情,這行代碼我就不再分析源碼了,今天的重點不在這裡如果想了解請看這篇插件式換膚架構搭建 - setContentView源碼閱讀,getWindow() 傳回的是 mWindow 而 mWindow 是在 attach 方法中執行個體化的:

那麼 attach 方法到底是在什麼時候調用的呢?在 ActivityThread 中的 performLaunchActivity 方法中調用的,這裡涉及到 Activity 的啟動流程分析想了解請看這篇Android插件化架構 - Activity的啟動流程分析

目前我們隻知道了通過 setContentView 方法會調用 mWindow 的 setContentView 方法,這個方法隻是去解析我們的布局而已什麼時都沒做,而 mWindow 的執行個體實在 activity 的 attach 方法中調用的,而這個方法是由 ActivityThread 調用的然後就沒有了,那麼布局到底是怎麼顯示的?

在Android插件化架構 - Activity的啟動流程分析中我們能夠在 ActivityThread 中找到 handleResumeActivity 這個方法:

最終調用了 ViewManager 的 addView 方法,但是我們發現 ViewManager 其實是個接口,是以我們得去找實作方法,是在上面 Activity 的 attach 方法通過 context.getSystemService(Context.WINDOW_SERVICE) 擷取的,找到 context 的執行個體類 ContextImpl 的 getSystemService 方法其實調用了 SystemServiceRegistry.getSystemService 方法:

SYSTEM_SERVICE_FETCHERS 是一個靜态的 HashMap 對象,是通過靜态代碼塊指派的。這裡其實我們可以總結一下,通過 Context 擷取的系統服務其實早就被注冊和添加到 SYSTEM_SERVICE_FETCHERS 集合中了,這是典型的單例設計模式。至此我們總算找到了這個類 WindowManagerImpl 的 addView 方法。

WindowManagerImpl 是 ViewManager 的實作類卻把活交給了 WindowManagerGlobal 方法,而且我們發現 mGlobal 對象盡然是個單例,為什麼要這麼設計在文章中我就不做過多的講解了。我們看下 mGlobal 的 addView 方法:

走了這麼久最重要的方法其實就是 root.setView(view, wparams, panelParentView); 這行代碼,很複雜偏偏這個方法又是最主要的,希望我講得并不太深入而且通俗易懂。在分析這個之前我們先講一下上面反複出現的 type 屬性。

Window(視窗)是有類型的,而且上面我們也看到了不同的 type 會做不同的處理,Window 分為三種類型:系統 Window,應用程式 Window,子 Window

常見的系統Window,比如在手機電量低的時候,會有一個提示電量低的Window,我們輸入文字的時候,會彈出輸入法Window,還有搜尋條Window,來電顯示Window,Toast對應的Window,可以總結出來,系統Window是獨立與我們的應用程式的,對于應用程式而言,我們理論上是無法建立系統Window,因為沒有權限,這個權限隻有系統程序有。所對應的層級區間是 2000 以上

應用程式Window,比如 Activity 就是一個應用程式 Window 從上面源碼可以看到 type 給的是 TYPE_BASE_APPLICATION 。所對應的層級區間是 1 - 99

子Window,所謂的子Window,是說這個Window必須要有一個父窗體,比如PopWindow,Dialog 等等 。所對應的層級區間是 1000 - 1999

那麼每個層級具體有那些,請看 WindowManager.LayoutParams中的 type 值,這裡我貼出來但是不做翻譯,因為我英語不好:

回到 ViewRootImpl 中的 setView 方法:

requestLayout() 這個方法很有含金量,如果想了解看下這篇 源碼解析 - View的繪制流程 ,我們主要還是分析mWindowSession.addToDisplay 方法,mWindowSession 又是什麼呢?看到 Session 其實還是知道什麼意思,是不是真的和網絡的 Session 差不多呢?mWindowSession 是通過 WindowManagerGlobal.getWindowSession(); 擷取的,我們去看下:

這又是一個典型的 IPC 的通信機制,和 AMS 非常的類似想了解看下這篇 Android程序間的通信 - IPC(機制)Binder的原理和源碼閱讀 ,現在我們去服務端 WindowManagerService 的 openSession 方法:

Session我們總算是擷取到了,服務端 WindowManagerService 建立了一個 Session 對象傳回回來了,接下來我們發現 Session 的 addToDisplay 方法來到了 WindowManager 的 addWindow 方法,相信應該差不多快完了吧,這不是人幹的事:

這段代碼首先在WindowManagerService類的成員變量mWindowMap所描述的一個HashMap中檢查是否存在一個與參數client所對應的WindowState對象,如果已經存在,那麼就說明WindowManagerService服務已經為它建立過一個WindowState對象了,是以,這裡就不會繼續往前執行,而是直接傳回一個錯誤碼WindowManagerImpl.ADD_DUPLICATE_ADD。我們繼續往前看代碼:

參數attrs指向的是一個WindowManager.LayoutParams對象,用來描述正在啟動的Activity元件的UI布局,當它的成員變量type的值大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW的時候,就說明現在要增加的是一個子視窗。在這種情況下,就必須要指定一個父視窗,而這個父視窗是通過數attrs指向的是一個WindowManager.LayoutParams對象的成員變量token來指定的,是以,這段代碼就會調用WindowManagerService類的另外一個成員函數windowForClientLocked來獲得用來描述這個父視窗的一個WindowState對象,并且儲存在變量attachedWindow。

如果得到變量attachedWindow的值等于null,那麼就說明父視窗不存在,這是不允許的,是以,函數就不會繼續向前執行,而是直接傳回一個錯誤碼WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。另一方面,如果變量attachedWindow的值不等于null,但是它的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量type的值也是大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW,那麼也說明找到的父視窗也是一個子視窗,這種情況也是不允許的,是以,函數就不會繼續向前執行,而是直接傳回一個錯誤碼WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。

繼續看代碼:

通過上面的合法性檢查之後,這裡就可以為正在增加的視窗建立一個WindowState對象了。 WindowManagerService類的成員變量mPolicy指向的是一個實作了WindowManagerPolicy接口的視窗管理政策器。在Phone平台中,這個視窗管理政策器是由com.android.internal.policy.impl.PhoneWindowManager來實作的,它負責對系統中的視窗實作制定一些規則。這裡主要是調用視窗管理政策器的成員函數adjustWindowParamsLw來調整目前正在增加的視窗的布局參數,以及調用成員函數prepareAddWindowLw來檢查目前應用程式程序請求增加的視窗是否是合法的。如果不是合法的,即變量res的值不等于WindowManagerImpl.ADD_OKAY,那麼函數就不會繼續向前執行,而直接傳回錯誤碼res。

我們就先看到這裡了,你甚至還可以在去了解 WindowState 的成員變量都有一些啥作用,又或者是那些合法的檢查代碼都有什麼用等等,最後我再畫一張草圖:

源碼閱讀分析 - Window底層原理與系統架構

我花了大概半個多月的時間才勉強看懂一些源碼,當然包括這篇文章中的所有源碼連結,如果看不太懂需要多花些時間,如果文字讓你受不了可以看看我的直播視訊,同時這也是自定義View部分的最後一篇文章了。

所有分享大綱:Android進階之旅 - 自定義View篇

視訊講解位址:http://pan.baidu.com/s/1pLNXkxl