Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> WindowManagerService計算activity大小的過程和窗口組織方式分析

WindowManagerService計算activity大小的過程和窗口組織方式分析

編輯:關於Android編程

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

,如圖1所示。

\

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

WindowManagerService服務只要將它所管理的各個窗品的位置以及大小告訴SurfaceFlinger服務,後者可以幫幫它計算出各個窗口的可見區域了。注意,這裡,這裡所說的窗口位置包括窗口在X、Y和Z軸的位置。

WindowManagerService服務大致按照以下方式來控制哪些窗口需要顯示的以及要顯在哪裡:

 

1. 只要對每一個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交互,如圖2所示。

\

圖2 WindowManagerService服務與Activity窗口、SurfaceFlinger服務、PhoneWindowManager策略的關系圖

一、Activity大小的計算

 

一般來說,Activity窗口的大小等於整個屏幕的大小,但是它並不占據著整塊屏幕。為了理解這一點,我們首先分析一下Activity窗口的區域是如何劃分的。

我們知道,Activity窗口的上方一般會有一個狀態欄,用來顯示3G信號、電量使用等圖標,如圖1所示。

\

圖1 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窗口有時候需要顯示輸入法窗口,如圖2所示。

\

圖2Activity窗口的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元素進行測量、布局以及繪制等操作了。

應用程序進程是從ViewRootImpl類的成員函數performTraversals開始,向WindowManagerService服務請求計算一個Activity窗口的大小的,因此,接下來我們就從ViewRoot類的成員函數performTraversals開始分析一個Activity窗口大小的計算過程,如圖3所示。

 

\

圖3 Activity窗口大小的計算過程

Step 1. ViewRoot.performTraversals

這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中,它的實現很復雜,一共有600-行,不過大部分代碼都是用來計算Activity窗口的大小的,我們分段來閱讀:

private void performTraversals() {  
        ......  
  
        final View host = mView;  
        ......  
   
        int desiredWindowWidth;  
        int desiredWindowHeight;  
        int childWidthMeasureSpec;  
        int childHeightMeasureSpec;  
        ......  
  
        Rect frame = mWinFrame;  
        if (mFirst) {  
            ......  
   
            DisplayMetrics packageMetrics =  
                 mView.getContext().getResources().getDisplayMetrics();  
            desiredWindowWidth = packageMetrics.widthPixels;  
            desiredWindowHeight = packageMetrics.heightPixels;  
        } else {  
            desiredWindowWidth = frame.width();  
            desiredWindowHeight = frame.height();  
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {  
                ......  
                windowResizesToFitContent = true;  
            }  
        }
這段代碼用來獲得Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight。

注意,Activity窗口當前的寬度和高度是保存在成員變量mWinFrame中的。ViewRoot類的另外兩個成員變量mWidth和mHeight也是用來描述Activity窗口當前的寬度和高度的,但是它們的值是由應用程序進程上一次主動請求WindowManagerService服務計算得到的,並且會一直保持不變到應用程序進程下一次再請求WindowManagerService服務來重新計算為止。Activity窗口的當前寬度和高度有時候是被WindowManagerService服務主動請求應用程序進程修改的,修改後的值就會保存在ViewRoot類的成員變量mWinFrame中,它們可能會與ViewRoot類的成員變量mWidth和mHeight的值不同。

如果Activity窗口是第一次被請求執行測量、布局和繪制操作,即ViewRoot類的成員變量mFirst的值等於true,那麼它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等於屏幕的寬度和高度,否則的話,它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等於保存在ViewRoot類的成員變量mWinFrame中的寬度和高度值。

 

如果Activity窗口不是第一次被請求執行測量、布局和繪制操作,並且Activity窗口主動上一次請求WindowManagerService服務計算得到的寬度mWidth和高度mHeight不等於Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight,那麼就說明Activity窗口的大小發生了變化,這時候變量windowResizesToFitContent的值就會被標記為true,以便接下來可以對Activity窗口的大小變化進行處理。

我們繼續往下閱讀代碼:

boolean insetsChanged = false;  
  
if (mLayoutRequested) {  
    ......  
  
    if (mFirst) {  
        host.fitSystemWindows(mAttachInfo.mContentInsets);  
        ......  
    } else {  
        if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) {  
            mAttachInfo.mContentInsets.set(mPendingContentInsets);  
            host.fitSystemWindows(mAttachInfo.mContentInsets);  
            insetsChanged = true;  
            ......  
        }  
        if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) {  
            mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);  
            ......  
        }  
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT  
                || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {  
            windowResizesToFitContent = true;  
  
            DisplayMetrics packageMetrics =  
                mView.getContext().getResources().getDisplayMetrics();  
            desiredWindowWidth = packageMetrics.widthPixels;  
            desiredWindowHeight = packageMetrics.heightPixels;  
        }  
    }  
  
    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);   
    ......  
  
    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  
    ......  
} 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved