在Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起。大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在下面,Activity窗口夹在它们的中间。

一个Activity窗口如果需要显示壁纸,那么它必须满足以下两个条件:

  1. 背景是半透明的,例如,它在AndroidManifest.xml文件中的android:theme属性设置为Theme.Translucent:
<activity android:name=".WallpaperActivity"  android:theme="@android:style/Theme.Translucent">  ......
</activity>
  1. 窗口属性中的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窗口和壁纸窗口,它们的位置关系就如图所示:


对象的关系如下所示:

  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对象。

总的来说,就是描述了系统当前激活的Activity窗口需要显示壁纸的情景。WindowManagerService服务的职能之一就是要时刻关注系统中是否有窗口需要显示壁纸。WindowManagerService服务一旦发现有窗口需要显示壁纸,那么就会调整壁纸窗口在窗口堆栈中的位置,使得它放置在需要显示壁纸的窗口的下面。此外,需要显示壁纸的窗口还可以设置壁纸窗口在X轴和Y轴上的偏移位置,以便可以将壁纸窗口的某一部分指定为它的背景。

接下来,我们就首先分析两个需要调整壁纸窗口在窗口堆栈中的位置的情景,然后再分析壁纸窗口在X轴和Y轴上的偏移位置的调整过程,最后分析壁纸窗口在窗口堆栈中的位置调整过程。

一、调整壁纸窗口在窗口堆栈中的位置的情景

第一个需要调整壁纸窗口在窗口堆栈中的位置的情景是增加一个窗口到WindowManagerService服务去的时候。增加一个窗口到WindowManagerService服务最终是通过调用WindowManagerService类的成员函数addWindow来实现的。

public class WindowManagerService extends IWindowManager.Stubimplements 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.Stubimplements 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);    }    ......
}

应用程序进程在请求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,本文主要是关注成员函数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,如图所示:


这时候壁纸窗口在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.Stubimplements 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;  ......  }  ......
}

此外,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轴上的偏移位置,这个过程如图所示:


这个过程大概如下所示:
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.Stubimplements 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);  }  }  }  ......  }  ......
}

Session类的成员函数setWallpaperPosition首先调用WindowManagerService类的成员函数windowForClientLocked来找到与参数window所对应的一个WindowState对象,这个WindowState对象描述的是要改变壁纸窗口位置的窗口,接着再调用WindowManagerService类的另外一个成员函数setWindowWallpaperPositionLocked来执行设置壁纸窗口在X轴和Y轴的偏移位置的操作。

WindowManagerService类的成员函数setWindowWallpaperPositionLocked的实现如下所示:

public class WindowManagerService extends IWindowManager.Stubimplements 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();  }  }  }  ......
}

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.Stubimplements 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;  }  ......
}

当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.Stubimplements 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;  }  ......
}

WindowManagerService类的四个参数版本的成员函数updateWallpaperOffsetLocked首先计算参数wallpaper所描述的壁纸窗口在X轴和Y轴上的偏移位置,接着再向提供该壁纸窗口的服务发向一个壁纸窗口在X轴和Y轴上的偏移位置变化事件通知。

参数wallpaper所描述的壁纸窗口在X轴和Y轴上的偏移位置的计算过程是一样的,壁纸窗口的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轴上的偏移位置变化事件通知的过程就如上图的Step 6至Step 8所示。

至此,我们就分析完成壁纸窗口在X轴和Y轴上的偏移位置的调整过程了,接下来我们就继续分析壁纸窗口在窗口堆栈中的位置调整过程。

三、调整壁纸窗口在窗口堆栈中的位置

调整壁纸窗口在窗口堆栈中的位置实际上就是将壁纸窗口放置在需要显示壁纸的窗口的下面,这是是通过调用WindowManagerService类的成员函数adjustWallpaperWindowsLocked来实现的。

public class WindowManagerService extends IWindowManager.Stubimplements 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;  }  ......
}

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.,那么窗口堆栈的状态就如图所示:


假设foundW的值不等于null,并且变量topCurI的值大于0.,那么窗口堆栈的状态就如图所示:


如果没有找到接下来要显示壁纸的窗口,那么变量foundW的值就等于null,并且另外一个变量foundI的值等于0。这时候变量topCurW的值始终等于null,而变量topCurI的值可能不等于0,取决于系统中是否存在壁纸窗口。

为了方便描述,我们假设系统中是存在壁纸窗口,那么这时候topCurI的值就不等于0,并且它记录的是Z轴位置最大的那个壁纸窗口在窗口堆栈中的位置,如图所示:


标号为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。

这段代码执行完成之后,窗口堆栈的状态就如图所示:


标号为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有关联的。

查找与窗口foundW关联的窗口之前:


查找与窗口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中。

在调整系统中的壁纸窗口在窗口堆栈中的位置之前,变量foundW描述的应该是Z轴位置最大的壁纸窗口,而变量foundI记录的是需要显示壁纸的窗口在窗口堆栈中的位置,如图所示:


接下来需要显示壁纸的是窗口A,在它下面依次是窗口B、C和D,并且系统中存在着三个壁纸窗口,它们的编号分别为1、2和3。假设窗口B和编号为3的壁纸窗口是同一个窗口,那么就说明编号为3的壁纸窗口已经在窗口堆栈中的正确位置了,因此,就不需要调整它在窗口堆栈中的位置了。这时候窗口堆栈中的状态如图所示:


假设窗口C和编号为2的壁纸窗口不是同一个窗口,那么就需要将编号为2的壁纸窗口放置在窗口C的位置上,如图所示:


假设窗口C和编号为1的壁纸窗口也不是同一个窗口,那么就需要将编号为1的壁纸窗口放置在窗口C的位置上,如图所示:


处理完成编号为1的壁纸窗口之后,系统中所有的壁纸窗口都调整到窗口A的下面去了,这样在下一次在刷新系统UI时,就可以将系统中的壁纸窗口作为窗口A的背景了。

至此,我们就分析完成壁纸窗口在窗口堆栈中的位置调整过程了,WindowManagerService服务对壁纸窗口的管理也分析完成了。

当系统中的所有窗口都在窗口堆栈排列好之后,WindowManagerService服务就可以计算每一个窗口的Z轴坐标了,以便可以传递给SurfaceFlinger服务做可见性计算,从而正确地将系统的UI渲染出来。

对壁纸窗口(Wallpaper Window)的管理分析相关推荐

  1. Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析

    在Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起.大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在 ...

  2. Mac壁纸精灵Wallpaper Wizard如何保存我喜欢的图片?

    Wallpaper Wizard是Mac 平台最强大的壁纸管理软件.安装Wallpaper Wizard桌面软件后,你立刻就拥有超过10万张的超高质量壁纸. Wallpaper Wizard2有没有办 ...

  3. flink笔记8(接笔记7——窗口(Window),迟到数据的处理)

    flink 3. 窗口(Window) (1)窗口的概念 (2)窗口的分类 (3)窗口 API 概览 (4)窗口分配器(Window Assigners) (5)窗口函数(Window Functio ...

  4. springboot大学生时间管理分析系统毕业设计源码130930

    摘  要 时间是一种无形资源,但可以对其进行有效的使用与管理.时间管理倾向是个体在运用时间方式上所表现出来的心理和行为特征,具有多维度.多层次的心理结构,由时间价值感.时间监控观和时间效能感构成.时间 ...

  5. (附源码)ssm大学生时间管理分析系统设计与实现 毕业设计130930

    摘 要 时间是一种无形资源,但可以对其进行有效的使用与管理.时间管理倾向是个体在运用时间方式上所表现出来的心理和行为特征,具有多维度.多层次的心理结构,由时间价值感.时间监控观和时间效能感构成.时间是 ...

  6. 传感与RFID在血液管理分析中运用

    传感与RFID在血液管理分析中运用 1 引 言 传感技术与RFID的融合运用还刚刚起步,中国作为世界的制造业大国与消费大国之一,应牢牢抓住这一机遇,自主探索,推动本土RFID产业的发展,提升社会信息化 ...

  7. emqttd 0.9.0版本的会话(Session)、消息队列(MQueue)、飞行窗口(Inflight Window)设计

    emqttd 0.9.0版本重新设计了MQTT连接会话管理 每个MQTT客户端连接,不管是否持久的(Persistent),都启动一个连接会话进程. 该会话进程管理: 客户端的全部订阅(Subscri ...

  8. ssm大学生时间管理分析系统设计与实现 毕业设计-附源码130930

    摘 要 时间是一种无形资源,但可以对其进行有效的使用与管理.时间管理倾向是个体在运用时间方式上所表现出来的心理和行为特征,具有多维度.多层次的心理结构,由时间价值感.时间监控观和时间效能感构成.时间是 ...

  9. Tomcat7.0源码分析——Session管理分析(下)

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/beliefer/article/details/52451061 前言 在<Tomcat7.0 ...

最新文章

  1. Unity自动保存场景脚本
  2. dns服务器ip地址 常用列表
  3. 2019牛客暑期多校训练营(第五场)- generator 1
  4. 安装计算机教室的请示,教室使用申请书4篇
  5. winform窗体控件多,加载慢、卡顿的解决办法
  6. 车牌识别数据集_行人再识别数据集
  7. ADSL 定时断线重连bat文件
  8. 一位算法工程师从30+场秋招面试中总结出的超强面经——目标检测篇(含答案)...
  9. Selenium RC for Java 环境配置
  10. Unity 防止数组索引越界的几种方法
  11. 移植STM32F103VE程序到STM32F207VE系列应用注意事项
  12. jq-ui-multiselect插件的使用
  13. 100个最励志最科学的无敌成功法则
  14. 30本引进翻译版日本计算机技术书籍推荐
  15. 关于电子科技大学寝室电费缴费情况和满意度的调研
  16. 接口地址没错但是报404
  17. 输入法(IME)实现原理
  18. 新版Qq为什么不受欢迎?
  19. xhci主机规范初探(1) --架构预览
  20. ar9331修改flash大小和df、cat /proc/mtd的区别

热门文章

  1. dvbbs sql版
  2. EasyDSS高性能RTMP/HLS(m3u8)/HTTP-FLV/RTSP流媒体服务器EasyDSS出现加密机授权异常问题如何解决?
  3. 小白学习Java第四十天
  4. Android图库/相册,禁止扫描指定的图片目录
  5. 万年历首页效果android,简约好用的日历APP
  6. 计算机系统中的存储系统
  7. 无线电中继台天线的安装及注意事项
  8. 方舟 服务器端命令 修改pvp,方舟生存进化服务器管理命令是什么_方舟生存进化服务器管理命令大全-66街机网...
  9. Pytorch加载txt格式的数据集文件(以PTB数据集为例)
  10. Pytorch训练PTB数据集时速度慢的问题