3.3 Android窗口
3.3.1 概述
Android系統(tǒng)中,窗口管理系統(tǒng)是基于C/S模式的,客戶端(App)請(qǐng)求創(chuàng)建窗口和使用窗口,服務(wù)端(WMS)管理所有窗口,包括創(chuàng)建、刪除窗口,以及將某個(gè)窗口設(shè)置為焦點(diǎn)窗口(當(dāng)前正在和用戶交互的窗口)。
Android Gui系統(tǒng)
我們先簡(jiǎn)單來(lái)介紹一下Android的GUI系統(tǒng),它包含以下部分內(nèi)容:
- 應(yīng)用框架系統(tǒng) — AMS
- 窗口和圖形系統(tǒng) — WMS
- 顯示渲染系統(tǒng) — SurfaceFlinger
- 用戶輸入系統(tǒng) — InputManager
它們之間的關(guān)系,如下圖所示:

簡(jiǎn)單來(lái)講,AMS負(fù)責(zé)Activity的啟動(dòng),以及生命周期的管理。一個(gè)Activity對(duì)應(yīng)一個(gè)應(yīng)用窗口,WMS負(fù)責(zé)管理這個(gè)窗口(創(chuàng)建、刪除等)以及事件分發(fā)。View系統(tǒng)管理每個(gè)窗口中的具體布局,最終這個(gè)View系統(tǒng)中最頂端的根View(即DecorView)會(huì)被作為窗口,添加到WMS中。WMS管理著所有這些添加的窗口,負(fù)責(zé)管理這些窗口的層次,顯示位置等內(nèi)容。每個(gè)窗口都有一塊自己的Surface,SurfaceFlinger負(fù)責(zé)把這些Surface顯示到屏幕上。
窗口的概念
看AndroidSDK文檔的描述:Window是一個(gè)抽象基類,用于控制頂層窗口的外觀和行為,如繪制背景、標(biāo)題欄和按鍵處理等。View是一個(gè)基本的UI單元,占據(jù)屏幕的一塊矩形區(qū)域,用于繪制顯示UI,接收處理事件等。
而窗口的概念,從不同的角度來(lái)看,其含義是不一樣的,有時(shí)候是指Window,有時(shí)候是指View。我們知道,WMS管理所有的窗口,這里的窗口其實(shí)是一個(gè)View(DecorView),而不是Window。WMS負(fù)責(zé)管理這些View的Z-order,顯示區(qū)域,以及把消息派發(fā)到對(duì)應(yīng)的View。
3.3.2 窗口的類型
Framework中定義了三種類型的窗口:應(yīng)用窗口,子窗口,系統(tǒng)窗口。
應(yīng)用窗口
Activity對(duì)應(yīng)的窗口就是應(yīng)用窗口,其默認(rèn)的窗口類型是TYPE_BASE_APPLICATION。而Dialog的窗口類型是TYPE_APPLICATION,而很多Dialog的子類,修改了窗口類型,如ContextMenu,本質(zhì)是用Dialog來(lái)實(shí)現(xiàn)的,但是在添加窗口前,修改了type類型,賦值為TYPE_APPLICATION_ATTACHED_DIALOG。從這個(gè)我們可以看到,WMS并沒(méi)有把應(yīng)用窗口與子窗口區(qū)分得那么清楚。
與應(yīng)用窗口相關(guān)的窗口表示類是PhoneWindow,PhoneWindow繼承自Window,其本身只是一個(gè)窗口封裝類,其核心是成員mDecorView,mDecorView是一個(gè)頂層的View,窗口的添加就是通過(guò)調(diào)用WindowManager.addView()把該View添加到WMS。
子窗口
子窗口是指該窗口必須要有一個(gè)父窗口,父窗口可以是一個(gè)應(yīng)用類型窗口,也可以是其他類型的窗口。例如前面手Q界面中,點(diǎn)擊右上角的按鈕顯示一個(gè)PopupWindow,它就是一個(gè)子窗口,其類型一般TYPE_APPLICATION_PANEL。既然稱為子窗口,其與父窗口的關(guān)系是比較容易理解的:B是A的子窗口,A不可見(jiàn)時(shí),B也會(huì)不可見(jiàn)的。
系統(tǒng)窗口
一般來(lái)講,系統(tǒng)窗口應(yīng)該由系統(tǒng)來(lái)創(chuàng)建的,例如發(fā)生異常,ANR時(shí)的提示框,狀態(tài)欄,屏保等。但是,F(xiàn)ramework還是定義了一些可以被應(yīng)用所創(chuàng)建的系統(tǒng)窗口,如TYPE_TOAST,TYPE_INPUT_METHOD和TYPE_WALLPAPER等等。系統(tǒng)窗口的添加也是直接調(diào)用WindowManager.addView()將目標(biāo)View添加到WMS。
3.3.3 窗口的創(chuàng)建、顯示與移除
這里以應(yīng)用窗口為例簡(jiǎn)單介紹以下窗口的創(chuàng)建,顯示和移除過(guò)程。
窗口的創(chuàng)建
我們知道每個(gè)Activity對(duì)應(yīng)一個(gè)PhoneWindow,當(dāng)我們調(diào)用setContentView時(shí),其實(shí)最終結(jié)果是把我們的View樹作為子View添加到PhoneWindow的DecorView中,而這個(gè)DecorView會(huì)在ActivityThread的handleResumeActivity方法中,通過(guò)WindowManager.addView()方法添加到WMS中去的。
AMS在接收到啟動(dòng)Activity請(qǐng)求時(shí),首先生成一個(gè)token作為該Activity的唯一標(biāo)識(shí),然后在WMS中添加一個(gè)AppWindowToken,其封裝了Activity的token。接著AMS啟動(dòng)應(yīng)用客戶端進(jìn)程并把token傳遞到該進(jìn)程,在客戶端進(jìn)程里完成Activity的初始化。在Activity的attach()方法中,Activity完成PhoneWindow的創(chuàng)建,并且把token傳遞給PhoneWindow。在Activity調(diào)用WindowManager.addView()時(shí),在WindowManager內(nèi)部會(huì)把token和該View關(guān)聯(lián),真正向WMS申請(qǐng)創(chuàng)建窗口的時(shí)候,再把token傳遞給WMS。WMS接收到創(chuàng)建窗口的請(qǐng)求的時(shí)候,通過(guò)mTokenMap查詢對(duì)應(yīng)該token的AppWindowToken,如果為空則拋出異常,否則創(chuàng)建一個(gè)WindowState并完成初始化工作和其他數(shù)據(jù)結(jié)構(gòu)的調(diào)整工作。在這個(gè)過(guò)程中,token貫穿了服務(wù)端的AMS、WMS和客戶端的Activity、Window。

應(yīng)用請(qǐng)求創(chuàng)建窗口時(shí),和應(yīng)用直接交互的是WindowManager對(duì)象,其負(fù)責(zé)管理一個(gè)應(yīng)用的所有本地窗口。當(dāng)應(yīng)用調(diào)用addView()創(chuàng)建窗口時(shí),WindowManager會(huì)生成一個(gè)ViewRoot對(duì)象與之相對(duì)應(yīng),并且把相應(yīng)的參數(shù)LayoutParams保存起來(lái)。addView()的執(zhí)行流程如下:
- 檢查所添加的窗口是否已經(jīng)添加過(guò),不允許重復(fù)添加;
- 如果所添加窗口為子窗口類型,找到其父窗口,并保存在內(nèi)部變量中;
- 創(chuàng)建一個(gè)新的ViewRoot,并保存對(duì)應(yīng)的View(DecorView)和LayoutParams;
- 調(diào)用ViewRoot的setView()方法,完成添加工作。
ViewRoot本質(zhì)上是一個(gè)Handler,并且實(shí)現(xiàn)了ViewParent接口。ViewRoot的主要功能是:
- 負(fù)責(zé)分發(fā)消息事件,如Key、Motion事件等;
- 負(fù)責(zé)和WMS的交互,分發(fā)WMS的交互命令;
- 作為DecorView的parent,對(duì)DecorView進(jìn)行measure、layout和draw等操作;
在addView()的第3、4步完成之后,之后和WMS的交互工作就由ViewRoot負(fù)責(zé)。而ViewRoot和WMS之間的雙向?qū)υ挘饕峭ㄟ^(guò)以下兩個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行的:IWindowSession,IWindow。其中IWindowSession負(fù)責(zé)ViewRoot到WMS的請(qǐng)求,IWindow則用于WMS回調(diào)ViewRoot。在ViewRoot對(duì)象內(nèi)部,存在著一個(gè)IWindowSession的靜態(tài)成員和一個(gè)IWindow的非靜態(tài)成員,所以一個(gè)進(jìn)程里只有一個(gè)IWindowSession對(duì)象,但是可以有多個(gè)IWindow對(duì)象。參見(jiàn)下圖:

到此為止,整個(gè)窗口管理系統(tǒng)整體架構(gòu)可表示如下:

窗口顯示
從Client端調(diào)用WindowManager的addView()方法到WMS完成WindowState的初始化,在這個(gè)過(guò)程中,主要是完成了窗口數(shù)據(jù)結(jié)構(gòu)的創(chuàng)建以及Client端的窗口和Server端的窗口建立起連接關(guān)系:WMS能夠?qū)lient端的窗口進(jìn)行操作,同時(shí)WMS也能夠接收Client端窗口的請(qǐng)求,對(duì)WindowState進(jìn)行相應(yīng)的調(diào)整。
一個(gè)Window想要顯示在屏幕上,必須申請(qǐng)一個(gè)顯示緩存(Surface),這個(gè)顯示緩存的管理和維護(hù)是在底層圖形模塊實(shí)現(xiàn)的。WindowState申請(qǐng)到Surface對(duì)象之后,會(huì)將此Surface對(duì)象的相關(guān)數(shù)據(jù)拷貝到Client端的ViewRoot中,ViewRoot中也維護(hù)了一個(gè)Surface對(duì)象,這兩個(gè)對(duì)象是指向同一塊顯示緩存。ViewRoot有了這塊顯示緩存的引用之后,即可以通過(guò)lockCanvas來(lái)獲取繪畫畫布,繪制完畢之后通過(guò)unlockAndPostCanvas來(lái)將繪制內(nèi)容刷新到顯示緩存中。也就是說(shuō),Client端窗口和Server端窗口共用一個(gè)Surface,Client負(fù)責(zé)繪制Surface的內(nèi)容,Server負(fù)責(zé)控制Surface在屏幕上的大小位置等。
ViewRoot通過(guò)IWindowSession的relayout方法來(lái)向WMS發(fā)送請(qǐng)求命令,包括窗口的顯示和隱藏,窗口的布局信息如位置大小,同時(shí)還會(huì)接收WMS的處理結(jié)果。WMS會(huì)根據(jù)屏幕大小和Client請(qǐng)求的布局參數(shù)來(lái)決定窗口最終的布局信息,同時(shí)也會(huì)根據(jù)Client請(qǐng)求的顯示隱藏命令來(lái)返回一個(gè)有效的或者無(wú)效的Surface對(duì)象。通常一個(gè)窗口的顯示過(guò)程為:
- Client請(qǐng)求顯示窗口,并且傳遞布局參數(shù);
- WMS根據(jù)布局參數(shù),申請(qǐng)一個(gè)Surface對(duì)象并返回給Client;
- Client對(duì)Surface進(jìn)行繪畫操作,完成后告訴WMS;
- WMS將Surface顯示在屏幕上,并且進(jìn)行層級(jí)等相應(yīng)調(diào)整;

于是一個(gè)窗口從添加到顯示可用以下時(shí)序圖表示:

一個(gè)橫跨Activity、View、Window、WMS以及Surface的整體概念如下如所示:

窗口的移除
窗口的移除是通過(guò)調(diào)用WindowManager.removeView()來(lái)完成的,具體流程如下:

ViewRootImpl在收到要?jiǎng)h除窗口的命令后,會(huì)執(zhí)行以下操作:
- 判斷是否可以立即刪除窗口,否則會(huì)等下次UI操作時(shí)執(zhí)行;
- 確認(rèn)需要?jiǎng)h除窗口時(shí),會(huì)執(zhí)行doDie方法,通過(guò)dispatchDetachedFromWindow通知View樹,窗口要被刪除了;
- dispatchDetachedFromWindow執(zhí)行以下操作:通過(guò)dispatchDetachedFromWindow,通知View樹,窗口已經(jīng)移除了;把窗口對(duì)應(yīng)的HardRender、Surface給釋放了;通過(guò)mWindowSession,通知WMS,窗口要移除了,WMS會(huì)把跟這個(gè)窗口相關(guān)的WindowState,以及WindowToken給移除,同時(shí)更新其它窗口的顯示;通知Choreographer,這個(gè)窗口不需要顯示了,跟這個(gè)窗口相關(guān)的一些UI刷新操作,可以取消了。
- 當(dāng)根View收到dispatchDetachedFromWindow調(diào)用后,會(huì)遍歷View樹中的每一個(gè)View,把這個(gè)通知傳遞下來(lái)。
3.3.4 總結(jié)
這里我們總結(jié)一下,Android中窗口的相關(guān)內(nèi)容:
- 在Window System中,分為兩部分的內(nèi)容,一部分是運(yùn)行在SystemServer進(jìn)程中的WMS及相關(guān)類,另一部分是運(yùn)行在應(yīng)用進(jìn)程的WindowManager,ViewRoot等相關(guān)類。WMS用WindowState來(lái)描述一個(gè)窗口,而應(yīng)用進(jìn)程用DecorView、ViewRoot以及WindowManager.LayoutParams等來(lái)描述一個(gè)窗口的相關(guān)內(nèi)容。
- 對(duì)于WMS來(lái)講,窗口對(duì)應(yīng)一個(gè)View對(duì)象,而不是Window對(duì)象。通過(guò)WindowManager.addView()方法添加一個(gè)窗口,通過(guò)removeView方法移除一個(gè)窗口,通過(guò)updateViewLayout()方法更新一個(gè)窗口的屬性。
- Android把窗口分為三種類型:應(yīng)用窗口,子窗口以及系統(tǒng)窗口。不同類型的窗口,在執(zhí)行添加窗口操作時(shí),對(duì)于WindowManager.LayoutParams中的參數(shù)token具有不同的要求。應(yīng)用窗口,LayoutParams中的token,必須是某個(gè)有效的Activity的mToken。而子窗口,LayoutParams中的token,必須是父窗口的ViewRootImpl中的W對(duì)象。
- 在調(diào)用WindowManager.addView()之前,如果沒(méi)有給token賦值,則會(huì)走默認(rèn)的token賦值邏輯:如果mParentWindow不為空,則會(huì)調(diào)用其adjustLayoutParamsForSubWindow方法,在該方法中,如果當(dāng)前要添加的窗口是應(yīng)用窗口,則會(huì)把當(dāng)前PhoneWindow的mToken賦值給token。如果是子窗口,則會(huì)把當(dāng)前PhoneWindow對(duì)應(yīng)的DecorView的mAttachInfo中的mWindowToken賦值給token。而View中的mAttachIno來(lái)自ViewRootImpl的mAttachInfo。因此這個(gè)token本質(zhì)就是父窗口的ViewRootImpl中的W類對(duì)象。