在Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起。大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在下面,Activity窗口夹在它们的中间。
一个Activity窗口如果需要显示壁纸,那么它必须满足以下两个条件:
- 背景是半透明的,例如,它在AndroidManifest.xml文件中的android:theme属性设置为Theme.Translucent:
<activity android:name=".WallpaperActivity"
android:theme="@android:style/Theme.Translucent">
......
</activity>
- 窗口属性中的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窗口和壁纸窗口的位置关系就如图所示:
WindowManagerService服务是使用堆栈来组织系统中的窗口的,因此,如果我们在窗口堆栈中观察Activity窗口和壁纸窗口,它们的位置关系就如图所示:
对象的关系如下所示:
- 在ActivityManagerService服务内部的Activity组件堆栈顶端的ActivityRecord对象N描述的是系统当前激活的Activity组件。
- ActivityRecord对象N在WindowManagerService服务内部的窗口令牌列表顶端对应有一个AppWindowToken对象N。
- AppWindowToken对象N在WindowManagerService服务内部的窗口堆栈中对应有一个WindowState对象N,用来描述系统当前激活的Activity组件窗口。
- WindowState对象N下面有一个WindowState对象WP,用来描述系统中的壁纸窗口。
- 系统中的壁纸窗口在WindowManagerService服务内部中对应的窗口令牌是由WindowToken对象WP来描述的。
- WindowToken对象WP在WallpaperManagerService服务中对应有一个Binder对象。
总的来说,就是描述了系统当前激活的Activity窗口需要显示壁纸的情景。WindowManagerService服务的职能之一就是要时刻关注系统中是否有窗口需要显示壁纸。WindowManagerService服务一旦发现有窗口需要显示壁纸,那么就会调整壁纸窗口在窗口堆栈中的位置,使得它放置在需要显示壁纸的窗口的下面。此外,需要显示壁纸的窗口还可以设置壁纸窗口在X轴和Y轴上的偏移位置,以便可以将壁纸窗口的某一部分指定为它的背景。
接下来,我们就首先分析两个需要调整壁纸窗口在窗口堆栈中的位置的情景,然后再分析壁纸窗口在X轴和Y轴上的偏移位置的调整过程,最后分析壁纸窗口在窗口堆栈中的位置调整过程。
一、调整壁纸窗口在窗口堆栈中的位置的情景
第一个需要调整壁纸窗口在窗口堆栈中的位置的情景是增加一个窗口到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();
......
}
......
}
......
}
如果当前增加到WindowManagerService服务来的是一个壁纸窗口,即参数attrs所描述的一个WindowManager.LayoutParams对象的成员变量type的值等于TYPE_WALLPAPER,那么就要求与该壁纸窗口所对应的类型为WindowToken的窗口令牌已经存在,否则的话,WindowManagerService类的成员函数addWindow就会直接返回一个错误码WindowManagerImpl.ADD_BAD_APP_TOKEN给调用者。这个类型为WindowToken的窗口令牌是WallpaperManagerService服务请求WindowManagerService服务创建的,即调用WindowManagerService类的成员函数addWindowToken来创建的。
如果当前增加到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,本文主要是关注成员函数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