Android基礎系列-----------GUI系統之WindowManagerService

轉自http://blog.csdn.net/luoshengyang

        WindowManagerService,顧名思義,它是是一個窗口管理系統服務,它的主要功能包含如下:
            窗口管理,繪製
            轉場動畫--Activity切換動畫
            Z-ordered的維護,Activity窗口顯示前後順序
            輸入法管理
            Token管理
            系統消息收集線程
            系統消息分發線程

       在Android系統中,對系統中的所有窗口進行管理是窗口管理服務WindowManagerService的職責。在Android系統中,同一時刻,只有一個Activity窗口是激活的,但是,對於WindowManagerService服務來說,這並不意味着它每次只需要管理一個Activity窗口,例如,在兩個Activity窗口的切換過程中,前後兩個Activity窗口實際上都是可見的。即使在只有一個Activity窗口是可見的時候,WindowManagerService服務仍然需要同時管理着多個窗口,這是因爲可見的Activity窗口可能還會被設置了壁紙窗口(Wallpaper Winodw)或者彈出了子窗口(Sub Window),以及可能會出現狀態欄(Status Bar)以及輸入法窗口(Input Method Window),如下圖所示。


Activity窗口及其子窗口、壁紙窗口、輸入法窗口和狀態欄的位置結構

        因此,WindowManagerService服務是不可以假設同一時刻它只需要管理一個窗口的,它需要通過各個窗口在屏幕上的位置以及大小來決定哪些窗口需要顯示的以及要顯在哪裏,這實際上就是要計算出各個窗口的可見區域。SurfaceFlinger服務在渲染整個屏幕的UI的時候,會對各個窗品的可見性進行計算,因此,WindowManagerService服務只要將它所管理的各個窗品的位置以及大小告訴SurfaceFlinger服務,後者可以幫幫它計算出各個窗口的可見區域了。注意,這裏,這裏所說的窗口位置包括窗口在X、Y和Z軸的位置。
       WindowManagerService服務大致按照以下方式來控制哪些窗口需要顯示的以及要顯在哪裏:
       1. 每一個Activity窗口的大小都等於屏幕的大小,因此,只要對每一個Activity窗口設置一個不同的Z軸位置,然後就可以使得位於最上面的,即當前被激活的Activity窗口,纔是可見的。
       2. 每一個子窗口的Z軸位置都比它的父窗口大,但是大小要比父窗口小,這時候Activity窗口及其所彈出的子窗口都可以同時顯示出來。
       3. 對於非全屏Activity窗口來說,它會在屏幕的上方留出一塊區域,用來顯示狀態欄。這塊留出來的區域稱對於屏幕來說,稱爲裝飾區(decoration),而對於Activity窗口來說,稱爲內容邊襯區(Content Inset)。
       4. 輸入法窗口只有在需要的時候纔會出現,它同樣是出現在屏幕的裝飾區或者說Activity窗口的內容邊襯區的。
       5. 對於壁紙窗口,它出現需要壁紙的Activity窗口的下方,這時候要求Activity窗口是半透明的,這樣就可以將它後面的壁紙窗口一同顯示出來。
       6. 兩個Activity窗口在切換過程,實際上就是前一個窗口顯示退出動畫而後一個窗口顯示開始動畫的過程,而在動畫的顯示過程,窗口的大小會有一個變化的過程,這樣就導致前後兩個Activity窗口的大小不再都等於屏幕的大小,因而它們就有可能同時都處於可見的狀態。事實上,Activity窗口的切換過程是相當複雜的,因爲即將要顯示的Activity窗口可能還會被設置一個啓動窗口(Starting Window)。一個被設置了啓動窗口的Activity窗口要等到它的啓動窗口顯示了之後纔可以顯示出來。

       從以上六點就可以看出,窗口在X、Y和Z軸的位置及其大小的計算非常重要,它們共同決定了一個窗口是否是整體可見的,還是部分可見的,或者整體不可見的。在Android系統中,WindowManagerService服務是通過一個實現了WindowManagerPolicy接口的策略類來計算一個窗口的位置和大小的。例如,在Phone平臺上,這個策略類就是PhoneWindowManager。這樣做的好處就是對於不同的平臺實現不同的策略類來達到不同的窗口控制模式。
      從上面的描述就可以看出,WindowManagerService服務除了要與Activity窗口所運行在的應用程序進程打交道之外,還需要與SurfaceFlinger服務以及窗口管理策略類PhoneWindowManager交互,如下圖所示。

下面將主要分析WindowManagerService服務的實現,以及它與SurfaceFlinger服務、PhoneWindowManager策略類的交互過程。

一、WindowManagerService計算Activity窗口大小的過程

        在Android系統中,Activity窗口的大小是由WindowManagerService服務來計算的。WindowManagerService服務會根據屏幕及其裝飾區的大小來決定Activity窗口的大小。一個Activity窗口只有知道自己的大小之後,才能對它裏面的UI元素進行測量、佈局以及繪製。
        一般來說,Activity窗口的大小等於整個屏幕的大小,但是它並不佔據着整塊屏幕。爲了理解這一點,首先分析一下Activity窗口的區域是如何劃分的。
        Activity窗口的上方一般會有一個狀態欄,用來顯示3G信號、電量使用等圖標,如下圖所示。

Activity窗口的Content區域示意圖
       從Activity窗口剔除掉狀態欄所佔用的區域之後,所得到的區域就稱爲內容區域(Content Region)。顧名思義,內容區域就是用來顯示Activity窗口的內容的。我們再抽象一下,假設Activity窗口的四周都有一塊類似狀態欄的區域,那麼將這些區域剔除之後,得到中間的那一塊區域就稱爲內容區域,而被剔除出來的區域所組成的區域就稱爲內容邊襯區域(Content Insets)。Activity窗口的內容邊襯區域可以用一個四元組(content-left, content-top, content-right, content-bottom)來描述,其中,content-left、content-right、content-top、content-bottom分別用來描述內容區域與窗口區域的左右上下邊界距離
       Activity窗口有時候需要顯示輸入法窗口,如下圖所示。

Activity窗口的Visible區域示意圖
        這時候Activity窗口的內容區域的大小有可能沒有發生變化,這取決於它的Soft Input Mode。假設Activity窗口的內容區域沒有發生變化,但是它在底部的一些區域被輸入法窗口遮擋了,即它在底部的一些內容是不可見的。從Activity窗口剔除掉狀態欄和輸入法窗口所佔用的區域之後,所得到的區域就稱爲可見區域(Visible Region)。同樣,再抽象一下,假設Activity窗口的四周都有一塊類似狀態欄和輸入法窗口的區域,那麼將這些區域剔除之後,得到中間的那一塊區域就稱爲可見區域,而被剔除出來的區域所組成的區域就稱爲可見邊襯區域(Visible Insets)。Activity窗口的可見邊襯區域可以用一個四元組(visible-left, visible-top, visible-right, visible-bottom)來描述,其中,visible-left、visible-right、visible-top、visible-bottom分別用來描述可見區域與窗口區域的左右上下邊界距離。
        在大多數情況下,Activity窗口的內容區域和可見區域的大小是一致的,而狀態欄和輸入法窗口所佔用的區域又稱爲屏幕裝飾區。理解了這些概念之後,就可以推斷,WindowManagerService服務實際上就是需要根據屏幕以及可能出現的狀態欄和輸入法窗口的大小來計算出Activity窗口的整體大小及其內容區域邊襯和可見區域邊襯的大小。有了這三個數據之後,Activity窗口就可以對它裏面的UI元素進行測量、佈局以及繪製等操作了。

二、WindowManagerService對窗口的組織方式

        在Android系統中,Activity是以堆棧的形式組織在ActivityManagerService服務中的。與Activity類似,Android系統中的窗口也是以堆棧的形式組織在WindowManagerService服務中的,其中,Z軸位置較低的窗口位於Z軸位置較高的窗口的下面。下面詳細分析WindowManagerService服務是如何以堆棧的形式來組織窗口的。
        應用程序進程中的每一個Activity組件在Activity管理服務ActivityManagerService中都對應有一個ActivityRecord對象。Activity管理服務ActivityManagerService中每一個ActivityRecord對象在Window管理服務WindowManagerService中都對應有一個AppWindowToken對象。
        此外,在輸入法管理服務InputMethodManagerService中,每一個輸入法窗口都對應有一個Binder對象,這個Binder對象在Window管理服務WindowManagerService又對應有一個WindowToken對象。
        與輸入法窗口類似,在壁紙管理服務WallpaperManagerService中,每一個壁紙窗口都對應有一個Binder對象,這個Binder對象在Window管理服務WindowManagerService也對應有一個WindowToken對象。
        在Window管理服務WindowManagerService中,無論是AppWindowToken對象,還是WindowToken對象,它們都是用來描述一組有着相同令牌的窗口的,每一個窗口都是通過一個WindowState對象來描述的。例如,一個Activity組件窗口可能有一個啓動窗口(Starting Window),還有若干個子窗口,那麼這些窗口就會組成一組,並且都是以Activity組件在Window管理服務WindowManagerService中所對應的AppWindowToken對象爲令牌的。從抽象的角度來看,就是在Window管理服務WindowManagerService中,每一個令牌(AppWindowToken或者WindowToken)都是用來描述一組窗口(WindowState)的,並且每一個窗口的子窗口也是與它同屬於一個組,即都有着相同的令牌。
        上述的窗口組織方式下圖所示:

窗口在WindowManagerService服務中的組織方式

        其中,Activity Stack是在ActivityManagerService服務中創建的,Token List和Window Stack是在WindowManagerService中創建的,而Binder for IM和Binder for WP分別是在InputMethodManagerService服務和WallpaperManagerService服務中創建的,用來描述一個輸入法窗口和一個壁紙窗口。
        圖中的對象的對應關係如下所示:

       1. ActivityRecord-J對應於AppWindowToken-J,後者描述的一組窗口是{WindowState-A, WindowState-B, WindowState-B-1},其中, WindowState-B-1是WindowState-B的子窗口。
       2. ActivityRecord-K對應於AppWindowToken-K,後者描述的一組窗口是{WindowState-C, WindowState-C-1, WindowState-D, WindowState-D-1},其中, WindowState-C-1是WindowState-C的子窗口,WindowState-D-1是WindowState-D的子窗口。
       3. ActivityRecord-N對應於AppWindowToken-N,後者描述的一組窗口是{WindowState-E},其中, WindowState-E是系統當前激活的Activity窗口。
       4. Binder for IM對應於WindowToken-I,後者描述的一組窗口是{WindowState-I},其中, WindowState-I是WindowState-E的輸入法窗口。
       5. Binder for WP對應於WindowToken-W,後者描述的一組窗口是{WindowState-W},其中, WindowState-W是WindowState-E的壁紙窗口。

       從圖中還可以知道,Window Stack中的WindowState是按照它們所描述的窗口的Z軸位置從低到高排列的。

三、WindowManagerService計算窗口Z軸位置的過程

        在Android系統中,無論是普通的Activity窗口,還是特殊的輸入法窗口和壁紙窗口,它們都是被WindowManagerService服務組織在一個窗口堆棧中的,其中,Z軸位置較大的窗口排列在Z軸位置較小的窗口的上面。有了這個窗口堆棧之後,WindowManagerService服務就可以按照一定的規則計算每一個窗口的Z軸位置了,基於窗口堆棧來計算窗口的Z軸位置是比較有意思的。按照一般的理解,應該是先計算好窗口的Z軸位置,然後再按照Z軸位置的大小來將各個窗口排列在堆棧中。但是,事實上,窗口是按照其它規則排列在堆棧中。這些規則與窗口的類型、創建順序和運行狀態等有關。例如,狀態欄窗口總是位於堆棧的頂端,輸入法窗口總是位於需要輸入法的窗口的上面,而壁紙窗口總是位於需要顯示壁紙的窗口的下面。又如,當一個Activity組件從後臺激活到前臺時,與它所對應的窗口就會被相應地移動到窗口堆棧的上面去。

        窗口的UI最終是需要通過SurfaceFlinger服務來統一渲染的,而SurfaceFlinger服務在渲染窗口的UI之前,需要計算基於各個窗口的Z軸位置來計算它們的可見區域。因此,WindowManagerService服務計算好每一個窗口的Z軸位置之後,還需要將它們設置到SurfaceFlinger服務中去,以便SurfaceFlinger服務可以正確地渲染每一個窗口的UI。

四、WindowManagerService顯示Activity組件的啓動窗口(Starting Window)的過程

        在Android系統中,Activity組件在啓動之後,並且在它的窗口顯示出來之前,可以顯示一個啓動窗口。這個啓動窗口可以看作是Activity組件的預覽窗口,是由WindowManagerService服務統一管理的,即由WindowManagerService服務負責啓動和結束。

        Activity組件的啓動窗口是由ActivityManagerService服務來決定是否要顯示的。如果需要顯示,那麼ActivityManagerService服務就會通知WindowManagerService服務來爲正在啓動的Activity組件顯示一個啓動窗口,而WindowManagerService服務又是通過窗口管理策略類PhoneWindowManager來創建這個啓動窗口的。這個過程如下圖所示。

Activity窗口的啓動窗品的創建過程


        窗口管理策略類PhoneWindowManager創建完成Activity組件的啓動窗口之後,就會請求WindowManagerService服務將該啓動窗口顯示出來。當Activity組件啓動完成,並且它的窗口也顯示出來的時候,WindowManagerService服務就會結束顯示它的啓動窗口。
        注意,Activity組件的啓動窗口是由ActivityManagerService服務來控制是否顯示的,也就是說,Android應用程序是無法決定是否要要Activity組件顯示啓動窗口的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章