Android窗口管理服務WindowManagerService計算窗口Z軸位置的過程分析

       通過前面幾篇文章的學習,我們知道了在Android系統中,無論是普通的Activity窗口,還是特殊的輸入法窗口和壁紙窗口,它們都是被WindowManagerService服務組織在一個窗口堆棧中的,其中,Z軸位置較大的窗口排列在Z軸位置較小的窗口的上面。有了這個窗口堆棧之後,WindowManagerService服務就可以按照一定的規則計算每一個窗口的Z軸位置了,本文就詳細分析這個計算過程。

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

       從前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系列的文章可以知道,窗口的UI最終是需要通過SurfaceFlinger服務來統一渲染的,而SurfaceFlinger服務在渲染窗口的UI之前,需要計算基於各個窗口的Z軸位置來計算它們的可見區域。因此,WindowManagerService服務計算好每一個窗口的Z軸位置之後,還需要將它們設置到SurfaceFlinger服務中去,以便SurfaceFlinger服務可以正確地渲染每一個窗口的UI。

       上述窗口的Z軸位置計算和設置過程如圖1所示:


圖1 窗口Z軸位置的計算和設置過程

       接下來,我們就首先分析兩個需要重新計算窗口Z軸位置的情景,接着再分析窗口的Z軸位置的計算過程,最後分析WindowManagerService服務將窗口的Z軸設置到SurfaceFlinger服務中去的過程。

       一. 需要重新計算窗口Z軸位置的情景

       這裏主要分析兩個需要重新計算窗口Z軸位置的情景:應用程序增加一個窗口到WindowManagerService服務和應用程序請求WindowManagerService服務重新佈局一個窗口。

       從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,應用程序請求增加一個窗口到WindowManagerService服務的時候,最終會調用到WindowManagerService類的成員函數addWindow。接下來我們就主要分析這個函數與重新計算窗口Z軸位置相關的邏輯,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                                                                          
    public int addWindow(Session session, IWindow client, 
            WindowManager.LayoutParams attrs, int viewVisibility, 
            Rect outContentInsets, InputChannel outInputChannel) { 
        ...... 
                                                                                          
        synchronized(mWindowMap) { 
            ...... 
                                                                                          
            WindowToken token = mTokenMap.get(attrs.token);  
            ...... 
                                                                                          
            win = new WindowState(session, client, token, 
                    attachedWindow, attrs, viewVisibility); 
            ...... 
                                                                                          
            if (attrs.type == TYPE_INPUT_METHOD) { 
                mInputMethodWindow = win;
                addInputMethodWindowToListLocked(win);
                ...... 
            } else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) { 
                mInputMethodDialogs.add(win);
                addWindowToListInOrderLocked(win, true);
                adjustInputMethodDialogsLocked();
                ...... 
            } else { 
                addWindowToListInOrderLocked(win, true); 
                if (attrs.type == TYPE_WALLPAPER) { 
                    ...... 
                    adjustWallpaperWindowsLocked(); 
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { 
                    adjustWallpaperWindowsLocked(); 
                } 
            } 
            ...... 
                                                                                          
            assignLayersLocked(); 
                                                                                          
            ...... 
        } 
                                                                                          
        ...... 
    } 
                                                                                          
    ...... 
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       WindowManagerService類的成員函數addWindow的具體實現可以參考Android窗口管理服務WindowManagerService對壁紙窗口(Wallpaper Window)的管理分析和Android窗口管理服務WindowManagerService對輸入法窗口(Input Method Window)的管理分析這兩篇文章。我們注意到,WindowManagerService類的成員函數addWindow會根據當前正在添加的窗口的類型來調用不同的成員函數來向窗口堆棧的合適位置插入一個WindowState對象,即:

       1. 如果添加的是一個輸入法窗口,那麼就調用成員函數addInputMethodWindowToListLocked將它放置在需要顯示輸入法的窗口的上面去;

       2. 如果添加的是一個輸入法對話框,那麼就先調用成員函數addWindowToListInOrderLocked來將它插入到窗口堆棧中,接着再調用成員函數adjustInputMethodDialogsLocked來將它放置在輸入法窗口的上面;

       3. 如果添加的是一個普通窗口,那麼就直接調用成員函數addWindowToListInOrderLocked來將它插入到窗口堆棧中;

       4. 如果添加的是一個普通窗口,並且這個窗口需要顯示壁紙,那麼就先調用成員函數addWindowToListInOrderLocked來將它插入到窗口堆棧中,接着再調用成員函數adjustWallpaperWindowsLocked來將壁紙窗口放置在它的下面。

       5. 如果添加的是一個壁紙窗口,那麼就先調用成員函數addWindowToListInOrderLocked來將它插入到窗口堆棧中,接着再調用成員函數adjustWallpaperWindowsLocked來將它放置在需要顯示壁紙的窗口的下面。

       無論如何,WindowManagerService類的成員函數addWindow最終都會調用成員函數assignLayersLocked來重新計算系統中所有窗口的Z軸位置,這是因爲前面往窗口堆棧增加了一個新的窗口。

       從前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文可以知道,應用程序進程請求WindowManagerService服務重新佈局一個窗口的時候,最終會調用到WindowManagerService類的成員函數relayoutWindow。接下來我們就主要分析這個函數與重新計算窗口Z軸位置相關的邏輯,如下所示:


public class WindowManagerService extends IWindowManager.Stub       
        implements Watchdog.Monitor {     
    ......   
                                                                          
    public int relayoutWindow(Session session, IWindow client,   
            WindowManager.LayoutParams attrs, int requestedWidth,   
            int requestedHeight, int viewVisibility, boolean insetsPending,   
            Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,   
            Configuration outConfig, Surface outSurface) {   
        ......   
                                                                          
        synchronized(mWindowMap) {   
            WindowState win = windowForClientLocked(session, client, false);   
            ......   
                                                                          
            int attrChanges = 0;   
            int flagChanges = 0;   
            if (attrs != null) {   
                flagChanges = win.mAttrs.flags ^= attrs.flags;   
                attrChanges = win.mAttrs.copyFrom(attrs);   
            }   
            ......   
                                                                          
            boolean imMayMove = (flagChanges&(   
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |   
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0;   
                                                                          
            boolean focusMayChange = win.mViewVisibility != viewVisibility   
                    || ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0)   
                    || (!win.mRelayoutCalled);   
            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
            ......   
                                                                          
                                                                          
            if (focusMayChange) {   
                ......   
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {   
                    imMayMove = false;   
                }   
                ......   
            }   
                                                                          
            // updateFocusedWindowLocked() already assigned layers so we only need to   
            // reassign them at this point if the IM window state gets shuffled   
            boolean assignLayers = false;   
                                                                          
            if (imMayMove) {   
                if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {   
                    // Little hack here -- we -should- be able to rely on the   
                    // function to return true if the IME has moved and needs   
                    // its layer recomputed.  However, if the IME was hidden   
                    // and isn't actually moved in the list, its layer may be   
                    // out of data so we make sure to recompute it.   
                    assignLayers = true;   
                }   
            }
            if (wallpaperMayMove) {
                if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
                    assignLayers = true;
                }
            }   
            ......   
                                                                          
            if (assignLayers) {   
                assignLayersLocked();   
            }   
                                                                          
            ......   
        }   
                                                                          
        ......   
                                                                          
        return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)   
                | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);   
    }   
                                                                          
    ......   
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       WindowManagerService類的成員函數relayoutWindow具體實現可以參考Android窗口管理服務WindowManagerService對壁紙窗口(Wallpaper Window)的管理分析和Android窗口管理服務WindowManagerService對輸入法窗口(Input Method Window)的管理分析這兩篇文章,與窗口Z軸位置計算相關的邏輯大概是這樣的:

       1. 如果系統當前獲得焦點的窗口可能發生了變化,那麼就會調用成員函數updateFocusedWindowLocked來重新計算系統當前應該獲得焦點的窗口。如果系統當前獲得焦點的窗口真的發生了變化,即窗口堆棧的窗口排列發生了變化,那麼在調用成員函數updateFocusedWindowLocked的時候,就會調用成員函數assignLayersLocked來重新計算系統中所有窗口的Z軸位置。

       2. 如果系統中的輸入法窗口可能需要移動,那麼就會調用成員函數moveInputMethodWindowsIfNeededLocked來檢查是否真的需要移動輸入法窗口。如果需要移動,那麼成員函數moveInputMethodWindowsIfNeededLocked的返回值就會等於true,這時候就說明輸入法窗口在窗口堆棧中的位置發生了變化,因此,就會將變量assignLayers的值設置爲true,表示接下來需要重新計算系統中所有窗口的Z軸位置。

       3. 如果當前正在請求調整其佈局的窗口是由不可見變化可見的,即變量displayed的值等於true,那麼接下來也是需要重新計算系統中所有窗口的Z軸位置的,因此,就會將assignLayers的值設置爲true。

       4. 如果系統中的壁紙窗口可能需要移動,那麼就會調用成員函數adjustWallpaperWindowsLocked來檢查是否真的需要移動壁紙窗口。如果需要移動,那麼成員函數adjustWallpaperWindowsLocked的返回值的ADJUST_WALLPAPER_LAYERS_CHANGED位就會等於1,這時候就說明壁紙窗口在窗口堆棧中的位置發生了變化,因此,就會將變量assignLayers的值設置爲true,表示接下來需要重新計算系統中所有窗口的Z軸位置。

       經過上述的一系列操作後,如果得到的變量assignLayers的值設置等於true,那麼WindowManagerService類的成員函數relayoutWindow就會調用成員函數assignLayersLocked來重新計算系統中所有窗口的Z軸位置。

       二. 計算系統中所有窗口的Z軸位置

       從前面第一部分的內容可以知道,一旦窗口堆棧中的窗口發生了變化,那麼WindowManagerService類的成員函數assignLayersLocked就會調用來計算系統中所有窗口的Z軸位置。

       窗口的Z軸位置除了與它在窗口堆棧中的位置有關之外,還與窗口的類型有關。窗口的類型在創建的時候就已經是確定了的,WindowManagerService服務在爲它創建一個WindowState對象的時候,就會根據它的類型得到一個BaseLayer值,這個BaseLayer值在計算它的Z軸位置的時候會用到。

       接下來我們就通過WindowState類的構造函數來分析一個窗口的BaseLayer值是如何確定的,如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    /** How much to multiply the policy's type layer, to reserve room
     * for multiple windows of the same type and Z-ordering adjustment
     * with TYPE_LAYER_OFFSET. */
    static final int TYPE_LAYER_MULTIPLIER = 10000;
    /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
     * or below others in the same layer. */
    static final int TYPE_LAYER_OFFSET = 1000;
    ......
    private final class WindowState implements WindowManagerPolicy.WindowState {
        ......
        final int mBaseLayer;
        final int mSubLayer;
        ......
        WindowState(Session s, IWindow c, WindowToken token,
               WindowState attachedWindow, WindowManager.LayoutParams a,
               int viewVisibility) {
            ......
            if ((mAttrs.type >= FIRST_SUB_WINDOW &&
                    mAttrs.type <= LAST_SUB_WINDOW)) {
                // The multiplier here is to reserve space for multiple
                // windows in the same type layer.
                mBaseLayer = mPolicy.windowTypeToLayerLw(
                        attachedWindow.mAttrs.type) * TYPE_LAYER_MULTIPLIER
                        + TYPE_LAYER_OFFSET;
                mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
                ......
            } else {
                // The multiplier here is to reserve space for multiple
                // windows in the same type layer.
                mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
                        * TYPE_LAYER_MULTIPLIER
                        + TYPE_LAYER_OFFSET;
                mSubLayer = 0;
                ......
            }
            ......
        }
        ......
    }
    ......
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       一個窗口除了有一個BaseLayer值之外,還有一個SubLayer值,分別保存在一個對應的WindowState對象的成員變量mBaseLayer和mSubLayer。SubLayer值是用來描述一個窗口是否是另外一個窗口的子窗口的。

       假設一個窗口是另外一個窗口的子窗口,那麼參數attachedWindow所描述的窗口就是父窗口,這時候子窗口的BaseLayer值就等於父窗口的BaseLayer值,而SubLayer值要麼大於0,要麼小於0,這與它自己的具體類型有關。

       假設一個窗口不是另外一個窗口的子窗口,那麼這個窗口的BaseLayer值就與它自己的具體類型有關,而SubLayer值就等於0。

       現在的關鍵就是要根據窗口的類型來計算它的BaseLayer值和SubLayer值,它們分別是通過調用WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數windowTypeToLayerLw和subWindowTypeToLayerLw來計算得到的。這裏有兩個地方是需要注意的。

       第一個地方是PhoneWindowManager對象的成員函數windowTypeToLayerLw的返回值並且不是一個窗口的最終的BaseLayer值,而是要將它的返回值乘以一個常量TYPE_LAYER_MULTIPLIER,再加上另外一個常量TYPE_LAYER_OFFSET之後,纔得到最終的BaseLayer值。這是因爲在Android系統中,相同類型的窗口的Z軸位置都是有着相同的值域,而不同類型的窗口的Z軸位置都是處於兩個不相交的值域。例如,假設有兩種不同類型的窗口,它們的Z軸位置的值域分別爲[a, b]和[c, d],那麼[a, b]和[c, d]的交集一定等於空。又由於每一種類型的窗口的數量是不確定的,因此,WindowManagerService服務就需要爲每一種類型的窗口都預留一個範圍足夠大的值域,以便可以滿足要求。

      WindowManagerService服務是如何爲類型相同的窗口的Z軸位置預留一個範圍足夠大的值域的呢?我們假設類型爲t的窗口的Z軸位置的值域爲[a, b],並且以t爲參數調用PhoneWindowManager對象的成員函數windowTypeToLayerLw的返回值爲T,那麼a的值就等於T * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET,而b的值就等於(T - 1) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET - 1,即從T * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET開始,一共預留了TYPE_LAYER_MULTIPLIER個值作爲類型爲t窗口的Z軸位置。由於TYPE_LAYER_MULTIPLIER的值定義爲10000,而TYPE_LAYER_OFFSET的值定義爲1000,因此,每一種類型的窗口都預留有一個足夠大的值域來作爲Z軸位置。

      第二個地方是窗口的SubLayer值並不直接參與窗口的Z軸位置的計算,但是它會影響到窗口在窗口堆棧的位置。接下來我們就會看到,窗口在窗口堆棧的位置是會影響到它的Z軸位置的計算的,因此,窗口的SubLayer間接地參與了窗口的Z軸位置的計算。

      窗口的SubLayer值是如何影響到窗口在窗口堆棧的位置的呢?在前面Android窗口管理服務WindowManagerService對窗口的組織方式分析一文中,在分析WindowManagerService類的成員函數addWindowToListInOrderLocked的實現時提到,如果一個窗口是另外一個窗口的子窗口,那麼當它的SubLayer值小於0的時候,它就會位於父窗口的下面,否則的話,就會位於父窗口的上面。

      在繼續分析WindowManagerService類的成員函數assignLayersLocked之前,我們首先分析PhoneWindowManager類的成員函數windowTypeToLayerLw和subWindowTypeToLayerLw的實現,以便可以瞭解一個窗口的BaseLayer值和SubLayer值是如何確定的。

      PhoneWindowManager類的成員函數windowTypeToLayerLw的實現如下所示:


public class PhoneWindowManager implements WindowManagerPolicy {
    ......
    public int windowTypeToLayerLw(int type) {
        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            return APPLICATION_LAYER;
        }
        switch (type) {
        case TYPE_STATUS_BAR:
            return STATUS_BAR_LAYER;
        case TYPE_STATUS_BAR_PANEL:
            return STATUS_BAR_PANEL_LAYER;
        case TYPE_SYSTEM_DIALOG:
            return SYSTEM_DIALOG_LAYER;
        case TYPE_SEARCH_BAR:
            return SEARCH_BAR_LAYER;
        case TYPE_PHONE:
            return PHONE_LAYER;
        case TYPE_KEYGUARD:
            return KEYGUARD_LAYER;
        case TYPE_KEYGUARD_DIALOG:
            return KEYGUARD_DIALOG_LAYER;
        case TYPE_SYSTEM_ALERT:
            return SYSTEM_ALERT_LAYER;
        case TYPE_SYSTEM_ERROR:
            return SYSTEM_ERROR_LAYER;
        case TYPE_INPUT_METHOD:
            return INPUT_METHOD_LAYER;
        case TYPE_INPUT_METHOD_DIALOG:
            return INPUT_METHOD_DIALOG_LAYER;
        case TYPE_SYSTEM_OVERLAY:
            return SYSTEM_OVERLAY_LAYER;
        case TYPE_SECURE_SYSTEM_OVERLAY:
            return SECURE_SYSTEM_OVERLAY_LAYER;
        case TYPE_PRIORITY_PHONE:
            return PRIORITY_PHONE_LAYER;
        case TYPE_TOAST:
            return TOAST_LAYER;
        case TYPE_WALLPAPER:
            return WALLPAPER_LAYER;
        }
        Log.e(TAG, "Unknown window type: " + type);
        return APPLICATION_LAYER;
    }
    ......
}


      這個函數定義在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java文件中。


      從這裏就可以看出,每一種窗口類型type都對應有一個BaseLayer值,即每一個TYPE_XXX值都對應有一個XXX_LAYER值,其中,TYPE_XXX值定義在WindowManager.LayoutParams類中,而XXX_LAYER值就定義在PhoneWindowManager類中,它們的對應關係如圖2所示:


圖2 窗口類型與窗口BaseLayer值的對應關係

      注意,如果參數type的值小於FIRST_APPLICATION_WINDOW,或者大於LAST_APPLICATION_WINDOW,或者不是圖2列出來的其中一個值,那麼PhoneWindowManager類的成員函數windowTypeToLayerLw就會返回一個APPLICATION_LAYER(2)值給調用者。

      PhoneWindowManager類的成員函數subWindowTypeToLayerLw的實現如下所示:


public class PhoneWindowManager implements WindowManagerPolicy {
    ......
    public int subWindowTypeToLayerLw(int type) {
        switch (type) {
        case TYPE_APPLICATION_PANEL:
        case TYPE_APPLICATION_ATTACHED_DIALOG:
            return APPLICATION_PANEL_SUBLAYER;
        case TYPE_APPLICATION_MEDIA:
            return APPLICATION_MEDIA_SUBLAYER;
        case TYPE_APPLICATION_MEDIA_OVERLAY:
            return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
        case TYPE_APPLICATION_SUB_PANEL:
            return APPLICATION_SUB_PANEL_SUBLAYER;
        }
        Log.e(TAG, "Unknown sub-window type: " + type);
        return 0;
    }
    ......
}


       這個函數定義在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java文件中。


       從這裏就可以看出,只有類型爲TYPE_APPLICATION_PANEL、TYPE_APPLICATION_MEDIA、TYPE_APPLICATION_MEDIA_OVERLAY和TYPE_APPLICATION_SUB_PANEL的窗口才對應有一個SubLayer值,它們的對應關係如圖3所示:


圖3 窗口類型與窗口SubLayer值的對應關係

       在圖3中,TYPE_XXX值定義在WindowManager.LayoutParams類中,而XXX_LAYER值就定義在PhoneWindowManager類中。注意,有兩種特殊的多媒體窗口TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY,它們是用來顯示多媒體的,例如,用來顯示視頻,並且它們都是附加在應用程序窗口之上的,但是由於它們的SubLayer值爲負數,因此它們實際上是位於宿主窗口之下的。類型爲TYPE_APPLICATION_MEDIA的窗口有一個魔術,它會在宿主窗口裏面挖一個洞,以便可以將自己顯示出來,而類型爲TYPE_APPLICATION_MEDIA_OVERLAY背景一般都是透明的,位於類型爲TYPE_APPLICATION_MEDIA的窗口,可以用來顯示視頻的字幕之類的東西。實際上,類型爲TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的窗口也稱爲SurfaceView。SurfaceView很特殊,它與普通的View的最大區別就在於它們有獨立的繪圖表面,於是它們就可以在一個獨立的子線程裏面進行UI渲染。

       理解了窗口的BaseLayer值和SubLayer值的計算過程之外,接下來我們就可以分析WindowManagerService類的成員函數assignLayersLocked的實現了,如下所示:


public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor {
    ......
    /** How much to increment the layer for each window, to reserve room
     * for effect surfaces between them.
     */
    static final int WINDOW_LAYER_MULTIPLIER = 5;
    ......
    private final void assignLayersLocked() {
        int N = mWindows.size();
        int curBaseLayer = 0;
        int curLayer = 0;
        int i;
        for (i=0; i<N; i++) {
            WindowState w = mWindows.get(i);
            if (w.mBaseLayer == curBaseLayer || w.mIsImWindow
                    || (i > 0 && w.mIsWallpaper)) {
                curLayer += WINDOW_LAYER_MULTIPLIER;
                w.mLayer = curLayer;
            } else {
                curBaseLayer = curLayer = w.mBaseLayer;
                w.mLayer = curLayer;
            }
            if (w.mTargetAppToken != null) {
                w.mAnimLayer = w.mLayer + w.mTargetAppToken.animLayerAdjustment;
            } else if (w.mAppToken != null) {
                w.mAnimLayer = w.mLayer + w.mAppToken.animLayerAdjustment;
            } else {
                w.mAnimLayer = w.mLayer;
            }
            if (w.mIsImWindow) {
                w.mAnimLayer += mInputMethodAnimLayerAdjustment;
            } else if (w.mIsWallpaper) {
                w.mAnimLayer += mWallpaperAnimLayerAdjustment;
            }
            ......
        }
    }
                                                  
    ......
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。

       注意,在調用WindowManagerService類的成員函數assignLayersLocked之前,系統中的所有窗口在窗口堆棧中的位置都是已經排列好了的,這時候WindowManagerService類的成員函數assignLayersLocked就從下往上遍歷窗口堆棧,以連排列在一起的類型相同的窗口爲單位來計算每一個窗口的Z位置,即:

       1. 每次遇到一個窗口,它的BaseLayer值與上一次計算的窗口的BaseLayer值不相等,就開始一個新的計算單元。

       2. 在每一個計算單元中,第一個窗口的Z軸位置就等於它的BaseLayer值,而之後的每一個窗口的Z軸位置都比前一個窗口的Z軸位置大WINDOW_LAYER_MULTIPLIER。

       這個窗口的Z軸位置計算方法有三個地方是需要注意的。        

       第一個地方是從第2點可以看出,每一個窗口的Z軸位置值都不是連續的,這樣就在每兩個窗口之間都保留了一定的位置來插入其它窗口。

       第二個地方是由於系統中所有類型相同的窗口不一定都是排列在一起的,因此,就有可能出現有些類型相同的窗口具有相同的Z軸位置。WindowManagerService服務並不關心兩個不同窗口的Z軸位置是否相同,但是SurfaceFlinger服務就需要關心了,因爲SurfaceFlinger服務需要是按照Z軸從大到小的順序來計算窗口的可見性。那麼SurfaceFlinger服務是如何確定兩個Z軸位置相同的窗口的次序的呢?從前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系列的文章可以知道,每一個窗口在SurfaceFlinger服務都對應有一個Layer對象,而每一個Layer對象都有一個sequence值,其中,先創建的Layer對象的sequence值大於後創建的Layer對象的sequence值。這樣,SurfaceFlinger服務在計算對於兩個Z軸位置相同的窗口的可見性的時候,就會比較它們所對應的Layer對象的sequence值,其中,sequence值大的窗口的可見性會先於sequence值小的窗口得到計算,即先計算後創建的窗口的可見性,再計算先創建的窗口的可見性。

       第三個地方是有兩種特殊的窗口,即輸入法窗口和壁紙窗口,當它們不是窗口堆棧底部的第一個窗口時,它們所在的計算單元不是以窗口類型來劃分的,而靠近在哪個窗口,就與哪個窗口在同一個計算單元中。當輸入法窗口是窗口堆棧底部的第一個窗口時,它的Z軸位置就等於WINDOW_LAYER_MULTIPLIER,而當壁紙窗口是窗口堆棧底部的第一個窗口時,它的Z軸位置就等於它的BaseLayer值。

       前面計算得到的窗口的Z軸位置保存在WindowState類的成員變量mLayer中。事實上,保存在WindowState類的成員變量mLayer中的Z軸位置還不是窗口的最終Z軸位置,因爲還沒有考慮到窗口與窗口令牌之間的關係。每一個窗口令牌都可以設置一個Z軸調整值,而每一個窗口要加上它所對應的窗口令牌所設置的Z軸調整值之後,才能得到最終的Z軸位置。注意,只有類型爲AppWindowToken的窗口令牌纔可以設置Z軸調整值,這個Z軸調整值就保存在AppWindowToken類的成員變量animLayerAdjustment中。

       有時候,一個窗口會有一個目標窗口。例如,輸入法窗口的目標窗口是系統當前需要顯示輸入法的窗口。在這種情況下,我們要使用目標窗口所對應的窗口令牌所設置的Z軸調整值來調整窗口的的Z軸位置。

       那麼,WindowManagerService服務是如何知道一個窗口所對應的窗口令牌的類型是AppWindowToken,或者一個窗口有沒有目標窗口的呢?當用來描述一個窗口的WindowState對象成員變量mAppToken的值不等於null的時候,那麼就說明該窗口所對應的窗口令牌的類型是AppWindowToken,而當用來描述一個窗口的WindowState對象成員變量mTargetAppToken的值不等於null的時候,那麼就說明該窗口有一個目標窗口。

       經過上面的調整之後,窗口的Z軸位置就保存在WindowState類的成員變量mAnimLayer中。對於非輸入法窗口和非壁紙窗口來說,這時候保存在用來描述它們的WindowState對象的成員變量mAnimLayer中的Z軸位置就是它們最終的Z軸位置了,但是對於輸入法窗口和壁紙窗口來說,還需要繼續判斷它們的目標窗口是否需要調整它們的Z軸位置。

       從前面Android窗口管理服務WindowManagerService對壁紙窗口(Wallpaper Window)的管理分析和Android窗口管理服務WindowManagerService對輸入法窗口(Input Method Window)的管理分析這兩篇文章知道,如果一個窗口要調整它所關聯的輸入法窗口和壁紙窗口的Z軸位置,那麼要調整的值就會保存在WindowManagerService類的成員變量mInputMethodAnimLayerAdjustment和mWallpaperAnimLayerAdjustment中,因此,只要將WindowManagerService類的成員變量mInputMethodAnimLayerAdjustment和mWallpaperAnimLayerAdjustment的值分別增加到前面所計算得到的輸入法窗口和壁紙窗口的Z軸位置上去,就可以得到輸入法窗口和壁紙窗口的最終Z軸位置,並且保存到用來對應的WindowState對象的成員變量mAnimLayer中。

       從上面的計算過程就可以知道,系統中所有類型的窗口的最終Z軸位置都保存在WindowState類的成員變量mAnimLayer中。

       三. 設置窗口的Z軸位置到SurfaceFlinger服務中去

       WindowManagerService服務在刷新系統的UI的時候,就會將系統中已經計算好了的窗口Z軸位置設置到SurfaceFlinger服務中去,以便SurfaceFlinger服務可以對系統中的窗口進行可見性計算以及合成和渲染等操作。

       從前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文可以知道,刷新系統UI是通過調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner來實現的,接下來我們就分析這個成員函數與設置窗口的Z軸位置到SurfaceFlinger服務中去相關的邏輯。

       爲了方便描述設置窗口的Z軸位置到SurfaceFlinger服務中去的過程,我們先列出WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現架構,如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                                     
    private final void performLayoutAndPlaceSurfacesLockedInner( 
            boolean recoveringMemory) { 
        ...... 
                                     
        Surface.openTransaction(); 
        ...... 
                                     
        try { 
            ...... 
            int repeats = 0; 
            int changes = 0; 
                                                 
            do { 
                repeats++; 
                if (repeats > 6) { 
                    ...... 
                    break; 
                } 
                                     
                // FIRST LOOP: Perform a layout, if needed. 
                if (repeats < 4) { 
                    changes = performLayoutLockedInner(); 
                    if (changes != 0) { 
                        continue; 
                    } 
                } else { 
                    Slog.w(TAG, "Layout repeat skipped after too many iterations"); 
                    changes = 0; 
                } 
                                     
                // SECOND LOOP: Execute animations and update visibility of windows. 
                ...... 
                                                     
            } while (changes != 0); 
                                     
            // THIRD LOOP: Update the surfaces of all windows.
            ......
            //更新窗口的繪圖表面的操作包括:
            //1. 設置窗口的大小
            //2. 設置窗口在X軸和Y軸上的位置
            //3. 設置窗口在Z軸上的位置
            //4. 設置窗口的Alpha通道
            //5. 設置窗口的變換矩陣
            ......
                                                      
        } catch (RuntimeException e) { 
            ...... 
        } 
                                     
        ...... 
                                     
        Surface.closeTransaction(); 
                                     
        ...... 
                                     
        // Destroy the surface of any windows that are no longer visible. 
        ...... 
                                     
        // Time to remove any exiting tokens? 
        ...... 
                                     
        // Time to remove any exiting applications? 
        ...... 
    } 
                                     
    ...... 
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       在前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文中,我們已經分析過WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現架構了,其中,設置窗口的Z軸位置到SurfaceFlinger服務中去是在更新窗口的繪圖表面的操作中進行的,即是在THIRD LOOP中進行的,同時設置的還包括窗口的大小、X軸和Y軸位置、Alpha通道和變換矩陣,這些代碼如下所示:


//更新窗口的繪圖表面的操作包括:
//1. 設置窗口的大小
//2. 設置窗口在X軸和Y軸上的位置
//3. 設置窗口在Z軸上的位置
//4. 設置窗口的Alpha通道值
//5. 設置窗口的變換矩陣
final int N = mWindows.size();
for (i=N-1; i>=0; i--) {
    WindowState w = mWindows.get(i);
    ......
    if (w.mSurface != null) {
        ......
        w.computeShownFrameLocked();
        ......
        boolean resize;
        int width, height;
        if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
            resize = w.mLastRequestedWidth != w.mRequestedWidth ||
            w.mLastRequestedHeight != w.mRequestedHeight;
            // for a scaled surface, we just want to use
            // the requested size.
            width  = w.mRequestedWidth;
            height = w.mRequestedHeight;
            w.mLastRequestedWidth = width;
            w.mLastRequestedHeight = height;
            w.mLastShownFrame.set(w.mShownFrame);
            try {
                ......
                w.mSurfaceX = w.mShownFrame.left;
                w.mSurfaceY = w.mShownFrame.top;
                w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
            } catch (RuntimeException e) {
                ......
                if (!recoveringMemory) {
                    reclaimSomeSurfaceMemoryLocked(w, "position");
                }
            }
        } else {
            resize = !w.mLastShownFrame.equals(w.mShownFrame);
            width = w.mShownFrame.width();
            height = w.mShownFrame.height();
            w.mLastShownFrame.set(w.mShownFrame);
        }
        if (resize) {
            if (width < 1) width = 1;
            if (height < 1) height = 1;
            if (w.mSurface != null) {
                try {
                    ......
                    w.mSurfaceResized = true;
                    w.mSurfaceW = width;
                    w.mSurfaceH = height;
                    w.mSurface.setSize(width, height);
                    w.mSurfaceX = w.mShownFrame.left;
                    w.mSurfaceY = w.mShownFrame.top;
                    w.mSurface.setPosition(w.mShownFrame.left,
                            w.mShownFrame.top);
                } catch (RuntimeException e) {
                    ......
                    if (!recoveringMemory) {
                        reclaimSomeSurfaceMemoryLocked(w, "size");
                    }
                }
            }
        }
        ......
        if (w.mAttachedHidden || !w.isReadyForDisplay()) {
            if (!w.mLastHidden) {
                w.mLastHidden = true;
                ......
                if (w.mSurface != null) {
                    w.mSurfaceShown = false;
                    try {
                        w.mSurface.hide();
                    } catch (RuntimeException e) {
                        ......
                    }
                }
            } 
            ......
        } else if (w.mLastLayer != w.mAnimLayer
                || w.mLastAlpha != w.mShownAlpha
                || w.mLastDsDx != w.mDsDx
                || w.mLastDtDx != w.mDtDx
                || w.mLastDsDy != w.mDsDy
                || w.mLastDtDy != w.mDtDy
                || w.mLastHScale != w.mHScale
                || w.mLastVScale != w.mVScale
                || w.mLastHidden) {   
            ......
            w.mLastAlpha = w.mShownAlpha;
            w.mLastLayer = w.mAnimLayer;
            w.mLastDsDx = w.mDsDx;
            w.mLastDtDx = w.mDtDx;
            w.mLastDsDy = w.mDsDy;
            w.mLastDtDy = w.mDtDy;
            w.mLastHScale = w.mHScale;
            w.mLastVScale = w.mVScale;
            ......
            if (w.mSurface != null) {
               try {
                    w.mSurfaceAlpha = w.mShownAlpha;
                    w.mSurface.setAlpha(w.mShownAlpha);
                    w.mSurfaceLayer = w.mAnimLayer;
                    w.mSurface.setLayer(w.mAnimLayer);
                    w.mSurface.setMatrix(
                            w.mDsDx*w.mHScale, w.mDtDx*w.mVScale,
                            w.mDsDy*w.mHScale, w.mDtDy*w.mVScale);
                } catch (RuntimeException e) {
                    .....
                    if (!recoveringMemory) {
                        reclaimSomeSurfaceMemoryLocked(w, "update");
                    }
                }
            }
            if (w.mLastHidden && !w.mDrawPending
                    && !w.mCommitDrawPending
                    && !w.mReadyToShow) {
                ......
                if (showSurfaceRobustlyLocked(w)) {
                    w.mHasDrawn = true;
                    w.mLastHidden = false;
                }
        } 
        ......
    }
    ......                         
}


       這段代碼通過一個for循環來遍歷保存在窗口堆棧的每一個WindowState對象,以便可以對系統中的每一個窗口的繪圖表面進行更新。注意,只有那些成員變量mSurface的值不等於null的WindowState對象,它們所描述的窗口才具有繪圖表面,因此需要對它們進行更新。


       在更新WindowState對象w所描述的窗口的繪圖表面之前,首先要調用它的成員函數computeShownFrameLocked來確定該窗口實際要顯示的大小、位置、Alpha通道和變換矩陣等信息,其中:

       1. 窗口實際要顯示的大小和X軸、Y軸位置保存在WindowState對象w的成員變量mShownFrame中。

       2. 窗口實際要顯示的Alpha通道保存在WindowState對象w的成員變量mShownAlpha中。

       3. 窗口實際要顯示的Z軸位置保存在WindowState對象w的成員變量mAnimLayer中。

       4. 窗口實際要使用的變換矩陣保存在WindowState對象w的成員變量mDsDx、mDtDx、mDsDy和mDtDy中。

       有了上述信息之後,我們就可以將WindowState對象w所描述的窗口實際要顯示的大小、位置、Alpha通道和變換矩陣等信息設置到SurfaceFlinger服務中去了。

       我們首先分析WindowState對象w所描述的窗口實際要顯示的大小、X軸和Y軸位置的設置過程,接着再分析WindowState對象w所描述的窗口實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣的設置過程。

       在調用WindowState對象w的成員函數computeShownFrameLocked來計算它所描述的窗口的大小的時候,是沒有考慮該窗口的大小是否設置有縮放因子的。

       當WindowState對象w所描述的窗口的大小設置有縮放因子的時候,那麼WindowState對象w的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_SCALED位就會等於1,這時候WindowState對象w所描述的窗口實際要顯示的大小是保存在它的成員變量mRequestedWidth和mRequestedHeight中的。在這種情況下,這段代碼就會執行以下操作:

       1. 計算WindowState對象w所描述的窗口實際要顯示的大小是否發生了變化。如果發生了變化,那麼就將變量resize的值設置爲true。注意,WindowState對象w所描述的窗口上一次實際要顯示的大小保存在成員變量mLastRequestedWidth和mLastRequestedHeight中,因此,當這兩個成員變量與其它兩個成員變量mRequestedWidth和mRequestedHeight的值不相等於時,就說明WindowState對象w所描述的窗口實際要顯示的大小是否發生了變化。

       2. 將WindowState對象w所描述的窗口實際要顯示的大小分別更新到成員變量mLastRequestedWidth和mLastRequestedHeight中,以及變量width和height中。

       3. 將WindowState對象w的成員變量mShownFrame的值保存在另外一個成員變量mLastShownFrame中,以便可以記錄WindowState對象w所描述的窗口上一次實際要顯示的大小和X軸、Y軸位置。

       4. 將WindowState對象w所描述的窗口的X軸和Y軸位置分別保存到成員變量mSurfaceX和mSurfaceY中,並且調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setPosition來將這兩個位置值設置到SurfaceFlinger服務中去。

       5. 在設置WindowState對象w所描述的窗口的X軸和Y軸位置到SurfaceFlinger服務中去的過程中,如果出現了異常,那麼就說明系統內存資源不足。在這種情況下,如果參數recoveringMemory的值等於false,那麼就說明WindowManagerService服務目前不是處於內存資源的回收過程中,於是就會調用WindowManagerService類的成員函數reclaimSomeSurfaceMemoryLocked來執行回收系統內存資源的操作。

       當WindowState對象w所描述的窗口的大小沒有設置有縮放因子的時候,那麼WindowState對象w的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_SCALED位就會等於0,這時候WindowState對象w所描述的窗口實際要顯示的大小是保存在它的成員變量mShownFrame中的。在這種情況下,這段代碼就會執行以下操作:

       1. 計算WindowState對象w所描述的窗口實際要顯示的大小是否發生了變化。如果發生了變化,那麼就將變量resize的值設置爲true。注意,這時候只要比較WindowState對象w的成員變量mLastShownFrame和mShownFrame所描述的兩個矩形區域的大小是否相等,就可以知道WindowState對象w所描述的窗口實際要顯示的大小是否發生了變化,因爲WindowState對象w的成員變量mLastShownFrame保存的是窗口上一次實際要顯示的大小。

       2. 將WindowState對象w所描述的窗口實際要顯示的大小分別保存在變量width和height中。

       3. 將WindowState對象w的成員變量mShownFrame的值保存在另外一個成員變量mLastShownFrame中,以便可以記錄WindowState對象w所描述的窗口上一次實際要顯示的大小和X軸、Y軸位置。

       執行完成以上操作之後,WindowState對象w所描述的窗口實際要顯示的X軸和Y軸位置就保存在成員變量mShownFrame所描述的一個 Rect對象的成員變量left和top中,而實際要顯示的大小就顯示在變量width和height中。這時候如果變量resize的值等於true,那麼就說明WindowState對象w所描述的窗口的大小發生了變化。在這種情況下,就需要執行以下操作:

       1. 重新設置WindowState對象w所描述的窗口的大小到SurfaceFlinger服務中去,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setSize來實現的。注意,如果前面計算得到WindowState對象w所描述的窗口的寬度width和高度height的值小於1,那麼就需要將它們的值設置爲1,因爲一個窗口的寬度和高度值是不能小於1的。

       2. 重新設置WindowState對象w所描述的窗口在X軸和Y軸上的位置到SurfaceFlinger服務中去,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setPosition來實現的。注意,在設置之前,還會將WindowState對象w所描述的窗口在X軸和Y軸上的位置保存在成員變量mSurfaceX和mSurfaceY中。

       3. 在設置WindowState對象w所描述的窗口的大小以及在X軸和Y軸上的位置到SurfaceFlinger服務中去的過程中,如果出現了異常,那麼同樣需要判斷參數recoveringMemory的值來決定是否需要WindowManagerService類的成員函數reclaimSomeSurfaceMemoryLocked來回收系統內存資源。

       設置好WindowState對象w所描述的窗口實際要顯示的大小、X軸和Y軸位置到SurfaceFlinger服務中去之後,接下來就要繼續設置它實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣了,不過只有當WindowState對象w所描述的窗口當前是處於可見狀態、並且這些值沒有發生變化的情況下才需要這樣做。

       當WindowState對象w的成員函數isReadyForDisplay的返回值等於false時,就說明WindowState對象w所描述的窗口當前是處於不可見狀態的。還有另外一種情況,即當WindowState對象w所描述的窗口是附加在另外一個窗口之上、並且這個被附加的窗口是不可見時,即WindowState對象w的成員變量mAttachedHidden的值等於true時,也是說明WindowState對象w所描述的窗口當前是處於不可見狀態的。

       在WindowState對象w所描述的窗口當前是處於不可見狀態的情況下,如果該窗口在上一次系統UI刷新時是處於可見狀態的,即WindowState對象w的成員變量mLastHidden的值等於true,那麼這時候就需要將WindowState對象w所描述的窗口隱藏起來,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數hide來實現的。注意,在調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數hide來隱藏窗口之前,需要分別將WindowState對象w的成員變量mLastHidden和mSurfaceShown的值設置爲true和false,以便可以正確描述窗口的不可見狀態。

       在WindowState對象w所描述的窗口當前是處於可見狀態的情況下,如果該窗口實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣發生了變化,那麼就需要將新的值設置到SurfaceFlinger服務中去,其中:

       1. WindowState對象w的成員變量mLastLayer與mAnimLayer的值不相等說明它描述的窗口的Z軸位置發生了變化。

       2. WindowState對象w的成員變量mLastAlpha與mShownAlpha的值不相等說明它描述的窗口的Alpha通道發生了變化。

       3. WindowState對象w的成員變量mLastDsDx、mLastDtDx、mLastDsDy、 mLastDtDy、mLastHScale、mLastVScale與成員變量mDsDx、mDtDx、mDsDy、 mDtDy、mHScale、mVScale的值不相等說明它描述的窗口的變換矩陣發生了變化。

       在WindowState對象w所描述的窗口當前是處於可見狀態的情況下,如果該窗口在上一次系統UI刷新時是處於可見狀態的,即WindowState對象w的成員變量mLastHidden的值等於true,那麼也是需要重新設置WindowState對象w所描述的窗口實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣到SurfaceFlinger服務中去的。

       無論如何,當需要重新設置WindowState對象w所描述的窗口實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣到SurfaceFlinger服務中去時,就需要執行以下操作:

       1. 重新設置WindowState對象w所描述的窗口的Alpha通道到SurfaceFlinger服務中去,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setAlpha來實現的。在設置之前,還會將WindowState對象w的成員變量mShownAlpha的值同時保存在成員變量mLastAlpha和mSurfaceAlpha中,以便可以記錄WindowState對象w所描述的窗口上一次所使用的Alpha通道。

       2. 重新設置WindowState對象w所描述的窗口的Z軸位置到SurfaceFlinger服務中去,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setLayer來實現的。在設置之前,還會將WindowState對象w的成員變量mAnimLayer的值同時保存在成員變量mLastLayer和mSurfaceLayer中,以便可以記錄WindowState對象w所描述的窗口上一次所使用的Z軸位置。

       3. 重新設置WindowState對象w所描述的窗口的變換矩陣到SurfaceFlinger服務中去,這是通過調用WindowState對象w的成員變量mSurface所指向的一個Surface對象的成員函數setMatrix來實現的。在設置之前,還會將WindowState對象w的成員變量成員變量mDsDx、mDtDx、mDsDy、 mDtDy、mHScale、mVScale的值分別保存在成員變量mLastDsDx、mLastDtDx、mLastDsDy、 mLastDtDy、mLastHScale、mLastVScale中,以便可以記錄WindowState對象w所描述的窗口上一次所使用的變換矩陣。注意,WindowState對象的成員變量mHScale和mVScale描述的窗口在寬度和高度上的縮放因子,因此,在設置窗口的變換矩陣時,需要乘以這些因子纔可以得到正確的變換矩陣參數。

       4. 在設置WindowState對象w所描述的窗口的Alpha通道、Z軸位置以及實際要使用的變換矩陣到SurfaceFlinger服務的過程中,如果出現了異常,那麼同樣需要判斷參數recoveringMemory的值來決定是否需要WindowManagerService類的成員函數reclaimSomeSurfaceMemoryLocked來回收系統內存資源。

       將WindowState對象w所描述的窗口實際要顯示的Alpha通道、Z軸位置以及實際要使用的變換矩陣設置到SurfaceFlinger服務之後,如果WindowState對象w所描述的窗口滿足以下條件:

       1. 上一次處於不可見狀態,即WindowState對象w的成員變量mLastHidden的值等於true;

       2. UI已經繪製完成,即WindowState對象w的成員變量mDrawPending和mCommitDrawPending值等於false;

       3. 不是處於等待同一個窗口令牌的其它窗口的完成UI繪製的狀態,即WindowState對象w的成員變量mReadyToShow的值等於false;

       那麼就說明現在就是時候要將WindowState對象w所描述的窗口顯示出來了,這是通過調用WindowManagerService類的成員函數showSurfaceRobustlyLocked來實現的。如果WindowManagerService類的成員函數showSurfaceRobustlyLocked的返回值等於true,那麼就說明WindowManagerService服務已經成功地通知SurfaceFlinger服務將WindowState對象w所描述的窗口顯示出來,於是就會分別將WindowState對象w的成員變量mHasDrawn和mLastHidden的值設置爲true和false,以便可以表示WindowState對象w所描述的窗口的UI已經繪製完成,並且已經顯示出來。

       WindowManagerService類的成員函數showSurfaceRobustlyLocked的實現如下所示:


public class WindowManagerService extends IWindowManager.Stub 
        implements Watchdog.Monitor { 
    ...... 
                         
    boolean showSurfaceRobustlyLocked(WindowState win) {
        try {
            if (win.mSurface != null) {
                win.mSurfaceShown = true;
                win.mSurface.show();
                ......
            }
            return true;
        } catch (RuntimeException e) {
            ......
        }
        reclaimSomeSurfaceMemoryLocked(win, "show");
        return false;
    }
                         
    ...... 
}


       這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。


       WindowManagerService類的成員函數showSurfaceRobustlyLocked用來通知SurfaceFlinger服務將參數win所描述的窗口顯示出來,這是通過調用WindowState對象win的成員變量mSurface所指向的一個Surface對象的成員函數show來實現的。注意,在通知SurfaceFlinger服務將WindowState對象win所描述的窗口顯示出來之前,還會將它的成員變量mSurfaceShown的值設置爲true。

       如果在通知SurfaceFlinger服務將WindowState對象win所描述的窗口顯示出來的過程出現了異常,那麼WindowManagerService類的成員函數showSurfaceRobustlyLocked就會調用另外一個成員函數reclaimSomeSurfaceMemoryLocked來回收系統內存資源。

       從上面分析可以知道,一個窗口的顯示和隱藏,以及大小、X軸和Y軸位置、Z軸位置、Alpha通道和變換矩陣設置,是通過調用Java層的Surface類的成員函數show、hide、setSize、setPosition、setLayer、setAlpha和setMatrix來實現的,它們都是一些JNI方法,定義在文件frameworks/base/core/java/android/view/Surface.java中,如下所示:


public class Surface implements Parcelable {
    ......
    private int mSurfaceControl;
    ......
    /**
     * set surface parameters.
     * needs to be inside open/closeTransaction block
     */
    public native   void setLayer(int zorder);
    public native   void setPosition(int x, int y);
    public native   void setSize(int w, int h);
    public native   void hide();
    public native   void show();
    ......
    public native   void setAlpha(float alpha);
    public native   void setMatrix(float dsdx, float dtdx,
                                   float dsdy, float dtdy);
    ......
}


      這些JNI方法是由C++層中的函數Surface_show、Surface_hide、Surface_setSize、Surface_setPosition、Surface_setLayer、Surface_setAlpha和Surface_setMatrix來實現的,如下所示:


static void Surface_setLayer(
        JNIEnv* env, jobject clazz, jint zorder)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->setLayer(zorder);
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setPosition(
        JNIEnv* env, jobject clazz, jint x, jint y)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->setPosition(x, y);
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setSize(
        JNIEnv* env, jobject clazz, jint w, jint h)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->setSize(w, h);
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_hide(
        JNIEnv* env, jobject clazz)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->hide();
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_show(
        JNIEnv* env, jobject clazz)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->show();
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setAlpha(
        JNIEnv* env, jobject clazz, jfloat alpha)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->setAlpha(alpha);
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setMatrix(
        JNIEnv* env, jobject clazz,
        jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy)
{
    const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
    if (surface == 0) return;
    status_t err = surface->setMatrix(dsdx, dtdx, dsdy, dtdy);
    if (err<0 && err!=NO_INIT)
        doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
        這些JNI方法定義在文件frameworks/base/core/jni/android_view_Surface.cpp中。


       這些JNI都有一個共同的特點,即先調用函數getSurfaceControl來獲得與參數clazz所描述的一個Java層的Surface對象所對應的一個SurfaceControl對象。有了這個SurfaceControl對象之後,就可以分別調用它的成員函數show、hide、setSize、setPosition、setLayer、setAlpha和setMatrix來通知SurfaceFlinger服務來顯示和隱藏一個窗口,以及設置一個窗口大小、X軸和Y軸位置、Z軸位置、Alpha通道和變換矩陣。

       從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文可以知道,每一個Activity窗口在Java層都對應有兩個Surface對象,其中一個位於應用程序進程這一側,而另外一個位於WindowManagerService服務這一側。每一個位於應用程序進程這一側的Java層的Surface對象在C++層中都對應有一個Surface對象,而每一個位於WindowManagerService服務這一側的Java層的Surface對象在C++層中都對應有一個SurfaceControl對象,這個C++層的SurfaceControl對象的地址就保存在Java層的Surface對象的成員變量mSurfaceControl中。

      從上面的分析可以知道,我們目前正在操作的是正在位於WindowManagerService服務這一側的Java層的Surface對象,因此,通過調用函數getSurfaceControl就可以在C++層中獲得一個對應的SurfaceControl對象,而有了這個SurfaceControl對象之後,就可以用來通知SurfaceFlinger服務更新一個窗口的屬性,這一點可以參考前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃和Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃兩個系列的文章。

      至此,WindowManagerService服務計算窗口Z軸位置的過程就分析完成了,這個過程如下所示:

      1. WindowManagerService服務將窗口排列在一個窗口堆棧中;

      2. WindowManagerService服務根據窗口類型以及窗口在窗口堆棧的位置來計算得窗口的Z軸位置;

      3. WindowManagerService服務通過Java層的Surface類的成員函數setLayer來將窗口的Z軸位置設置到SurfaceFlinger服務中去;

      4. Java層的Surface類的成員函數setLayer又是通過調用C++層的SurfaceControl類的成員函數setLayer來將窗口的Z軸位置設置到SurfaceFlinger服務中去的;

      通過這篇文章以及前面三篇文章(窗口組織、輸入法窗口、壁紙窗口)的學習,我們對WindowManagerService服務對窗口的管理就有一個比較深刻的認識了,在接下來的文章中,我們還將繼續分析ActivityWindowManager服務和WindowManagerService服務是如何協作來完成Activity窗口的顯示過程的,敬請關注!

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

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