在Android系統中,壁紙窗口和輸入法窗口一樣,都是一種特殊類型的窗口,而且它們都是喜歡和一個普通的Activity窗口纏綿在一起。大家可以充分地想象這樣的一個3W場景:輸入法窗口在上面,壁紙窗口在下面,Activity窗口夾在它們的中間。在前面一篇文章中,我們已經分析過輸入法窗口是如何壓在Activity窗口上面的了。在這篇文章中,我們就將繼續分析壁紙窗口是如何貼在Activity窗口下面的。
一個Activity窗口如果需要顯示壁紙,那麼它必須滿足以下兩個條件:
1. 背景是半透明的,例如,它在AndroidManifest.xml文件中的android:theme屬性設置爲Theme.Translucent:
<activity android:name=".WallpaperActivity" android:theme="@android:style/Theme.Translucent"> ...... </activity>
2. 窗口屬性中的WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER位設置爲1:
<activity android:name=".WallpaperActivity" android:theme="@android:style/Theme.Translucent"> ...... </activity>
滿足了以上兩個條件之後,Activity窗口和壁紙窗口的位置關係就如圖1所示:
圖1 Activity窗口和Wallpaper窗口的位置關係
在前面Android窗口管理服務WindowManagerService組織窗口的方式分析一文中提到,WindowManagerService服務是使用堆棧來組織系統中的窗口的,因此,如果我們在窗口堆棧中觀察Activity窗口和壁紙窗口,它們的位置關係就如圖2所示:
圖2 Activity窗口和Wallpaper窗口在窗口堆棧中的位置關係
圖2中的對象的關係如下所示:
1. 在ActivityManagerService服務內部的Activity組件堆棧頂端的ActivityRecord對象N描述的是系統當前激活的Activity組件。
2. ActivityRecord對象N在WindowManagerService服務內部的窗口令牌列表頂端對應有一個AppWindowToken對象N。
3. AppWindowToken對象N在WindowManagerService服務內部的窗口堆棧中對應有一個WindowState對象N,用來描述系統當前激活的Activity組件窗口。
4. WindowState對象N下面有一個WindowState對象WP,用來描述系統中的壁紙窗口。
5. 系統中的壁紙窗口在WindowManagerService服務內部中對應的窗口令牌是由WindowToken對象WP來描述的。
6. WindowToken對象WP在WallpaperManagerService服務中對應有一個Binder對象。
總的來說,就是圖2描述了系統當前激活的Activity窗口需要顯示壁紙的情景。WindowManagerService服務的職能之一就是要時刻關注系統中是否有窗口需要顯示壁紙。WindowManagerService服務一旦發現有窗口需要顯示壁紙,那麼就會調整壁紙窗口在窗口堆棧中的位置,使得它放置在需要顯示壁紙的窗口的下面。此外,需要顯示壁紙的窗口還可以設置壁紙窗口在X軸和Y軸上的偏移位置,以便可以將壁紙窗口的某一部分指定爲它的背景。
接下來,我們就首先分析兩個需要調整壁紙窗口在窗口堆棧中的位置的情景,然後再分析壁紙窗口在X軸和Y軸上的偏移位置的調整過程,最後分析壁紙窗口在窗口堆棧中的位置調整過程。
一. 調整壁紙窗口在窗口堆棧中的位置的情景
第一個需要調整壁紙窗口在窗口堆棧中的位置的情景是增加一個窗口到WindowManagerService服務去的時候。從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,增加一個窗口到WindowManagerService服務最終是通過調用WindowManagerService類的成員函數addWindow來實現的。接下來我們就主要分析這個函數中與壁紙窗口調整相關的邏輯,如下所示:
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); if (token == null) { ...... if (attrs.type == TYPE_WALLPAPER) { ...... return WindowManagerImpl.ADD_BAD_APP_TOKEN; } ...... } ...... win = new WindowState(session, client, token, attachedWindow, attrs, viewVisibility); ...... if (attrs.type == TYPE_INPUT_METHOD) { ...... } else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) { ...... } 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服務來的是一個壁紙窗口,即參數attrs所描述的一個WindowManager.LayoutParams對象的成員變量type的值等於TYPE_WALLPAPER,那麼就要求與該壁紙窗口所對應的類型爲WindowToken的窗口令牌已經存在,否則的話,WindowManagerService類的成員函數addWindow就會直接返回一個錯誤碼WindowManagerImpl.ADD_BAD_APP_TOKEN給調用者。這個類型爲WindowToken的窗口令牌是WallpaperManagerService服務請求WindowManagerService服務創建的,即調用WindowManagerService類的成員函數addWindowToken來創建的,具體可以參考前面Android窗口管理服務WindowManagerService組織窗口的方式分析一文。
如果當前增加到WindowManagerService服務來的既不是一個輸入法窗口,也不是一個輸入法對話框,那麼WindowManagerService類的成員函數addWindow就會調用另外一個成員函數addWindowToListInOrderLocked來將前面爲它所創建的一個WindowState對象win增加到窗口堆棧的合適位置上去。
如果前面增加到窗口堆棧中的窗口是一個壁紙窗口,即參數attrs所描述的一個WindowManager.LayoutParams對象的成員變量type的值等於TYPE_WALLPAPER,或者是一個需要顯示壁紙的窗口,即參數attrs所描述的一個WindowManager.LayoutParams對象的成員變量flags的值的FLAG_SHOW_WALLPAPER位等於1,那麼就說明需要調整壁紙窗口在窗口堆棧中的位置,使得它位於需要顯示壁紙的窗口的下面,這是通過調用WindowManagerService類的成員函數adjustWallpaperWindowsLocked來實現的。
最後,由於增加了一個窗口到窗口堆棧中,以及窗口堆棧的窗口位置發生了變化,因此,就需要重新各個窗口的Z軸位置,這是通過調用WindowManagerService類的成員函數assignLayersLocked來實現的。
在這個情景中,主要涉及到了WindowManagerService類的三個成員函數addWindowToListInOrderLocked、adjustWallpaperWindowsLocked和assignLayersLocked,其中,成員函數addWindowToListInOrderLocked的實現可以參考前面前面Android窗口管理服務WindowManagerService組織窗口的方式分析一文,成員函數assignLayersLocked的實現在接下來的一篇文章中再分析,本文主要是關注成員函數adjustWallpaperWindowsLocked的實現。
第二個需要調整壁紙窗口在窗口堆棧中的位置的情景是一個應用程序進程請求WindowManagerService服務重新佈局一個窗口的時候。從前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文可以知道,應用程序進程請求WindowManagerService服務重新佈局一個窗口最終是通過調用WindowManagerService類的成員函數relayoutWindow來實現的。接下來我們就主要分析這個函數中與壁紙窗口調整相關的邏輯,如下所示:
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) { boolean displayed = false; ...... synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); ...... int attrChanges = 0; ...... if (attrs != null) { ...... attrChanges = win.mAttrs.copyFrom(attrs); } ...... boolean wallpaperMayMove = win.mViewVisibility != viewVisibility && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; ...... if (viewVisibility == View.VISIBLE && (win.mAppToken == null || !win.mAppToken.clientHidden)) { displayed = !win.isVisibleLw(); ...... if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) { // To change the format, we need to re-build the surface. win.destroySurfaceLocked(); displayed = true; } ...... } ...... boolean assignLayers = false; ...... if (wallpaperMayMove) { if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { assignLayers = true; } } ...... if (assignLayers) { assignLayersLocked(); } ...... performLayoutAndPlaceSurfacesLocked(); if (displayed && win.mIsWallpaper) { updateWallpaperOffsetLocked(win, mDisplay.getWidth(), mDisplay.getHeight(), false); } ...... } ...... return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0) | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0); } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
應用程序進程在請求WindowManagerService服務重新佈局一個窗口的時候,這個窗口的一些佈局參數可能會發生變化,而這些變化可能會引發系統的壁紙窗口在窗口堆棧中的位置發生變化。如果系統的壁紙窗口在窗口堆棧中的位置發生了變化,那麼就需要調整它們在窗口堆棧中的位置。
WindowManagerService類的成員函數relayoutWindow首先調用根據參數session和client來調用另外一個成員函數windowForClientLocked,以便可以獲得用來描述要重新佈局的窗口的一個WindowState對象win。
WindowState對象win的成員變量mViewVisibility描述的是窗口上一次佈局時的可見性,而參數viewVisibility描述的是窗口當前的可見性,當它們的值不相等時,就意味着窗口的可見性發生了變化。在窗口的可見性發生了變化的情況下,如果正在請求重新佈局的是一個需要顯示壁紙的窗口,即WindowState對象win的成員變量mAttrs所指向的是一個WindowManager.LayoutParams對象的成員變量flags的FLAG_SHOW_WALLPAPER位等於1,那麼就說明可能需要調整壁紙窗口在窗口堆棧中的位置,以便它可以位於WindowState對象win所描述的窗口的下面,這時候變量wallpaperMayMove的值就會等於true。
WindowManagerService類的成員函數relayoutWindow執行了一系列的其它操作之後,接下來就會判斷變量wallpaperMayMove的值是否等於true。如果等於true的話,那麼就會調用另外一個成員函數adjustWallpaperWindowsLocked來調整壁紙窗口在窗口堆棧中的位置,以便它可以位於需要顯示壁紙的窗口的下面。WindowManagerService類的成員函數adjustWallpaperWindowsLocked的返回值是一個整數,當它的ADJUST_WALLPAPER_LAYERS_CHANGED位等於1的時候,就說明壁紙窗口在窗口堆棧的位置發生了變化,於是就會將變量assignLayers的值設置爲true,以便接下來可以調用WindowManagerService類的成員函數assignLayersLocked來重新計算系統中各個窗品的Z軸位置。
變量displayed用來描述WindowState對象win所描述的窗口在當前佈局中是由不可見變爲可見的。在滿足以下的條件之下,WindowState對象win所描述的窗口是由不可見變爲可見的:
1. 參數viewVisibility的值等於View.VISIBLE,即應用程序進程請求顯示WindowState對象win所描述的窗口。
2. WindowState對象win描述的是一個Activity窗口,即它的成員變量mAppToken不等於null,並且它所指向的AppWindowToken對象的成員變量clientHidden的值等於false,即WindowState對象win的窗口所對應的Activity組件當前是可見的。注意,如果WindowState對象win描述的不是一個Activity窗口,即它的成員變量mAppToken等於null,那麼就可以忽略條件2。
3. WindowState對象win所描述的窗口上一次是不可見的,即調用WindowState對象win的成員函數isVisibleLw的返回值等於false。
此外,在滿足條件1和條件2的情況下,如果WindowState對象win所描述的窗口的像素格式發生了變化,那麼就需要將該窗口的繪圖表面銷燬掉,然後再重新創建一個,這時候也會認爲該窗口由不可見變爲了可見。
參數attrs所指向的一個WindowManager.LayoutParams對象是用來保存WindowState對象win所描述的窗口在當前佈局中所使用的佈局參數的,而WindowState對象win的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象是用來保存WindowState對象win所描述的窗口在上一次佈局所使用的佈局參數的。在將參數attrs所指向的一個WindowManager.LayoutParams對象的內容拷貝到WindowState對象win的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的過程中,如果某些佈局參數發生了變化,那麼就會記錄在變量attrChanges中。當變量attrChanges的WindowManager.LayoutParams.FORMAT_CHANGED位等於1時,就說明WindowState對象win所描述的窗口的像素格式發生了變化,因此,WindowManagerService類的成員函數relayoutWindow就會調用WindowState對象win的成員函數destroySurfaceLocked來銷燬該窗口的繪圖表面,並且將變量displayed的值設置爲true。
WindowManagerService類的成員函數relayoutWindow調用另外一個成員函數performLayoutAndPlaceSurfacesLocked來對WindowState對象win所描述的窗口進行了佈局之後,如果發現變量displayed的值等於true,並且WindowState對象win描述的是一個壁紙窗口,即它的成員變量mIsWallpaper的值等於true,那麼還需要調用另外一個成員函數updateWallpaperOffsetLocked來重新計算該壁紙窗口在X軸和Y軸上的偏移位置,以便可以將它的某一部分區域指定在需要顯示壁紙的窗口的背景。
在這個情景中,主要涉及到了WindowManagerService類的四個成員函數adjustWallpaperWindowsLocked、updateWallpaperOffsetLocked、performLayoutAndPlaceSurfacesLocked和assignLayersLocked,其中,成員函數performLayoutAndPlaceSurfacesLocked的實現框架可以參考前面Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文,成員函數assignLayersLocked的實現如上所述在接下來的一篇文章中再分析,本文主要是關注成員函數adjustWallpaperWindowsLocked和updateWallpaperOffsetLocked的實現。
從上面的分析就可以知道,在佈局一個窗口的過程中,可能需要調用WindowManagerService類的成員函數updateWallpaperOffsetLocked和adjustWallpaperWindowsLocked來調整壁紙窗口在X軸和Y軸上的偏移位置和在窗口堆棧中的位置。接下來我們就分別分析壁紙窗口在X軸和Y軸上的偏移位置和在窗口堆棧中的位置的調整過程。
二. 調整壁紙窗口在X軸和Y軸上的偏移位置
壁紙窗口的大小是可以大於屏幕大小的。在這種情況下,需要顯示壁紙的Activity窗口就需要指定壁紙在X軸和Y軸上的偏移位置,以便可以將壁紙的某一部分作爲窗口的背景。
假設壁紙窗口的大小爲(WallpaperWidth, WallpaperHeight),屏幕的大小爲(DisplayWidth, DisplayHeight),並且壁紙在X軸和Y軸上的偏移位置爲WallpaperX和WallpaperY,其中,WallpaperWidth > DisplayWidth,WallpaperHeight > DisplayHeight,0.0 <= WallpaperX <= 1.0,0.0 <= WallpaperY <= 1.0,如圖3所示:
圖3 指定壁紙窗口在X軸和Y軸上的偏移位置
這時候壁紙窗口在X軸和Y軸上的偏移位置的絕對值XOffset和YOffset就分別等於(WallpaperWidth - DisplayWidth)* WallpaperX和(WallpaperHeight - DisplayHeight)* WallpaperY。這意味道着:
1. 當WallpaperX = WallpaperY = 0.0時,取壁紙窗口的左上角區域作爲窗口背景。
2. 當WallpaperX = WallpaperY = 0.5時,取壁紙窗口的中間區域作爲窗口背景。
3. 當WallpaperX = WallpaperY = 1.0時,取壁紙窗口的右下角區域作爲窗口背景。
除了使用WallpaperX和WallpaperY來描述壁紙窗口在X軸和Y軸上的偏移位置之外,WindowManagerService服務還使用WallpaperXStep和WallpaperYStep來描述壁紙窗口跨越了多少個虛擬屏幕。例如,假設一個Activity窗口在X軸上有3個虛擬屏幕,即它的實際寬度是屏幕寬度的3倍,而在Y軸上有一個屏幕,即它的實際高度剛好等於屏幕高度,並且壁紙窗口的寬度也剛好是屏幕寬度的3倍,而高度也剛好是等於屏幕高度,那麼WallpaperXStep和WallpaperYStep的值就可以分別指定爲0.5和0,這意味着:
1. 第1個虛擬屏幕取壁紙窗口的左邊三分之一的區域作爲窗口背景,相當於是將壁紙窗口在X軸和Y軸上的偏移位置WallpaperX和WallpaperY的值分別設置爲0.0和0.0。
2. 第2個虛擬屏幕取壁紙窗口的中間三分之一的區域作爲窗口背景,相當於是將壁紙窗口在X軸和Y軸上的偏移位置WallpaperX和WallpaperY的值分別設置爲0.5和0.0。
3. 第3個虛擬屏幕取壁紙窗口的右邊三分之一的區域作爲窗口背景,相當於是將壁紙窗口在X軸和Y軸上的偏移位置WallpaperX和WallpaperY的值分別設置爲1.0和0.0。
一般地,如果一個Activity窗口在X軸上有N個虛擬屏幕,而在Y軸上有M個虛擬屏幕,那麼它就會將壁紙窗口的WallpaperXStep和WallpaperYStep值分別設置爲1.0 / (N - 1)和1.0 / (M - 1)。對於WindowManagerService服務來說,它並不關心壁紙窗口的WallpaperXStep和WallpaperYStep值,而只關心壁紙窗口的WallpaperX和WallpaperY值,因爲通過後兩者,它就可以知道怎麼顯示壁紙窗口了。壁紙窗口的WallpaperXStep和WallpaperYStep值是用來傳遞給提供壁紙的服務的。提供壁紙的服務一旦知道壁紙窗口的WallpaperXStep和WallpaperYStep值是多少,就可以知道當前需要顯示避紙的窗口有多少個虛擬屏幕。
上面提到的與壁紙窗口在X軸和Y軸上的偏移位置相關的六個狀態WallpaperX、WallpaperY、WallpaperXStep、WallpaperYStep、XOffset和YOffset由WindowManagerService服務來統一維護,它們分別對應於WindowState類的六個成員變量mWallpaperX、mWallpaperY、mWallpaperXStep、mWallpaperYStep、mXOffset和mYOffset,如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... float mLastWallpaperX = -1; float mLastWallpaperY = -1; float mLastWallpaperXStep = -1; float mLastWallpaperYStep = -1; ...... private final class WindowState implements WindowManagerPolicy.WindowState { ...... // If a window showing a wallpaper: the requested offset for the // wallpaper; if a wallpaper window: the currently applied offset. float mWallpaperX = -1; float mWallpaperY = -1; // If a window showing a wallpaper: what fraction of the offset // range corresponds to a full virtual screen. float mWallpaperXStep = -1; float mWallpaperYStep = -1; // Wallpaper windows: pixels offset based on above variables. int mXOffset; int mYOffset; ...... } ...... }
這段代碼定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
此外,WindowManagerService類還使用四個成員變量mLastWallpaperX、mLastWallpaperY、mLastWallpaperXStep和mLastWallpaperYStep來記錄壁紙窗口上一次所使用的WallpaperX、WallpaperY、WallpaperXStep和WallpaperYStep值。
在Android系統中,提供壁紙功能的組件叫做WallpaperService,它是一個Service組件,是由壁紙管理服務WallpaperManagerService負責啓動的。WallpaperService有兩個內部類BaseIWindow和Engine,其中,BaseIWindow是一個實現了IWindow接口的Binder本地對象類,用來和WindowManagerService服務通信,而Engine是一個真正用來實現壁紙功能的類。當一個Activity窗口需要指定壁紙窗口的某一部分區域作爲它的背景時,它就會通過WallpaperManager類來通知WallpaperService設置壁紙窗口在X軸和Y軸上的偏移位置,這個過程如圖4所示:
圖4 壁紙窗口在X軸和Y軸上的偏移位置的設置過程
這個過程大概如下所示:
Step 1. 需要顯示壁紙的Activity組件調用WallpaperManager類的成員函數setWallpaperOffsetSteps來設置壁紙窗口的WallpaperXStep和WallpaperYStep值。
Step 2. 需要顯示壁紙的Activity組件調用WallpaperManager類的成員函數setWallpaperOffsets來設置壁紙窗口的WallpaperX和WallpaperY值。
Step 3. 一個類型爲Session的Binder代理對象的成員函數setWallpaperPosition會被調用來通知WindowManagerService服務來重新計算壁紙窗口在X軸和Y軸上的偏移位置,傳遞的參數包括在Step 1和Step 2中所設置的WallpaperXStep、WallpaperYStep、WallpaperX和WallpaperY四個值。
Step 4. WindowManagerService類的成員函數setWindowWallpaperPositionLocked會被調用來保存從前面Step 3傳遞過來的WallpaperXStep、WallpaperYStep、WallpaperX和WallpaperY值。
Step 5. WindowManagerService類的成員函數updateWallpaperOffsetLocked會被調用來計算壁紙窗口在X軸和Y軸上的偏移位置的絕對值XOffset和YOffset,是根據壁紙窗口的大小(WallpapperWidth, WallpaperHeight)、屏幕的大小(DisplayWidth, DisplayHeight),以及保存在前面Step 4中的WallpaperX和WallpaperY來計算的。
Step 6. 在WallpaperService類內部的一個BaseIWindow對象的成員函數dispatchWallpaperOffsets會被調用來通知WallpaperService服務,壁紙窗口在X軸和Y軸上的偏移位置發生改變了,傳遞過來的參數包括壁紙窗口的XOffset、YOffset、WallpaperXStep和WallpaperYStep值。
Step 7. 在WallpaperService類內部的一個Engine對象的成員函數doOffsetsChanged會被調用來處理壁紙窗口在X軸和Y軸上的偏移位置變化事件。
Step 8. Engine類的成員函數doOffsetsChanged會調用另外一個成員函數onOffsetsChanged來分發壁紙窗口在X軸和Y軸上的偏移位置變化事件。Engine類的成員函數onOffsetsChanged一般是由其子類來重寫的,以便子類可以實現自己的壁紙效果。
本文不打算詳細這八個步驟,而主要關注Step 3、Step 4和Step 5這三步是如何計算壁紙窗口在X軸和Y軸上的偏移位置的,即主要關注Session類的成員函數setWallpaperPosition,以及WindowManagerService類的成員函數setWindowWallpaperPositionLocked和updateWallpaperOffsetLocked的實現。
Session類的成員函數setWallpaperPosition的實現如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { ...... public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); try { setWindowWallpaperPositionLocked( windowForClientLocked(this, window, true), x, y, xStep, yStep); } finally { Binder.restoreCallingIdentity(ident); } } } ...... } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
Session類的成員函數setWallpaperPosition首先調用WindowManagerService類的成員函數windowForClientLocked來找到與參數window所對應的一個WindowState對象,這個WindowState對象描述的是要改變壁紙窗口位置的窗口,接着再調用WindowManagerService類的另外一個成員函數setWindowWallpaperPositionLocked來執行設置壁紙窗口在X軸和Y軸的偏移位置的操作。
WindowManagerService類的成員函數setWindowWallpaperPositionLocked的實現如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public void setWindowWallpaperPositionLocked(WindowState window, float x, float y, float xStep, float yStep) { if (window.mWallpaperX != x || window.mWallpaperY != y) { window.mWallpaperX = x; window.mWallpaperY = y; window.mWallpaperXStep = xStep; window.mWallpaperYStep = yStep; if (updateWallpaperOffsetLocked(window, true)) { performLayoutAndPlaceSurfacesLocked(); } } } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService類的成員函數setWindowWallpaperPositionLocked首先檢查參數window所描述的WindowState對象上一次所設置的壁紙窗口的偏移位置與參數x和y所描述的偏移位置是否不一樣。如果不一樣的話,那麼就會分別將參數x、y、xStep和yStep分別保存在參數window所描述的WindowState對象的成員變量mWallpaperX、mWallpaperY、mWallpaperXStep和mWallpaperYStep中,並且調用WindowManagerService類的成員函數updateWallpaperOffsetLocked來更新系統中的壁紙窗口的偏移位置。
如果WindowManagerService類的成員函數updateWallpaperOffsetLocked的返回值等於true,那麼就說明它更新了系統中的壁紙窗口的偏移位置,因此,就需要調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked來刷新系統的UI。
接下來我們繼續分析WindowManagerService類的成員函數updateWallpaperOffsetLocked的實現,如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); // If non-null, this is the currently visible window that is associated // with the wallpaper. WindowState mWallpaperTarget = null; ...... boolean updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); boolean changed = false; WindowState target = mWallpaperTarget; if (target != null) { if (target.mWallpaperX >= 0) { mLastWallpaperX = target.mWallpaperX; } else if (changingTarget.mWallpaperX >= 0) { mLastWallpaperX = changingTarget.mWallpaperX; } if (target.mWallpaperY >= 0) { mLastWallpaperY = target.mWallpaperY; } else if (changingTarget.mWallpaperY >= 0) { mLastWallpaperY = changingTarget.mWallpaperY; } } int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { curTokenIndex--; WindowToken token = mWallpaperTokens.get(curTokenIndex); int curWallpaperIndex = token.windows.size(); while (curWallpaperIndex > 0) { curWallpaperIndex--; WindowState wallpaper = token.windows.get(curWallpaperIndex); if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) { wallpaper.computeShownFrameLocked(); changed = true; // We only want to be synchronous with one wallpaper. sync = false; } } } return changed; } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
當WindowManagerService類的成員變量mWallpaperTarget的值不等於null時,它所指向的一個WindowState對象描述的是系統當前可見的並且需要顯示壁紙的窗口。在這種情況下,要將這個WindowState對象當前正在使用的壁紙窗口在X軸和Y軸上的偏移位置分別保存在WindowManagerService類的成員變量mLastWallpaperX和mLastWallpaperY,以便接下來可以用來計算壁紙窗口在X軸和Y軸上的偏移位置的絕對值。
注意,如果WindowManagerService類的成員變量mWallpaperTarget所指向的一個WindowState對象的成員變量mWallpaperX(mWallpaperY)的值小於0,那麼就說明這個WindowState對象所描述的窗口還沒有設置過壁紙窗口在X軸上(Y軸上)的偏移位置,這時候就需要將參數changingTarget所指向的一個WindowState對象的成員變量mWallpaperX(mWallpaperY)的值保存在WindowManagerService類的成員變量mLastWallpaperX(mLastWallpaperY)中,前提也是它的值大於等於0,即它描述的是一個有效的偏移值。
WindowManagerService類的成員變量mWallpaperTokens保存的是一系列與壁紙相關的窗口令牌,與這些窗口令牌所對應的窗口就是系統當前所設置的壁紙窗口。WindowManagerService類的成員函數updateWallpaperOffsetLocked依次調用另外一個四參數版本的成員函數updateWallpaperOffsetLocked來更新系統當前所設置的每一個壁紙窗口在X軸和Y軸上的偏移位置。
注意, WindowManagerService類的四個參數版本的成員函數updateWallpaperOffsetLocked的最後一個參數sync是一個布爾值,用來表示在更新壁紙窗口在X軸和Y軸上的偏移位置的時候,是否需要同步等待提供壁紙窗口的服務處理完成壁紙窗口在X軸和Y軸上的偏移位置變化事件。參數sync本身也是由兩個參數版本的成員函數updateWallpaperOffsetLocked的調用者傳進來的,它的值即使等於true,兩個參數版本的成員函數updateWallpaperOffsetLocked也只會同步等待提供第一個壁紙窗口的服務處理完成壁紙窗口在X軸和Y軸上的偏移位置變化事件。
WindowManagerService類的四個參數版本的成員函數updateWallpaperOffsetLocked的實現如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... boolean updateWallpaperOffsetLocked(WindowState wallpaperWin, int dw, int dh, boolean sync) { boolean changed = false; boolean rawChanged = false; float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : 0.5f; float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw; int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0; changed = wallpaperWin.mXOffset != offset; if (changed) { ...... wallpaperWin.mXOffset = offset; } if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) { wallpaperWin.mWallpaperX = wpx; wallpaperWin.mWallpaperXStep = wpxs; rawChanged = true; } float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh; offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0; if (wallpaperWin.mYOffset != offset) { ...... changed = true; wallpaperWin.mYOffset = offset; } if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) { wallpaperWin.mWallpaperY = wpy; wallpaperWin.mWallpaperYStep = wpys; rawChanged = true; } if (rawChanged) { try { ...... if (sync) { mWaitingOnWallpaper = wallpaperWin; } wallpaperWin.mClient.dispatchWallpaperOffsets( wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY, wallpaperWin.mWallpaperXStep, wallpaperWin.mWallpaperYStep, sync); if (sync) { if (mWaitingOnWallpaper != null) { long start = SystemClock.uptimeMillis(); if ((mLastWallpaperTimeoutTime+WALLPAPER_TIMEOUT_RECOVERY) < start) { try { ...... mWindowMap.wait(WALLPAPER_TIMEOUT); } catch (InterruptedException e) { } ...... if ((start+WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) { ...... mLastWallpaperTimeoutTime = start; } } mWaitingOnWallpaper = null; } } } catch (RemoteException e) { } } return changed; } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService類的四個參數版本的成員函數updateWallpaperOffsetLocked首先計算參數wallpaper所描述的壁紙窗口在X軸和Y軸上的偏移位置,接着再向提供該壁紙窗口的服務發向一個壁紙窗口在X軸和Y軸上的偏移位置變化事件通知。
參數wallpaper所描述的壁紙窗口在X軸和Y軸上的偏移位置的計算過程是一樣的,這裏我們只要結合前面的圖3來分析壁紙窗口在X軸上的偏移位置的計算過程。
在圖3中,壁紙窗口的WallpaperX、WallpaperXStep、WallpaperWidth和DisplayWidth值分別等於這裏的mLastWallpaperX、mLastWallpaperXStep、wallpaperWin.mFrame.right - wallpaperWin.mFrame.left和dw。有了這些值之後,就可以計算得到參數wallpaper所描述的壁紙窗口在X軸上的偏移位置的絕對值XOffset了。
如果計算得到的XOffset、WallpaperX、WallpaperXStep的值與原來保存在參數wallpaper所指向的一個WindowState對象的成員變量mXOffset、mWallpaperX、mWallpaperXStep的值不相等,那麼就會將計算得到的XOffset、WallpaperX、WallpaperXStep的值分別保存在這個WindowState對象的成員變量mXOffset、mWallpaperX、mWallpaperXStep,並且相應地將變量changed和rawChanged的值設置爲true,表示參數wallpaper所描述的壁紙窗口在X軸上的偏移位置發生了變化。
有四個地方需要注意:
1. 當mLastWallpaperX的值小於0的時候,那麼就說明系統中的壁紙窗口還沒有被設置一個有效的X軸偏移位置,這時候計算壁紙窗口在X軸上的偏移位置所採用的WallpaperX值就會取爲0.5,即默認將壁紙窗口的中間區域指定爲需要顯示壁紙的窗口的背景。
2. 當mLastWallpaperXStep的值小於0的時候,那麼就說明需要顯示壁紙的窗口還沒有告訴WindowManagerService服務它有多少個虛擬屏幕,這時候就會將壁紙窗口的WallpaperXStep值設置爲-1.0,用來告訴提供壁紙窗口的服務,需要顯示壁紙的窗口沒有指定虛擬屏幕的個數。
3. 當壁紙窗口的寬度小於等於屏幕寬度的時候,即變量availw的值小於等於0的時候,那麼就說明不需要設置壁紙窗口在X軸上的偏移位置,也就是說,這時候壁紙窗口在X軸上的偏移位置始終保持爲0。
4. 當壁紙窗口的寬度大於屏幕寬度的時候,即變量availw的值大於0的時候,壁紙窗口在X軸上的偏移值等於availw * wps,加上0.5是爲了向上取整,向上取整後需要取反,因爲負數才能正確表達出壁紙窗口相對屏幕的偏移。
計算完成參數wallpaper所描述的壁紙窗口在X軸和Y軸上的偏移位置之後,如果變量rawChanged的值等於true,那麼就說明參數wallpaper所描述的壁紙窗口在X軸和Y軸上的偏移位置發生了變化,這時候就需要向提供該壁紙窗口的服務發送一個事件通知,這是通過調用參數wallpaperWin所指向的一個WindowState對象的成員變量mClient所描述的一個實現了IWindow接口的Binder代理對象的成員函數dispatchWallpaperOffsets來實現的,同時傳遞給壁紙窗口的服務的參數有壁紙窗口當前所使用的WallpaperX、WallpaperY、WallpaperXStep和WallpaperYStep值,以及另外一個同步參數sync。
當參數sync的值等於true的時候,就表示WindowManagerService服務需要等待提供壁紙窗口wallpaperWin的服務處理完成前面所發送的偏移位置變化事件通知,等待的最長時間爲WALLPAPER_TIMEOUT。如果提供壁紙窗口wallpaperWin的服務不能在WALLPAPER_TIMEOUT時間內向WindowManagerService服務發送一個事件處理完成通知,那麼WindowManagerService服務就會將這次事件通知發送時間start保存在WindowManagerService類的成員變量mLastWallpaperTimeoutTime中。
如果上一次發送的壁紙窗口偏移位置變化事件通知發生了超時,那麼在上次發送這個事件通知起的WALLPAPER_TIMEOUT_RECOVERY時間內,是不允許再次發送壁紙窗口偏移位置變化事件通知的。這是因爲在上一次事件通知超時的情況下,在短時間內再次發送相同的事件通知也是非常有可能是超時的,因此,就不允許短時間內重複發送相同的事件通知,避免出現雪崩現象。
關於互聯網的雪崩現象,可以舉一個常見的例子來說明。假設現在有一個Web頁面正在現場直播一項非常熱門的體育賽事,這時候就會有海量的用戶訪問這個頁面。一旦訪問量快要達到Web服務器的承受能力的時候,Web頁面的打開速度就會越來越慢。Web頁面打開速度變慢的時候,用戶就會下意識地不斷按F5刷新。越是不斷地按F5刷新,Web頁面的請求量就越大,而當請求量大於Web服務器的承受能力的時候,Web服務器就會宕機了,這個就是雪崩現象。爲了避免雪崩現象,就需要在請求量快要達到Web服務器的承受能力的時候,避免用戶發送更多的訪問請求,以使得Web服務器有喘息的機會。
廢話少說了,當WindowManagerService服務在等待壁紙窗口wallpaper所屬的服務處理它的偏移位置變化事件通知時,會將該壁紙窗口wallpaper保存在WindowManagerService類的成員變量mWaitingOnWallpaper中,用來表示WindowManagerService服務正在處於等待壁紙服務處理完成一個壁紙窗口偏移位置變化事件通知。一旦壁紙服務處理完成該事件通知,WindowManagerService類的成員變量mWaitingOnWallpaper的值就會被設置爲null。
壁紙服務處理壁紙窗口在X軸和Y軸上的偏移位置變化事件通知的過程就如圖4的Step 6至Step 8所示。
至此,我們就分析完成壁紙窗口在X軸和Y軸上的偏移位置的調整過程了,接下來我們就繼續分析壁紙窗口在窗口堆棧中的位置調整過程。
三. 調整壁紙窗口在窗口堆棧中的位置
調整壁紙窗口在窗口堆棧中的位置實際上就是將壁紙窗口放置在需要顯示壁紙的窗口的下面,這是是通過調用WindowManagerService類的成員函數adjustWallpaperWindowsLocked來實現的。
WindowManagerService類的成員函數adjustWallpaperWindowsLocked的實現框架如下所示:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... int adjustWallpaperWindowsLocked() { int changed = 0; final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. final ArrayList<WindowState> localmWindows = mWindows; int N = localmWindows.size(); WindowState w = null; WindowState foundW = null; int foundI = 0; WindowState topCurW = null; int topCurI = 0; int i = N; //Label #1: while (i > 0) { //從上到下遍歷窗口堆棧,查找需要顯示壁紙的窗口foundW,foundI爲窗口foundW在窗口堆棧中 //的位置如果沒有找到需要顯示壁紙的窗口,並且系統中存在壁紙窗口,那麼topCurW就指向Z軸 //位置最大的壁紙窗口,topCurI爲窗口topCurW在窗口堆棧中的位置,這時候foundW一定等於 //null。 ...... } //Label #2: if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { //如果系統當前正在窗口切換的過程中,並且系統當前存在一個需要顯示壁紙的Activity窗口, //那麼就認爲當前正在執行的窗口切換涉及到了這個需要顯示壁紙的Activity窗口, //因此,就暫時不要調整壁紙窗口的位置了,等到窗口切換過程完成了再說。 //系統當前存在一個需要顯示壁紙的Activity窗口,意味着mWallpaperTarget不等於null, //或者foundW不等於null。 ...... } if (mWallpaperTarget != foundW) { //上一次顯示壁紙的窗口和接下來要顯示壁紙的窗口發生了變化 mLowerWallpaperTarget = null; mUpperWallpaperTarget = null; WindowState oldW = mWallpaperTarget; mWallpaperTarget = foundW; // Now what is happening... if the current and new targets are // animating, then we are in our super special mode! if (foundW != null && oldW != null) { boolean oldAnim = oldW.mAnimation != null || (oldW.mAppToken != null && oldW.mAppToken.animation != null); boolean foundAnim = foundW.mAnimation != null || (foundW.mAppToken != null && foundW.mAppToken.animation != null); ...... //Label #3: if (foundAnim && oldAnim) { //上一次顯示壁紙的窗口oldW和接下來要顯示壁紙的窗口foundW正在顯示動畫的 //過程中,那麼就將Z軸位置較高的窗口保存在mUpperWallpaperTarget中,而將 //Z軸位置較低的窗口保存在mLowerWallpaperTarget中,並且將變量foundW指向 //Z軸位置較高的窗口,這樣就可以在這兩個窗口的動畫顯示過程中都能看到壁 //紙窗口. ...... } } }else if (mLowerWallpaperTarget != null) { //Label #4: //檢查mUpperWallpaperTarget和mLowerWallpaperTarget所指向的窗口的動畫顯示過程 //是否已經結束,如果已經結束,那麼就將mUpperWallpaperTarget和 //mLowerWallpaperTarget的值置null。 // Is it time to stop animating? ...... } boolean visible = foundW != null; //Label #5: if (visible) { //前面找到了一個需要顯示壁紙的窗口foundW,並且存在其它窗口與它關聯,這些關聯的 //窗口包括: //1. 在與該窗口所對應的窗口令牌的其它窗口 //2. 該窗口所設置的啓動窗口 //3. 附加在該窗口的其它窗口 //在上述這些關聯的窗口中,如果存在一些Z軸位置比窗口foundW小,那麼就將需要將壁紙 //窗口放在Z軸位置最小的那個窗口下面,即將變量foundW指向Z軸位置最小的那個窗口。 ...... } //讓變量foundW指向前面找到的需要顯示壁紙的窗口的下一個窗口, //這時候變量foundI記錄的仍是需要顯示壁紙的窗口在窗口堆棧中的位置, //接下來會根據這兩個變量來調整壁紙窗口在窗口堆棧中的位置 if (foundW == null && topCurW != null) { //前面提到,如果沒有找到需要顯示壁紙的窗口,並且系統中存在壁紙窗口,那麼foundW一 //定等於null,並且topCurW一定不等於null,這時候就不需要調整壁紙窗口在窗口堆棧中的 //位置。爲了與其它情況統一處理,這時候假設位於壁紙窗口上面的那個窗口就是需要顯示 //壁紙的窗口。因此,就會將foundI的值設置爲(topCurI+1),而將foundW的值設置爲 //topCurW。 // There is no wallpaper target, so it goes at the bottom. // We will assume it is the same place as last time, if known. foundW = topCurW; foundI = topCurI+1; } else { //前面找到了需要顯示壁紙的窗口,因此,就將它的下一個窗口保存在foundW中,變量foundI //的值不需要修改。 // Okay i is the position immediately above the wallpaper. Look at // what is below it for later. foundW = foundI > 0 ? localmWindows.get(foundI-1) : null; } //如果前面找到的需要顯示壁紙的窗口是可見的,並且當前正在顯示壁紙的窗口設置了壁紙窗口 //在X軸和Y軸上的偏移位置,那麼就將用來描述壁紙窗口在X軸和Y軸上的偏移位置的WallpaperX、 //WallpaperY、WallpaperXStep和WallpaperYStep值記錄在mLastWallpaperX、 //mLastWallpaperXStep、mLastWallpaperY和mLastWallpaperYStep中。 if (visible) { if (mWallpaperTarget.mWallpaperX >= 0) { mLastWallpaperX = mWallpaperTarget.mWallpaperX; mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; } if (mWallpaperTarget.mWallpaperY >= 0) { mLastWallpaperY = mWallpaperTarget.mWallpaperY; mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } } //Label #6: // Start stepping backwards from here, ensuring that our wallpaper windows // are correctly placed. int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { //一切準備就緒,開始調整系統中的壁紙窗口在窗口堆棧的位置,算法如下所示。 //對於從Z軸位置從高到低的每一個壁紙窗口wallpaper: //1. 如果它與變量foundW指向的不是同一個壁紙窗口,那麼就說明它在窗口堆棧中 //的位置不對,這時候就需要將它調整到窗口堆棧中的第foundI個位置上。 //2. 如果它與變量foundW指向的是同一個壁紙窗口,那麼就說明它在窗口堆棧中的 //位置是正確,這時候就不需要對它進行調整,不過要讓變量foundI的值減1,並且將 //在窗口堆棧第(foundI - 1)個位置的窗口記錄在變量foundW中。 //注意,變量foundW一開始就指向Z軸位置最高的壁紙窗口,而變量foundI記錄的是 //位於Z軸位置最高的壁紙窗口上面的那個窗口在窗口堆棧中的位置。 //上述算法實際上是用狀態機的方法將系統中的所有壁紙窗口(假設數量爲N)按照Z軸 //位置從高到底的順序放置在窗口堆棧中的第(foundI - 1)、(foundI - 2)、 //(foundI - 3)、......、(foundI - N)個位置上。 ...... } return changed; } ...... }
這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService類的成員函數adjustWallpaperWindowsLocked是按照以下流程來調整壁紙窗口在窗口堆棧中的位置的:
1. 通過一個while循環來從上到下地遍歷窗口堆棧,找到需要顯示壁紙的窗口foundW,其中,foundI爲窗口foundW在窗口堆棧中的位置。如果沒有找到需要顯示壁紙的窗口,並且系統中存在壁紙窗口,那麼topCurW就指向Z軸位置最大的壁紙窗口,其中,topCurI爲窗口topCurW在窗口堆棧中的位置。在這種情況下,變量foundW的值一定等於null的。
2. 如果WindowManagerService類的成員變量mNextAppTransition的值不等於WindowManagerPolicy.TRANSIT_UNSET,那麼就說明系統當前正在窗口切換的過程中。在這種情況下,如果系統當前存在一個需要顯示壁紙的Activity窗口,即WindowManagerService類的成員變量mWallpaperTarget的值不等於null,或者前面得到的變量foundW的值不等於null,那麼就認爲當前正在執行的窗口切換操作涉及到了這個需要顯示壁紙的Activity窗口。這時候就不需要調整壁紙窗口的位置,要等到窗口切換過程完成了之後再調整。
3. 如果WindowManagerService類的成員變量mWallpaperTarget和前面得到的變量foundW指向的不是同一個WindowState對象,那麼就說明上一次顯示壁紙的窗口和接下來要顯示壁紙的窗口發生了變化。在這種情況下,就會使用變量oldW來描述上一次顯示壁紙的窗口,而接下來要顯示壁紙的窗口通過WindowManagerService類的成員變量mWallpaperTarget以及變量foundW來描述。這時候如果檢查發現上一次顯示壁紙的窗口和接下來要顯示壁紙的窗口都處於顯示動畫的過程中,那麼就會將Z軸位置較高的窗口保存在WindowManagerService類的成員變量mUpperWallpaperTarget中,而將Z軸位置較低的窗口保存在WindowManagerService類的成員變量mLowerWallpaperTarget中,並且將變量foundW指向Z軸位置較高的窗口。這樣就能保證在這兩個窗口的動畫顯示過程中都能看到壁紙窗口,實際上就是保證在兩個窗口的切換過程中看到壁紙窗口。
4. 如果WindowManagerService類的成員變量mWallpaperTarget和前面得到的變量foundW指向的是同一個WindowState對象,並且WindowManagerService類的成員變量mLowerWallpaperTarget的值不等於null,那麼就說明需要檢查系統的窗口切換過程完成了沒有。如果已經完成,那麼就需要將WindowManagerService類的成員變量mUpperWallpaperTarget和mLowerWallpaperTarget的值設置爲null。由此可以,WindowManagerService類的成員變量mUpperWallpaperTarget和mLowerWallpaperTarget的作用就是用來記錄兩個處於切換狀態的需要顯示壁紙的窗口。
5. 如果變量foundW的值不等於null,那麼就說明前面找到了一個接下來要顯示壁紙的窗口。在這種情況下,需要做兩件事情。第一件事情是判斷接下來要顯示壁紙的窗口是否是可見的。如果是的話,那麼就會將變量visible的值設置爲true。第二件事情是在與接下來要顯示壁紙的窗口相關聯的窗口中,即與變量foundW所描述的窗口相關聯的窗口中,找到一個Z軸位置最小的窗口,因爲壁紙窗口最終是要放置在這個Z軸位置最小的窗口的下面,而不是最初找到的那個窗口的下面。與變量foundW所描述的窗口相關聯的窗口包括:A. 與變量foundW所描述的窗口具有相同窗口令牌的其它窗口;B. 與變量foundW所描述的窗口附加在同一個窗口的其它窗口;C. 爲變量foundW所描述的窗口所設置的啓動窗口;D. 附加在變量foundW所描述的窗口上的其它窗口。一旦找到這樣的一個窗口,那麼就會讓重新讓變量foundW指向它。
6. 再次重新調整變量foundW的值,讓它指向位於前面所找到的需要顯示壁紙的窗口的下面的一個窗口。注意,這個窗口有可能就是壁紙窗口。這時候變量foundI記錄的然是前面所找到的需要顯示壁紙的窗口在窗口堆棧中的位置。這樣做的目的是爲了接下來可以方便地調整壁紙窗口在窗口堆棧中的位置。但是如果變量foundW的值等於null,那麼就說明前面根本沒有找到需要顯示壁紙的窗口。在這種情況下,如果變量topCurW的值不等於null,那麼就說明系統中存在壁紙窗口。這種情況其實就不需要調整壁紙窗口在窗口堆棧中的位置了,但是爲了接下來的邏輯可以統一處理,就假定位於壁紙窗口上面的那個窗口是需要顯示壁紙的窗口。因此,就會將變量foundI的值設置爲(topCurI+1),而將變量foundW的值設置爲topCurW。
7. 如果前面所找到的需要顯示壁紙的窗口是可見的,即變量visible的值等於true,並且當前正在顯示壁紙的窗口設置了壁紙窗口在X軸和Y軸上的有效偏移位置,即WindowManagerService類的成員變量mWallpaperTarget所指向的一個WindowState對象的成員變量mWallpaperX和mWallpaperY的值大於等於0,那麼就將用來描述壁紙窗口在X軸和Y軸上的偏移位置的WallpaperX、WallpaperY、WallpaperXStep和WallpaperYStep值記錄在WindowManagerService類的成員變量mLastWallpaperX、mLastWallpaperY、mLastWallpaperXStep和mLastWallpaperYStep中。
8. 經過上面的一系列操作之後,現在一切準備就緒,因此就可以按照以下的算法來調整系統中的壁紙窗口在窗口堆棧的位置。對於從Z軸位置從高到低的每一個壁紙窗口wallpaper:(1). 如果它與變量foundW指向的不是同一個壁紙窗口,那麼就說明它在窗口堆棧中的位置不對,這時候就需要將它調整到窗口堆棧中的第foundI個位置上;(2). 如果它與變量foundW指向的是同一個壁紙窗口,那麼就說明它在窗口堆棧中的位置是正確,這時候就不需要對它進行調整,不過要讓變量foundI的值減1,並且將在窗口堆棧第(foundI - 1)個位置的窗口記錄在變量foundW中;(3). 重複執行第(1)和第(2)步的操作,直到系統所有的壁紙窗口都檢查完成爲止。注意,在上述算法中,變量foundW一開始就指向Z軸位置最高的壁紙窗口,而變量foundI記錄的是位於Z軸位置最高的壁紙窗口上面的那個窗口在窗口堆棧中的位置。每當Z軸位置最高的壁紙窗口在窗口堆棧中的位置調整完成之後,變量foundW就會指向Z軸位置次高的壁紙窗口,而變量foundI的值也會相應的地減少1。這個算法其實就是用狀態機的方法來將系統中的所有壁紙窗口(假設數量爲N)按照Z軸位置從高到底的順序放置在窗口堆棧中的第(foundI - 1)、(foundI - 2)、(foundI - 3)、......、(foundI - N)個位置上。
上述流程可能還是比較抽象,接下來我們就通過在標號爲Label #1、Label #2、Label #3、Label #4、Label #5和Label #6處所忽略的代碼來詳細分析壁紙窗口在窗口堆棧中的位置的調整過程。
標號爲Label #1的代碼如下所示:
while (i > 0) { i--; w = localmWindows.get(i); if ((w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER)) { if (topCurW == null) { topCurW = w; topCurI = i; } continue; } topCurW = null; if (w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. if (w.mAppToken.hidden && w.mAppToken.animation == null) { ...... topCurW = null; continue; } } ...... if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay() && (mWallpaperTarget == w || (!w.mDrawPending && !w.mCommitDrawPending))) { ...... foundW = w; foundI = i; if (w == mWallpaperTarget && ((w.mAppToken != null && w.mAppToken.animation != null) || w.mAnimation != null)) { // The current wallpaper target is animating, so we'll // look behind it for another possible target and figure // out what is going on below. ...... continue; } break; } }
這段代碼從上到下遍歷保存在窗口堆棧中的窗口,目的是要找到一個Z軸位置最大的並且需要顯示壁紙的窗口。一個窗口如果需要顯示壁紙,那麼用來描述它的一個WindowState對象w的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的值的FLAG_SHOW_WALLPAPER位就不等於0。
一個需要顯示壁紙的窗口只有準備就緒顯示並且UI也已經繪製完成之後,WindowManagerService服務纔會將壁紙窗口放置在它的下面。 一個需要顯示壁紙的窗口如果已經準備就緒顯示,那麼用來描述它的一個WindowState對象w的成員函數isReadyForDisplay的返回值等於true。另一方面,如果一個窗口的UI還沒有繪製,那麼用來描述它的一個WindowState對象w的成員變量mDrawPending的值就會等於true。一個窗口的UI雖然繪製好了,但是還沒有提交給SurfaceFlinger服務處理,即用來描述它的一個WindowState對象w的成員變量mCommitDrawPending的值等於true,那麼它的UI也是認爲還沒有繪製完成的。
在遍歷的過程中,如果發現一個窗口w剛好就是當前正在顯示壁紙的窗口mWallpaperTarget,那麼就會繼續檢查該窗口是否正處於顯示動畫的過程中。如果是的話,那麼就需要跳過該窗口,因爲我們的目標是要找到另外一個接下來要顯示壁紙的窗口。對於Activity窗口和非Activity窗口來說,判斷它們是否是正處於顯示動畫的過程中的方法是不一樣的。對於一個處於顯示動畫過程的Activity窗口來說,用來描述它的一個WindowState對象w的成員變量mAppToken的值不等於null,並且指向了一個AppWindowToken對象,並且這個AppWindowToken對象的成員變量animation的值不等於null。對於一個處於顯示動畫過程的非Activity窗口來說,用來描述它的一個WindowState對象w的成員變量mAnimation的值不等於null。這就是說,AppWindowToken類的成員變量animation和WindowState類的成員變量mAnimation都是用來描述一個動畫對象的。
在遍歷的過程中,有兩種類型的窗口是需要跳過的。第一種類型的窗口是壁紙窗口,即用來描述它的一個WindowState對象w的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量type的值等於WindowManager.LayoutParams.TYPE_WALLPAPER。第二種類型的窗口是Activity窗口,但是與之所對應的Activity組件處於不可見狀態,這意味着這種類型的窗口也是不可見的。前面提到,對於Activity窗口來說,用來描述它的一個WindowState對象w的成員變量mAppToken的值是不等於null的,並且指向了一個AppWindowToken對象。當這個AppWindowToken對象的成員變量hidden的值等於true的時候,就意味着對應的Activity組件是不可見的。有時候一個AppWindowToken對象的成員變量hidden的值雖然等於true,但是如果這個AppWindowToken對象的成員變量animation的值不等於null,那麼隱含着對應的Activity組件其實還是可見的,因爲它還處於顯示動畫的過程中。
遍歷完成之後,有可能找到了接下來要顯示壁紙的窗口,也有可能找不到接下來要顯示壁紙的窗口。
如果找到了接下來要顯示壁紙的窗口,那麼變量foundW的值就不等於null,並且指向了這個接下來要顯示壁紙的窗口,另外一個變量foundI記錄的是該窗口在窗口堆棧中位置。這時候變量topCurW的值一定等於null,但是變量topCurI的值卻不一定等於0,它有可能指向了Z軸位置最大的那個壁紙窗口。
假設foundW的值不等於null,並且變量topCurI的值等於0.,那麼窗口堆棧的狀態就如圖5所示:
圖5 foundw != null & topCurI == 0
假設foundW的值不等於null,並且變量topCurI的值大於0.,那麼窗口堆棧的狀態就如圖6所示:
圖6 foundW != null & topCurI != 0
如果沒有找到接下來要顯示壁紙的窗口,那麼變量foundW的值就等於null,並且另外一個變量foundI的值等於0。這時候變量topCurW的值始終等於null,而變量topCurI的值可能不等於0,取決於系統中是否存在壁紙窗口。
爲了方便描述,我們假設系統中是存在壁紙窗口,那麼這時候topCurI的值就不等於0,並且它記錄的是Z軸位置最大的那個壁紙窗口在窗口堆棧中的位置,如圖7所示:
圖7 foundW == null && topCurI != 0
標號爲Label #2的代碼如下所示:
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // If we are currently waiting for an app transition, and either // the current target or the next target are involved with it, // then hold off on doing anything with the wallpaper. // Note that we are checking here for just whether the target // is part of an app token... which is potentially overly aggressive // (the app token may not be involved in the transition), but good // enough (we'll just wait until whatever transition is pending // executes). if (mWallpaperTarget != null && mWallpaperTarget.mAppToken != null) { ...... return 0; } if (foundW != null && foundW.mAppToken != null) { ...... return 0; } }
WindowManagerService類的成員變量mNextAppTransition的值不等於WindowManagerPolicy.TRANSIT_UNSET意味着系統當前正在窗口切換的過程中。這裏說的窗口切換其實就是由Activity組件切換引起來的,即切換的是Activity窗口。如果正在切換的Activity窗口是是需要顯示壁紙的,那麼WindowManagerService類的成員函數adjustWallpaperWindowsLocked就要等到切換過程結束後,才能調整重新調整壁紙窗口在窗口堆棧中的位置。
這裏本來是要判斷正在發生切換的Activity窗口是否是當前壁紙窗口的目標窗口或者前面所找到的接下來要顯示壁紙的窗口的,但是卻沒有這樣做。這段代碼採取了一種比較激進的方法,即主要發現當前壁紙窗口的目標窗口是一個Activity窗口,或者前面所找到的接下來要顯示壁紙的窗口是一個Activity窗口,那麼就認爲當前正在執行的窗口切換過程涉及到了壁紙窗口,因此,就要等到切換過程結束後,再來重新調整壁紙窗口在窗口堆棧中的位置。
WindowManagerService類的成員變量mWallpaperTarget描述的就是當前壁紙窗口的目標窗口,當它的值不等於null時,並且它所指向的一個WindowState對象的成員變量mAppToken的值不等於null,那麼就說明當前壁紙窗口的目標窗口是一個Activity窗口。同樣,如果前面得到的變量foundW的值不等於null,並且它所指向的一個WindowState對象的成員變量mAppToken的值不等於null,那麼就說明前面所找到的接下來要顯示壁紙的窗口是一個Activity窗口。
標號爲Label #3的代碼如下所示:
if (foundAnim && oldAnim) { int oldI = localmWindows.indexOf(oldW); ...... if (oldI >= 0) { ...... // Set the new target correctly. if (foundW.mAppToken != null && foundW.mAppToken.hiddenRequested) { ...... mWallpaperTarget = oldW; } // Now set the upper and lower wallpaper targets // correctly, and make sure that we are positioning // the wallpaper below the lower. if (foundI > oldI) { // The new target is on top of the old one. ...... mUpperWallpaperTarget = foundW; mLowerWallpaperTarget = oldW; foundW = oldW; foundI = oldI; } else { // The new target is below the old one. ...... mUpperWallpaperTarget = oldW; mLowerWallpaperTarget = foundW; } } }
當變量foundAnim和oldAnim的值均等於true的時候,就說明當前正在顯示壁紙的窗口oldW和接下來要顯示壁紙的窗口foundW均處於顯示動畫的過程中,那麼就分別將它們記錄在WindowManagerService類的成員變量mLowerWallpaperTarget和mUpperWallpaperTarget中,其中,前者用來描述Z軸位置較低的窗口,而後者用來描述Z軸位置較高的的窗口。
變量foundI和oldI記錄的分別是窗口foundW和oldW在窗口堆棧中的位置。因此,當變量foundI的值大於變量oldI的值的時候,窗口foundW就是Z軸位置較高的的窗口,而窗口oldW就是Z軸位置較低的的窗口。相反,當變量foundI的值小於等於變量oldI的值的時候,窗口oldW就是Z軸位置較高的的窗口,而窗口foundW就是Z軸位置較低的的窗口。
這裏有三個地方是需要注意的:
1. 當前正在顯示壁紙的窗口oldW其實就是WindowManagerService類的成員變量mWallpaperTarget所描述的那個窗口。
2. 變量foundW和foundI記錄的始終都是Z軸位置較低的那個窗口及其在窗口堆棧的位置,因此,當變量foundI的值大於變量oldI的值的時候,要將變量foundW和foundI的值分別設置爲oldW和oldI,這樣做的目的是爲了接下來可以將壁紙窗口放置在Z軸位置較低的窗口的下面,以便可以在兩個窗口的動畫顯示過程中看到壁紙。
3. 如果前面找到的接下來要顯示壁紙的窗口是一個Activity窗口,即變量foundW所描述的一個WindowState對象的成員變量mAppToken的值不等於null,並且它所指向的一個AppWindowToken對象的成員變量hiddenRequested的值等於true,那麼就說明與窗口foundW所對應的一個Activity組件已經被請求隱藏起來了。在這種情況下,當前正在顯示壁紙的窗口就會仍然被當作是接下來壁紙窗口的目標窗口。由於此前我們已經將WindowManagerService類的成員變量mWallpaperTarget的值設置了爲foundW,因此,這時候就需要將它的值修改爲oldW。
這段代碼執行完成之後,窗口堆棧的狀態就如圖8所示:
圖8 mUpperWallpaperTarget、mLowerWallpaperTarget、foundW和foundI的關係
標號爲Label #4的代碼如下所示:
// Is it time to stop animating? boolean lowerAnimating = mLowerWallpaperTarget.mAnimation != null || (mLowerWallpaperTarget.mAppToken != null && mLowerWallpaperTarget.mAppToken.animation != null); boolean upperAnimating = mUpperWallpaperTarget.mAnimation != null || (mUpperWallpaperTarget.mAppToken != null && mUpperWallpaperTarget.mAppToken.animation != null); if (!lowerAnimating || !upperAnimating) { ...... mLowerWallpaperTarget = null; mUpperWallpaperTarget = null; }
這段代碼檢查WindowManagerService類的成員變量mLowerWallpaperTarget和mUpperWallpaperTarget所描述的兩個窗口的動畫是否已經顯示結束。如果已經顯示結束,那麼就會將這兩個成員變量的值設置爲null。
注意,如果一個窗口的動畫已經顯示結束,那麼用來描述它的一個WindowState對象的成員變量mAnimation的值就會等於null。另外,如果一個Activity窗口的動畫已經顯示結束,那麼用來描述它的WindowState對象的成員變量mAppWindowToken所指向的一個AppWindowToken對象的成員變量animation的值也會等於null。
標號爲Label #5的代碼如下所示:
boolean visible = foundW != null; if (visible) { // The window is visible to the compositor... but is it visible // to the user? That is what the wallpaper cares about. visible = isWallpaperVisible(foundW); ...... // If the wallpaper target is animating, we may need to copy // its layer adjustment. Only do this if we are not transfering // between two wallpaper targets. mWallpaperAnimLayerAdjustment = (mLowerWallpaperTarget == null && foundW.mAppToken != null) ? foundW.mAppToken.animLayerAdjustment : 0; final int maxLayer = mPolicy.getMaxWallpaperLayer() * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; // Now w is the window we are supposed to be behind... but we // need to be sure to also be behind any of its attached windows, // AND any starting window associated with it, AND below the // maximum layer the policy allows for wallpapers. while (foundI > 0) { WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && wb.mAttachedWindow != foundW.mAttachedWindow && (wb.mAttrs.type != TYPE_APPLICATION_STARTING || wb.mToken != foundW.mToken)) { // This window is not related to the previous one in any // interesting way, so stop here. break; } foundW = wb; foundI--; } }
當變量foundW的值不等於null時,就說明前面找到了一個接下來要顯示壁紙的窗口。在這種情況下,需要做三件事件:
1. 判斷窗口foundW是否是可見的,這是通過調用WindowManagerService類的成員函數isWallpaperVisible來實現的。如果可見,那麼變量visible的值就會等於true,否則就會等於false。後面在調整壁紙窗口在窗口堆棧中的位置時,會根據變量visible的值來決定要顯示壁紙窗口還是隱藏壁紙窗口。
2. 檢查窗口foundW是否是一個Activity窗口。如果是的話,那麼就會將用來描述它的一個WindowState對象的成員變量mAppToken所指向的一個AppWindowToken對象的成員變量animLayerAdjustment的值保存在WindowManagerService類的成員變量mWallpaperAnimLayerAdjustment中。在計算壁紙窗品的Z軸位置的時候,需要使用到WindowManagerService類的成員變量mWallpaperAnimLayerAdjustment,用來調整壁紙窗品的Z軸位置。在後面一篇文章分析窗口的Z軸位置的計算方法時,我們再詳細分析壁紙窗口的Z軸位置是如何計算的。注意,如果這時候系統的壁紙窗口有兩個目標窗口,即WindowManagerService類的成員變量mLowerWallpaperTarget的值不等於null,那麼就說明壁紙窗口的目標窗口正在顯示動畫的過程中。在這種情況下,就不需要調整壁紙窗品的Z軸位置,即會將WindowManagerService類的成員變量mLowerWallpaperTarget的值設置爲0。等到壁紙窗口的目標窗口結束動畫顯示過程之後,再來調整它的Z軸位置。
3. 檢查窗口foundW的下面是否存在一些關聯的窗口。如果存在的話,就需要將壁紙窗口放置在這些關聯的窗口中Z軸位置最低的窗口的下面。這段代碼通過一個while循環從窗口foundW的下面一個窗口開始往下檢查,直到找到一個沒有關聯的窗口爲止。在檢查的過程中,每碰到一個關聯的窗口,那麼就讓變量foundW指向它,並且將變量foundI的值減少1。這樣最終得到的變量foundW和foundI就是用來描述與窗口foundW有聯的、Z軸位置最低的窗口及其在窗口堆棧中的位置。
前面提到,窗口foundW所關聯的窗口四種,即對於一個窗口wb來,如果它滿足以下四個條件,那麼它就與窗口foundW有關聯:
A. 窗口wb與窗口foundW對應的是同一個窗品令牌,即分別用來描述窗口wb和窗口foundW的兩個WindowState對象的成員變量mToken指向的是同一個WindowToken對象。
B. 窗口wb附加在窗口foundW上,即用來描述窗口wb的一個WindowState對象的成員變量mAttachedWindow與變量foundW指向的是同一個WindowState對象。
C. 窗口wb與窗口foundW附加在同一個窗口上,即分別用來描述窗口wb和窗口foundW的兩個WindowState對象的成員變量mAttachedWindow指向的是同一個WindowState對象。
D. 窗口wb是窗口foundW的啓動窗口,即用來描述窗口wb的一個WindowState對象的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量type的值等於TYPE_APPLICATION_STARTING。
此外,WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象會規定系統中的壁紙窗口的Z軸位置不能大於某一個值,也就是說,壁紙窗口的Z軸位置有一個最大值限制。這個限制值可以通過調用WindowManagerService類的成員變量mPolicy所指向的一個PhoneWindowManager對象的成員函數getMaxWallpaperLayer來獲得。獲得了這個限制值之後,還需要乘以一個窗口類型因子TYPE_LAYER_MULTIPLIER,最後再加一個窗口類型偏移值TYPE_LAYER_OFFSET,就可以得到壁紙窗口的最大Z軸位置限制值maxLayer。這時候如果在窗口foundW的下面找到一個窗口wb,它的Z軸位置大於等於maxLayer,即用來描述它的一個WindowState對象的成員變量mBaseLayer的值大於maxLayer,那麼也會認爲窗口wb是與窗口foundW有關聯的。
我們通過圖9和圖10來說明查找與窗口foundW關聯的、Z軸位置最小的窗口的過程:
圖9 查找與窗口foundW關聯的窗口之前
圖10 查找與窗口foundW關聯的窗口之後
標號爲Label #6的代碼如下所示:
// Start stepping backwards from here, ensuring that our wallpaper windows // are correctly placed. int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { curTokenIndex--; WindowToken token = mWallpaperTokens.get(curTokenIndex); if (token.hidden == visible) { changed |= ADJUST_WALLPAPER_VISIBILITY_CHANGED; token.hidden = !visible; // Need to do a layout to ensure the wallpaper now has the // correct size. mLayoutNeeded = true; } int curWallpaperIndex = token.windows.size(); while (curWallpaperIndex > 0) { curWallpaperIndex--; WindowState wallpaper = token.windows.get(curWallpaperIndex); if (visible) { updateWallpaperOffsetLocked(wallpaper, dw, dh, false); } // First, make sure the client has the current visibility // state. if (wallpaper.mWallpaperVisible != visible) { wallpaper.mWallpaperVisible = visible; try { ...... wallpaper.mClient.dispatchAppVisibility(visible); } catch (RemoteException e) { } } wallpaper.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; ...... // First, if this window is at the current index, then all // is well. if (wallpaper == foundW) { foundI--; foundW = foundI > 0 ? localmWindows.get(foundI-1) : null; continue; } // The window didn't match... the current wallpaper window, // wherever it is, is in the wrong place, so make sure it is // not in the list. int oldIndex = localmWindows.indexOf(wallpaper); if (oldIndex >= 0) { i...... localmWindows.remove(oldIndex); mWindowsChanged = true; if (oldIndex < foundI) { foundI--; } } // Now stick it in. ...... localmWindows.add(foundI, wallpaper); mWindowsChanged = true; changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; } }
這段代碼就是用來調整系統中的壁紙窗口在窗口堆棧中的位置的,目標就是要將它們放置在前面所找到的接下來要顯示壁紙的窗口的下面。
WindowManagerService類的成員變量mWallpaperTokens保存的是一系列WindowToken對象,它們描述的是系統中的壁紙窗口令牌。這些WindowToken對象都有一個成員變量windows,裏面保存的是一系列WindowState對象,它們描述的是系統中的壁紙窗口。這段代碼就目標就要通過兩個嵌套的while循環來將這些WindowState對象調整到前面所找到的接下來要顯示壁紙的窗口的下面去。
在調整壁紙窗口在窗口堆棧中的位置的過程中,還會做以下四件事情:
1. 設置壁紙窗口令牌的可見性。也就是說,如果一個用來描述壁紙窗口令牌的WindowToken對象token的成員變量hidden的值不等於前面得到的變量visible的值,那麼就說明該壁紙窗口令牌的可見性發生了變化。由於WindowToken類的成員變量hidden是用來表示壁紙窗口令牌的不可見狀態的,而變量visible是用來表示接下來要顯示壁紙的窗口是可見的,因此,當一個壁紙窗口令牌的可見性發生變化時,就要將用來描述它的WindowToken對象token的成員變量hidden的值設置爲!visbile。壁紙窗口令牌的可見性發生了變化之後,需要重新刷新系統的UI,因此,就需要將WindowManagerService類的成員變量mLayoutNeeded 的值設置爲true,並且將函數返回值changed的ADJUST_WALLPAPER_VISIBILITY_CHANGED位設置爲1。
2. 在前面所找到的接下來要顯示壁紙的窗口是可見的情況下,即在變量visible的值等於true的情況下,重新計算每一個壁紙窗口wallpaper在X軸和Y軸上的偏移位置,這是通過調用WindowManagerService類的成員函數updateWallpaperOffsetLocked來實現的。
3. 如果一個壁紙窗口之前是不可見的,現在變得可見了,或者之前是可見的,現在變得不可見了,具體就表現在用來描述該壁紙窗口的一個WindowState對象的成員變量mWallpaperVisible的值不等於變量visible的值,那麼就需要該WindowState對象的成員變量mWallpaperVisible的值設置爲visible,並且向提供該壁紙窗口的服務發送一個可見性變化事件通知。
4. 調整每一個壁紙窗口的Z軸位置。一個壁紙窗口的Z軸位置保存在用來描述它的一個WindowState對象的成員變量mLayer中,用這個成員變量的值加上前面已經計算好的壁紙窗口的Z軸位置調整值,即保存在WindowManagerService類的成員變量mWallpaperAnimLayerAdjustment中的值,就可以得到一個壁紙窗口的最終Z軸位置值,並且保存WindowState對象的成員變量mAnimLayer中。
前面在分析WindowManagerService類的成員函數adjustWallpaperWindowsLocked的實現框架時提到,在調整系統中的壁紙窗口在窗口堆棧中的位置之前,變量foundW描述的應該是Z軸位置最大的壁紙窗口,而變量foundI記錄的是需要顯示壁紙的窗口在窗口堆棧中的位置,如圖11所示:
圖11 調整壁紙窗口前的窗口堆棧狀態
在圖11中,接下來需要顯示壁紙的是窗口A,在它下面依次是窗口B、C和D,並且系統中存在着三個壁紙窗口,它們的編號分別爲1、2和3。假設窗口B和編號爲3的壁紙窗口是同一個窗口,那麼就說明編號爲3的壁紙窗口已經在窗口堆棧中的正確位置了,因此,就不需要調整它在窗口堆棧中的位置了。這時候窗口堆棧中的狀態如圖12所示:
圖12 處理完成編號爲3的壁紙窗口後的窗口堆棧狀態
在圖12中,假設窗口C和編號爲2的壁紙窗口不是同一個窗口,那麼就需要將編號爲2的壁紙窗口放置在窗口C的位置上,如圖13所示:
圖13 處理完成編號爲2的壁紙窗口後的窗口堆棧狀態
在圖13中,假設窗口C和編號爲1的壁紙窗口也不是同一個窗口,那麼就需要將編號爲1的壁紙窗口放置在窗口C的位置上,如圖14所示:
圖14 處理完成編號爲1的壁紙窗口的窗口堆棧狀態
處理完成編號爲1的壁紙窗口之後,系統中所有的壁紙窗口都調整到窗口A的下面去了,這樣在下一次在刷新系統UI時,就可以將系統中的壁紙窗口作爲窗口A的背景了。
至此,我們就分析完成壁紙窗口在窗口堆棧中的位置調整過程了,WindowManagerService服務對壁紙窗口的管理也分析完成了。結合前面Android窗口管理服務WindowManagerService對窗口的組織方式分析和Android窗口管理服務WindowManagerService對輸入法窗口(Input Method Window)的管理分析這兩篇文章,我們就可以對WindowManagerService服務在內部所維護的窗口堆棧有一個清晰的認識了。
當系統中的所有窗口都在窗口堆棧排列好之後,WindowManagerService服務就可以計算每一個窗口的Z軸座標了,以便可以傳遞給SurfaceFlinger服務做可見性計算,從而正確地將系統的UI渲染出來。在接下來的一篇文章中,我們就將繼續分析WindowManagerService服務計算窗口的Z軸座標的過程,敬請關注!
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!