原文出處——>[Android窗口管理服務WindowManagerService對壁紙窗口(Wallpaper Window)的管理分析](http://blog.csdn.net/luoshengyang/article/details/8550820)
在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:
~~~
public class WallpaperActivity extends Activity {
......
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
}
......
}
~~~
滿足了以上兩個條件之后,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來實現的。接下來我們就主要分析這個函數中與壁紙窗口調整相關的邏輯,如下所示:
[java] view plain copy
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來實現的。接下來我們就主要分析這個函數中與壁紙窗口調整相關的邏輯,如下所示:
[java] view plain copy
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,如下所示:
[java] view plain copy
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的實現如下所示:
[java] view plain copy
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的實現如下所示:
[java] view plain copy
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的實現,如下所示:
[java] view plain copy
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的實現如下所示:
[java] view plain copy
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的實現框架如下所示:
[java] view plain copy
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的代碼如下所示:
[java] view plain copy
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的代碼如下所示:
[java] view plain copy
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的代碼如下所示:
[java] view plain copy
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的代碼如下所示:
[java] view plain copy
// 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的代碼如下所示:
[java] view plain copy
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的代碼如下所示:
[java] view plain copy
// 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軸坐標的過程,敬請關注!
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析