通過前面幾篇文章的學習,我們知道了在Android系統中,無論是普通的Activity視窗,還是特殊的輸入法視窗和桌面視窗,它們都是被WindowManagerService服務組織在一個視窗堆棧中的,其中,Z軸位置較大的視窗排列在Z軸位置較小的視窗的上面。有了這個視窗堆棧之後,WindowManagerService服務就可以按照一定的規則計算每一個視窗的Z軸位置了,本文就詳細分析這個計算過程。
《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!
基于視窗堆棧來計算視窗的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,歡迎關注!