Android窗口

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)容:

  1. 應(yīng)用框架系統(tǒng) — AMS
  2. 窗口和圖形系統(tǒng) — WMS
  3. 顯示渲染系統(tǒng) — SurfaceFlinger
  4. 用戶輸入系統(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í)行流程如下:

  1. 檢查所添加的窗口是否已經(jīng)添加過(guò),不允許重復(fù)添加;
  2. 如果所添加窗口為子窗口類型,找到其父窗口,并保存在內(nèi)部變量中;
  3. 創(chuàng)建一個(gè)新的ViewRoot,并保存對(duì)應(yīng)的View(DecorView)和LayoutParams;
  4. 調(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ò)程為:

  1. Client請(qǐng)求顯示窗口,并且傳遞布局參數(shù);
  2. WMS根據(jù)布局參數(shù),申請(qǐng)一個(gè)Surface對(duì)象并返回給Client;
  3. Client對(duì)Surface進(jìn)行繪畫操作,完成后告訴WMS;
  4. 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í)行以下操作:

  1. 判斷是否可以立即刪除窗口,否則會(huì)等下次UI操作時(shí)執(zhí)行;
  2. 確認(rèn)需要?jiǎng)h除窗口時(shí),會(huì)執(zhí)行doDie方法,通過(guò)dispatchDetachedFromWindow通知View樹,窗口要被刪除了;
  3. 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刷新操作,可以取消了。
  4. 當(dāng)根View收到dispatchDetachedFromWindow調(diào)用后,會(huì)遍歷View樹中的每一個(gè)View,把這個(gè)通知傳遞下來(lái)。

3.3.4 總結(jié)

這里我們總結(jié)一下,Android中窗口的相關(guān)內(nèi)容:

  1. 在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)容。
  2. 對(duì)于WMS來(lái)講,窗口對(duì)應(yīng)一個(gè)View對(duì)象,而不是Window對(duì)象。通過(guò)WindowManager.addView()方法添加一個(gè)窗口,通過(guò)removeView方法移除一個(gè)窗口,通過(guò)updateViewLayout()方法更新一個(gè)窗口的屬性。
  3. Android把窗口分為三種類型:應(yīng)用窗口,子窗口以及系統(tǒng)窗口。不同類型的窗口,在執(zhí)行添加窗口操作時(shí),對(duì)于WindowManager.LayoutParams中的參數(shù)token具有不同的要求。應(yīng)用窗口,LayoutParams中的token,必須是某個(gè)有效的Activity的mToken。而子窗口,LayoutParams中的token,必須是父窗口的ViewRootImpl中的W對(duì)象。
  4. 在調(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ì)象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評(píng)論 6 545
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,795評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,943評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 64,057評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,773評(píng)論 6 414
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,106評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,282評(píng)論 0 291
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,793評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,507評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,741評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,929評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,325評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,661評(píng)論 1 296
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,482評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,702評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容