计算窗口Z轴位置的过程分析
最后更新于:2022-04-02 05:03:30
原文出处——>[Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析](http://blog.csdn.net/luoshengyang/article/details/8570428)
通过前面几篇文章的学习,我们知道了在Android系统中,无论是普通的Activity窗口,还是特殊的输入法窗口和壁纸窗口,它们都是被WindowManagerService服务组织在一个窗口堆栈中的,其中,Z轴位置较大的窗口排列在Z轴位置较小的窗口的上面。有了这个窗口堆栈之后,WindowManagerService服务就可以按照一定的规则计算每一个窗口的Z轴位置了,本文就详细分析这个计算过程。
基于窗口堆栈来计算窗口的Z轴位置是比较有意思的。按照一般的理解,应该是先计算好窗口的Z轴位置,然后再按照Z轴位置的大小来将各个窗口排列在堆栈中。但是,事实上,窗口是按照其它规则排列在堆栈中。这些规则与窗口的类型、创建顺序和运行状态等有关。例如,状态栏窗口总是位于堆栈的顶端,输入法窗口总是位于需要输入法的窗口的上面,而壁纸窗口总是位于需要显示壁纸的窗口的下面。又如,当一个Activity组件从后台激活到前台时,与它所对应的窗口就会被相应地移动到窗口堆栈的上面去。
从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划和Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系列的文章可以知道,窗口的UI最终是需要通过SurfaceFlinger服务来统一渲染的,而SurfaceFlinger服务在渲染窗口的UI之前,需要计算基于各个窗口的Z轴位置来计算它们的可见区域。因此,WindowManagerService服务计算好每一个窗口的Z轴位置之后,还需要将它们设置到SurfaceFlinger服务中去,以便SurfaceFlinger服务可以正确地渲染每一个窗口的UI。
上述窗口的Z轴位置计算和设置过程如图1所示:
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/dde91162336345f248877aa9279124bf_817x352.jpg)
图1 窗口Z轴位置的计算和设置过程
接下来,我们就首先分析两个需要重新计算窗口Z轴位置的情景,接着再分析窗口的Z轴位置的计算过程,最后分析WindowManagerService服务将窗口的Z轴设置到SurfaceFlinger服务中去的过程。
#### **一. 需要重新计算窗口Z轴位置的情景**
这里主要分析两个需要重新计算窗口Z轴位置的情景:应用程序增加一个窗口到WindowManagerService服务和应用程序请求WindowManagerService服务重新布局一个窗口。
从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一文可以知道,应用程序请求增加一个窗口到WindowManagerService服务的时候,最终会调用到WindowManagerService类的成员函数addWindow。接下来我们就主要分析这个函数与重新计算窗口Z轴位置相关的逻辑,如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
public int addWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int viewVisibility,
Rect outContentInsets, InputChannel outInputChannel) {
......
synchronized(mWindowMap) {
......
WindowToken token = mTokenMap.get(attrs.token);
......
win = new WindowState(session, client, token,
attachedWindow, attrs, viewVisibility);
......
if (attrs.type == TYPE_INPUT_METHOD) {
mInputMethodWindow = win;
addInputMethodWindowToListLocked(win);
......
} else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) {
mInputMethodDialogs.add(win);
addWindowToListInOrderLocked(win, true);
adjustInputMethodDialogsLocked();
......
} else {
addWindowToListInOrderLocked(win, true);
if (attrs.type == TYPE_WALLPAPER) {
......
adjustWallpaperWindowsLocked();
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
adjustWallpaperWindowsLocked();
}
}
......
assignLayersLocked();
......
}
......
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类的成员函数addWindow的具体实现可以参考Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析和Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析这两篇文章。我们注意到,WindowManagerService类的成员函数addWindow会根据当前正在添加的窗口的类型来调用不同的成员函数来向窗口堆栈的合适位置插入一个WindowState对象,即:
1. 如果添加的是一个输入法窗口,那么就调用成员函数addInputMethodWindowToListLocked将它放置在需要显示输入法的窗口的上面去;
2. 如果添加的是一个输入法对话框,那么就先调用成员函数addWindowToListInOrderLocked来将它插入到窗口堆栈中,接着再调用成员函数adjustInputMethodDialogsLocked来将它放置在输入法窗口的上面;
3. 如果添加的是一个普通窗口,那么就直接调用成员函数addWindowToListInOrderLocked来将它插入到窗口堆栈中;
4. 如果添加的是一个普通窗口,并且这个窗口需要显示壁纸,那么就先调用成员函数addWindowToListInOrderLocked来将它插入到窗口堆栈中,接着再调用成员函数adjustWallpaperWindowsLocked来将壁纸窗口放置在它的下面。
5. 如果添加的是一个壁纸窗口,那么就先调用成员函数addWindowToListInOrderLocked来将它插入到窗口堆栈中,接着再调用成员函数adjustWallpaperWindowsLocked来将它放置在需要显示壁纸的窗口的下面。
无论如何,WindowManagerService类的成员函数addWindow最终都会调用成员函数assignLayersLocked来重新计算系统中所有窗口的Z轴位置,这是因为前面往窗口堆栈增加了一个新的窗口。
从前面Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析一文可以知道,应用程序进程请求WindowManagerService服务重新布局一个窗口的时候,最终会调用到WindowManagerService类的成员函数relayoutWindow。接下来我们就主要分析这个函数与重新计算窗口Z轴位置相关的逻辑,如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
public int relayoutWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, boolean insetsPending,
Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
Configuration outConfig, Surface outSurface) {
......
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
......
int attrChanges = 0;
int flagChanges = 0;
if (attrs != null) {
flagChanges = win.mAttrs.flags ^= attrs.flags;
attrChanges = win.mAttrs.copyFrom(attrs);
}
......
boolean imMayMove = (flagChanges&(
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0;
boolean focusMayChange = win.mViewVisibility != viewVisibility
|| ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0)
|| (!win.mRelayoutCalled);
boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
&& (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
......
if (focusMayChange) {
......
if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
imMayMove = false;
}
......
}
// updateFocusedWindowLocked() already assigned layers so we only need to
// reassign them at this point if the IM window state gets shuffled
boolean assignLayers = false;
if (imMayMove) {
if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {
// Little hack here -- we -should- be able to rely on the
// function to return true if the IME has moved and needs
// its layer recomputed. However, if the IME was hidden
// and isn't actually moved in the list, its layer may be
// out of data so we make sure to recompute it.
assignLayers = true;
}
}
if (wallpaperMayMove) {
if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
assignLayers = true;
}
}
......
if (assignLayers) {
assignLayersLocked();
}
......
}
......
return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)
| (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类的成员函数relayoutWindow具体实现可以参考Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析和Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析这两篇文章,与窗口Z轴位置计算相关的逻辑大概是这样的:
1. 如果系统当前获得焦点的窗口可能发生了变化,那么就会调用成员函数updateFocusedWindowLocked来重新计算系统当前应该获得焦点的窗口。如果系统当前获得焦点的窗口真的发生了变化,即窗口堆栈的窗口排列发生了变化,那么在调用成员函数updateFocusedWindowLocked的时候,就会调用成员函数assignLayersLocked来重新计算系统中所有窗口的Z轴位置。
2. 如果系统中的输入法窗口可能需要移动,那么就会调用成员函数moveInputMethodWindowsIfNeededLocked来检查是否真的需要移动输入法窗口。如果需要移动,那么成员函数moveInputMethodWindowsIfNeededLocked的返回值就会等于true,这时候就说明输入法窗口在窗口堆栈中的位置发生了变化,因此,就会将变量assignLayers的值设置为true,表示接下来需要重新计算系统中所有窗口的Z轴位置。
3. 如果当前正在请求调整其布局的窗口是由不可见变化可见的,即变量displayed的值等于true,那么接下来也是需要重新计算系统中所有窗口的Z轴位置的,因此,就会将assignLayers的值设置为true。
4. 如果系统中的壁纸窗口可能需要移动,那么就会调用成员函数adjustWallpaperWindowsLocked来检查是否真的需要移动壁纸窗口。如果需要移动,那么成员函数adjustWallpaperWindowsLocked的返回值的ADJUST_WALLPAPER_LAYERS_CHANGED位就会等于1,这时候就说明壁纸窗口在窗口堆栈中的位置发生了变化,因此,就会将变量assignLayers的值设置为true,表示接下来需要重新计算系统中所有窗口的Z轴位置。
经过上述的一系列操作后,如果得到的变量assignLayers的值设置等于true,那么WindowManagerService类的成员函数relayoutWindow就会调用成员函数assignLayersLocked来重新计算系统中所有窗口的Z轴位置。
#### **二. 计算系统中所有窗口的Z轴位置**
从前面第一部分的内容可以知道,一旦窗口堆栈中的窗口发生了变化,那么WindowManagerService类的成员函数assignLayersLocked就会调用来计算系统中所有窗口的Z轴位置。
窗口的Z轴位置除了与它在窗口堆栈中的位置有关之外,还与窗口的类型有关。窗口的类型在创建的时候就已经是确定了的,WindowManagerService服务在为它创建一个WindowState对象的时候,就会根据它的类型得到一个BaseLayer值,这个BaseLayer值在计算它的Z轴位置的时候会用到。
接下来我们就通过WindowState类的构造函数来分析一个窗口的BaseLayer值是如何确定的,如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
/** How much to multiply the policy's type layer, to reserve room
* for multiple windows of the same type and Z-ordering adjustment
* with TYPE_LAYER_OFFSET. */
static final int TYPE_LAYER_MULTIPLIER = 10000;
/** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
* or below others in the same layer. */
static final int TYPE_LAYER_OFFSET = 1000;
......
private final class WindowState implements WindowManagerPolicy.WindowState {
......
final int mBaseLayer;
final int mSubLayer;
......
WindowState(Session s, IWindow c, WindowToken token,
WindowState attachedWindow, WindowManager.LayoutParams a,
int viewVisibility) {
......
if ((mAttrs.type >= FIRST_SUB_WINDOW &&
mAttrs.type <= LAST_SUB_WINDOW)) {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.windowTypeToLayerLw(
attachedWindow.mAttrs.type) * TYPE_LAYER_MULTIPLIER
+ TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
......
} else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
* TYPE_LAYER_MULTIPLIER
+ TYPE_LAYER_OFFSET;
mSubLayer = 0;
......
}
......
}
......
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
一个窗口除了有一个BaseLayer值之外,还有一个SubLayer值,分别保存在一个对应的WindowState对象的成员变量mBaseLayer和mSubLayer。SubLayer值是用来描述一个窗口是否是另外一个窗口的子窗口的。
假设一个窗口是另外一个窗口的子窗口,那么参数attachedWindow所描述的窗口就是父窗口,这时候子窗口的BaseLayer值就等于父窗口的BaseLayer值,而SubLayer值要么大于0,要么小于0,这与它自己的具体类型有关。
假设一个窗口不是另外一个窗口的子窗口,那么这个窗口的BaseLayer值就与它自己的具体类型有关,而SubLayer值就等于0。
现在的关键就是要根据窗口的类型来计算它的BaseLayer值和SubLayer值,它们分别是通过调用WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数windowTypeToLayerLw和subWindowTypeToLayerLw来计算得到的。这里有两个地方是需要注意的。
第一个地方是PhoneWindowManager对象的成员函数windowTypeToLayerLw的返回值并且不是一个窗口的最终的BaseLayer值,而是要将它的返回值乘以一个常量TYPE_LAYER_MULTIPLIER,再加上另外一个常量TYPE_LAYER_OFFSET之后,才得到最终的BaseLayer值。这是因为在Android系统中,相同类型的窗口的Z轴位置都是有着相同的值域,而不同类型的窗口的Z轴位置都是处于两个不相交的值域。例如,假设有两种不同类型的窗口,它们的Z轴位置的值域分别为[a, b]和[c, d],那么[a, b]和[c, d]的交集一定等于空。又由于每一种类型的窗口的数量是不确定的,因此,WindowManagerService服务就需要为每一种类型的窗口都预留一个范围足够大的值域,以便可以满足要求。
WindowManagerService服务是如何为类型相同的窗口的Z轴位置预留一个范围足够大的值域的呢?我们假设类型为t的窗口的Z轴位置的值域为[a, b],并且以t为参数调用PhoneWindowManager对象的成员函数windowTypeToLayerLw的返回值为T,那么a的值就等于T * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET,而b的值就等于(T - 1) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET - 1,即从T * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET开始,一共预留了TYPE_LAYER_MULTIPLIER个值作为类型为t窗口的Z轴位置。由于TYPE_LAYER_MULTIPLIER的值定义为10000,而TYPE_LAYER_OFFSET的值定义为1000,因此,每一种类型的窗口都预留有一个足够大的值域来作为Z轴位置。
第二个地方是窗口的SubLayer值并不直接参与窗口的Z轴位置的计算,但是它会影响到窗口在窗口堆栈的位置。接下来我们就会看到,窗口在窗口堆栈的位置是会影响到它的Z轴位置的计算的,因此,窗口的SubLayer间接地参与了窗口的Z轴位置的计算。
窗口的SubLayer值是如何影响到窗口在窗口堆栈的位置的呢?在前面Android窗口管理服务WindowManagerService对窗口的组织方式分析一文中,在分析WindowManagerService类的成员函数addWindowToListInOrderLocked的实现时提到,如果一个窗口是另外一个窗口的子窗口,那么当它的SubLayer值小于0的时候,它就会位于父窗口的下面,否则的话,就会位于父窗口的上面。
在继续分析WindowManagerService类的成员函数assignLayersLocked之前,我们首先分析PhoneWindowManager类的成员函数windowTypeToLayerLw和subWindowTypeToLayerLw的实现,以便可以了解一个窗口的BaseLayer值和SubLayer值是如何确定的。
PhoneWindowManager类的成员函数windowTypeToLayerLw的实现如下所示:
~~~
public class PhoneWindowManager implements WindowManagerPolicy {
......
public int windowTypeToLayerLw(int type) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
switch (type) {
case TYPE_STATUS_BAR:
return STATUS_BAR_LAYER;
case TYPE_STATUS_BAR_PANEL:
return STATUS_BAR_PANEL_LAYER;
case TYPE_SYSTEM_DIALOG:
return SYSTEM_DIALOG_LAYER;
case TYPE_SEARCH_BAR:
return SEARCH_BAR_LAYER;
case TYPE_PHONE:
return PHONE_LAYER;
case TYPE_KEYGUARD:
return KEYGUARD_LAYER;
case TYPE_KEYGUARD_DIALOG:
return KEYGUARD_DIALOG_LAYER;
case TYPE_SYSTEM_ALERT:
return SYSTEM_ALERT_LAYER;
case TYPE_SYSTEM_ERROR:
return SYSTEM_ERROR_LAYER;
case TYPE_INPUT_METHOD:
return INPUT_METHOD_LAYER;
case TYPE_INPUT_METHOD_DIALOG:
return INPUT_METHOD_DIALOG_LAYER;
case TYPE_SYSTEM_OVERLAY:
return SYSTEM_OVERLAY_LAYER;
case TYPE_SECURE_SYSTEM_OVERLAY:
return SECURE_SYSTEM_OVERLAY_LAYER;
case TYPE_PRIORITY_PHONE:
return PRIORITY_PHONE_LAYER;
case TYPE_TOAST:
return TOAST_LAYER;
case TYPE_WALLPAPER:
return WALLPAPER_LAYER;
}
Log.e(TAG, "Unknown window type: " + type);
return APPLICATION_LAYER;
}
......
}
~~~
这个函数定义在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java文件中。
从这里就可以看出,每一种窗口类型type都对应有一个BaseLayer值,即每一个TYPE_XXX值都对应有一个XXX_LAYER值,其中,TYPE_XXX值定义在WindowManager.LayoutParams类中,而XXX_LAYER值就定义在PhoneWindowManager类中,它们的对应关系如图2所示:
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/94fb53f82e3d22301764149e5f1670b2_683x538.jpg)
图2 窗口类型与窗口BaseLayer值的对应关系
注意,如果参数type的值小于FIRST_APPLICATION_WINDOW,或者大于LAST_APPLICATION_WINDOW,或者不是图2列出来的其中一个值,那么PhoneWindowManager类的成员函数windowTypeToLayerLw就会返回一个APPLICATION_LAYER(2)值给调用者。
PhoneWindowManager类的成员函数subWindowTypeToLayerLw的实现如下所示:
~~~
public class PhoneWindowManager implements WindowManagerPolicy {
......
public int subWindowTypeToLayerLw(int type) {
switch (type) {
case TYPE_APPLICATION_PANEL:
case TYPE_APPLICATION_ATTACHED_DIALOG:
return APPLICATION_PANEL_SUBLAYER;
case TYPE_APPLICATION_MEDIA:
return APPLICATION_MEDIA_SUBLAYER;
case TYPE_APPLICATION_MEDIA_OVERLAY:
return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
case TYPE_APPLICATION_SUB_PANEL:
return APPLICATION_SUB_PANEL_SUBLAYER;
}
Log.e(TAG, "Unknown sub-window type: " + type);
return 0;
}
......
}
~~~
这个函数定义在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java文件中。
从这里就可以看出,只有类型为TYPE_APPLICATION_PANEL、TYPE_APPLICATION_MEDIA、TYPE_APPLICATION_MEDIA_OVERLAY和TYPE_APPLICATION_SUB_PANEL的窗口才对应有一个SubLayer值,它们的对应关系如图3所示:
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/451cacb434d44c2309b2a1654a9643cb_767x381.jpg)
图3 窗口类型与窗口SubLayer值的对应关系
在图3中,TYPE_XXX值定义在WindowManager.LayoutParams类中,而XXX_LAYER值就定义在PhoneWindowManager类中。注意,有两种特殊的多媒体窗口TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY,它们是用来显示多媒体的,例如,用来显示视频,并且它们都是附加在应用程序窗口之上的,但是由于它们的SubLayer值为负数,因此它们实际上是位于宿主窗口之下的。类型为TYPE_APPLICATION_MEDIA的窗口有一个魔术,它会在宿主窗口里面挖一个洞,以便可以将自己显示出来,而类型为TYPE_APPLICATION_MEDIA_OVERLAY背景一般都是透明的,位于类型为TYPE_APPLICATION_MEDIA的窗口,可以用来显示视频的字幕之类的东西。实际上,类型为TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的窗口也称为SurfaceView。SurfaceView很特殊,它与普通的View的最大区别就在于它们有独立的绘图表面,于是它们就可以在一个独立的子线程里面进行UI渲染。
理解了窗口的BaseLayer值和SubLayer值的计算过程之外,接下来我们就可以分析WindowManagerService类的成员函数assignLayersLocked的实现了,如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
/** How much to increment the layer for each window, to reserve room
* for effect surfaces between them.
*/
static final int WINDOW_LAYER_MULTIPLIER = 5;
......
private final void assignLayersLocked() {
int N = mWindows.size();
int curBaseLayer = 0;
int curLayer = 0;
int i;
for (i=0; i 0 && w.mIsWallpaper)) {
curLayer += WINDOW_LAYER_MULTIPLIER;
w.mLayer = curLayer;
} else {
curBaseLayer = curLayer = w.mBaseLayer;
w.mLayer = curLayer;
}
if (w.mTargetAppToken != null) {
w.mAnimLayer = w.mLayer + w.mTargetAppToken.animLayerAdjustment;
} else if (w.mAppToken != null) {
w.mAnimLayer = w.mLayer + w.mAppToken.animLayerAdjustment;
} else {
w.mAnimLayer = w.mLayer;
}
if (w.mIsImWindow) {
w.mAnimLayer += mInputMethodAnimLayerAdjustment;
} else if (w.mIsWallpaper) {
w.mAnimLayer += mWallpaperAnimLayerAdjustment;
}
......
}
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
注意,在调用WindowManagerService类的成员函数assignLayersLocked之前,系统中的所有窗口在窗口堆栈中的位置都是已经排列好了的,这时候WindowManagerService类的成员函数assignLayersLocked就从下往上遍历窗口堆栈,以连排列在一起的类型相同的窗口为单位来计算每一个窗口的Z位置,即:
1. 每次遇到一个窗口,它的BaseLayer值与上一次计算的窗口的BaseLayer值不相等,就开始一个新的计算单元。
2. 在每一个计算单元中,第一个窗口的Z轴位置就等于它的BaseLayer值,而之后的每一个窗口的Z轴位置都比前一个窗口的Z轴位置大WINDOW_LAYER_MULTIPLIER。
这个窗口的Z轴位置计算方法有三个地方是需要注意的。
* 第一个地方是从第2点可以看出,每一个窗口的Z轴位置值都不是连续的,这样就在每两个窗口之间都保留了一定的位置来插入其它窗口。
* 第二个地方是由于系统中所有类型相同的窗口不一定都是排列在一起的,因此,就有可能出现有些类型相同的窗口具有相同的Z轴位置。WindowManagerService服务并不关心两个不同窗口的Z轴位置是否相同,但是SurfaceFlinger服务就需要关心了,因为SurfaceFlinger服务需要是按照Z轴从大到小的顺序来计算窗口的可见性。那么SurfaceFlinger服务是如何确定两个Z轴位置相同的窗口的次序的呢?从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划和Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系列的文章可以知道,每一个窗口在SurfaceFlinger服务都对应有一个Layer对象,而每一个Layer对象都有一个sequence值,其中,先创建的Layer对象的sequence值大于后创建的Layer对象的sequence值。这样,SurfaceFlinger服务在计算对于两个Z轴位置相同的窗口的可见性的时候,就会比较它们所对应的Layer对象的sequence值,其中,sequence值大的窗口的可见性会先于sequence值小的窗口得到计算,即先计算后创建的窗口的可见性,再计算先创建的窗口的可见性。
* 第三个地方是有两种特殊的窗口,即输入法窗口和壁纸窗口,当它们不是窗口堆栈底部的第一个窗口时,它们所在的计算单元不是以窗口类型来划分的,而靠近在哪个窗口,就与哪个窗口在同一个计算单元中。当输入法窗口是窗口堆栈底部的第一个窗口时,它的Z轴位置就等于WINDOW_LAYER_MULTIPLIER,而当壁纸窗口是窗口堆栈底部的第一个窗口时,它的Z轴位置就等于它的BaseLayer值。
前面计算得到的窗口的Z轴位置保存在WindowState类的成员变量mLayer中。事实上,保存在WindowState类的成员变量mLayer中的Z轴位置还不是窗口的最终Z轴位置,因为还没有考虑到窗口与窗口令牌之间的关系。每一个窗口令牌都可以设置一个Z轴调整值,而每一个窗口要加上它所对应的窗口令牌所设置的Z轴调整值之后,才能得到最终的Z轴位置。注意,只有类型为AppWindowToken的窗口令牌才可以设置Z轴调整值,这个Z轴调整值就保存在AppWindowToken类的成员变量animLayerAdjustment中。
有时候,一个窗口会有一个目标窗口。例如,输入法窗口的目标窗口是系统当前需要显示输入法的窗口。在这种情况下,我们要使用目标窗口所对应的窗口令牌所设置的Z轴调整值来调整窗口的的Z轴位置。
那么,WindowManagerService服务是如何知道一个窗口所对应的窗口令牌的类型是AppWindowToken,或者一个窗口有没有目标窗口的呢?当用来描述一个窗口的WindowState对象成员变量mAppToken的值不等于null的时候,那么就说明该窗口所对应的窗口令牌的类型是AppWindowToken,而当用来描述一个窗口的WindowState对象成员变量mTargetAppToken的值不等于null的时候,那么就说明该窗口有一个目标窗口。
经过上面的调整之后,窗口的Z轴位置就保存在WindowState类的成员变量mAnimLayer中。对于非输入法窗口和非壁纸窗口来说,这时候保存在用来描述它们的WindowState对象的成员变量mAnimLayer中的Z轴位置就是它们最终的Z轴位置了,但是对于输入法窗口和壁纸窗口来说,还需要继续判断它们的目标窗口是否需要调整它们的Z轴位置。
从前面Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析和Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析这两篇文章知道,如果一个窗口要调整它所关联的输入法窗口和壁纸窗口的Z轴位置,那么要调整的值就会保存在WindowManagerService类的成员变量mInputMethodAnimLayerAdjustment和mWallpaperAnimLayerAdjustment中,因此,只要将WindowManagerService类的成员变量mInputMethodAnimLayerAdjustment和mWallpaperAnimLayerAdjustment的值分别增加到前面所计算得到的输入法窗口和壁纸窗口的Z轴位置上去,就可以得到输入法窗口和壁纸窗口的最终Z轴位置,并且保存到用来对应的WindowState对象的成员变量mAnimLayer中。
从上面的计算过程就可以知道,系统中所有类型的窗口的最终Z轴位置都保存在WindowState类的成员变量mAnimLayer中。
#### **三. 设置窗口的Z轴位置到SurfaceFlinger服务中去**
WindowManagerService服务在刷新系统的UI的时候,就会将系统中已经计算好了的窗口Z轴位置设置到SurfaceFlinger服务中去,以便SurfaceFlinger服务可以对系统中的窗口进行可见性计算以及合成和渲染等操作。
从前面Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析一文可以知道,刷新系统UI是通过调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner来实现的,接下来我们就分析这个成员函数与设置窗口的Z轴位置到SurfaceFlinger服务中去相关的逻辑。
为了方便描述设置窗口的Z轴位置到SurfaceFlinger服务中去的过程,我们先列出WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner的实现架构,如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
private final void performLayoutAndPlaceSurfacesLockedInner(
boolean recoveringMemory) {
......
Surface.openTransaction();
......
try {
......
int repeats = 0;
int changes = 0;
do {
repeats++;
if (repeats > 6) {
......
break;
}
// FIRST LOOP: Perform a layout, if needed.
if (repeats < 4) {
changes = performLayoutLockedInner();
if (changes != 0) {
continue;
}
} else {
Slog.w(TAG, "Layout repeat skipped after too many iterations");
changes = 0;
}
// SECOND LOOP: Execute animations and update visibility of windows.
......
} while (changes != 0);
// THIRD LOOP: Update the surfaces of all windows.
......
//更新窗口的绘图表面的操作包括:
//1. 设置窗口的大小
//2. 设置窗口在X轴和Y轴上的位置
//3. 设置窗口在Z轴上的位置
//4. 设置窗口的Alpha通道
//5. 设置窗口的变换矩阵
......
} catch (RuntimeException e) {
......
}
......
Surface.closeTransaction();
......
// Destroy the surface of any windows that are no longer visible.
......
// Time to remove any exiting tokens?
......
// Time to remove any exiting applications?
......
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
在前面Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析一文中,我们已经分析过WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLockedInner的实现架构了,其中,设置窗口的Z轴位置到SurfaceFlinger服务中去是在更新窗口的绘图表面的操作中进行的,即是在THIRD LOOP中进行的,同时设置的还包括窗口的大小、X轴和Y轴位置、Alpha通道和变换矩阵,这些代码如下所示:
~~~
//更新窗口的绘图表面的操作包括:
//1. 设置窗口的大小
//2. 设置窗口在X轴和Y轴上的位置
//3. 设置窗口在Z轴上的位置
//4. 设置窗口的Alpha通道值
//5. 设置窗口的变换矩阵
final int N = mWindows.size();
for (i=N-1; i>=0; i--) {
WindowState w = mWindows.get(i);
......
if (w.mSurface != null) {
......
w.computeShownFrameLocked();
......
boolean resize;
int width, height;
if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
resize = w.mLastRequestedWidth != w.mRequestedWidth ||
w.mLastRequestedHeight != w.mRequestedHeight;
// for a scaled surface, we just want to use
// the requested size.
width = w.mRequestedWidth;
height = w.mRequestedHeight;
w.mLastRequestedWidth = width;
w.mLastRequestedHeight = height;
w.mLastShownFrame.set(w.mShownFrame);
try {
......
w.mSurfaceX = w.mShownFrame.left;
w.mSurfaceY = w.mShownFrame.top;
w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
} catch (RuntimeException e) {
......
if (!recoveringMemory) {
reclaimSomeSurfaceMemoryLocked(w, "position");
}
}
} else {
resize = !w.mLastShownFrame.equals(w.mShownFrame);
width = w.mShownFrame.width();
height = w.mShownFrame.height();
w.mLastShownFrame.set(w.mShownFrame);
}
if (resize) {
if (width < 1) width = 1;
if (height < 1) height = 1;
if (w.mSurface != null) {
try {
......
w.mSurfaceResized = true;
w.mSurfaceW = width;
w.mSurfaceH = height;
w.mSurface.setSize(width, height);
w.mSurfaceX = w.mShownFrame.left;
w.mSurfaceY = w.mShownFrame.top;
w.mSurface.setPosition(w.mShownFrame.left,
w.mShownFrame.top);
} catch (RuntimeException e) {
......
if (!recoveringMemory) {
reclaimSomeSurfaceMemoryLocked(w, "size");
}
}
}
}
......
if (w.mAttachedHidden || !w.isReadyForDisplay()) {
if (!w.mLastHidden) {
w.mLastHidden = true;
......
if (w.mSurface != null) {
w.mSurfaceShown = false;
try {
w.mSurface.hide();
} catch (RuntimeException e) {
......
}
}
}
......
} else if (w.mLastLayer != w.mAnimLayer
|| w.mLastAlpha != w.mShownAlpha
|| w.mLastDsDx != w.mDsDx
|| w.mLastDtDx != w.mDtDx
|| w.mLastDsDy != w.mDsDy
|| w.mLastDtDy != w.mDtDy
|| w.mLastHScale != w.mHScale
|| w.mLastVScale != w.mVScale
|| w.mLastHidden) {
......
w.mLastAlpha = w.mShownAlpha;
w.mLastLayer = w.mAnimLayer;
w.mLastDsDx = w.mDsDx;
w.mLastDtDx = w.mDtDx;
w.mLastDsDy = w.mDsDy;
w.mLastDtDy = w.mDtDy;
w.mLastHScale = w.mHScale;
w.mLastVScale = w.mVScale;
......
if (w.mSurface != null) {
try {
w.mSurfaceAlpha = w.mShownAlpha;
w.mSurface.setAlpha(w.mShownAlpha);
w.mSurfaceLayer = w.mAnimLayer;
w.mSurface.setLayer(w.mAnimLayer);
w.mSurface.setMatrix(
w.mDsDx*w.mHScale, w.mDtDx*w.mVScale,
w.mDsDy*w.mHScale, w.mDtDy*w.mVScale);
} catch (RuntimeException e) {
.....
if (!recoveringMemory) {
reclaimSomeSurfaceMemoryLocked(w, "update");
}
}
}
if (w.mLastHidden && !w.mDrawPending
&& !w.mCommitDrawPending
&& !w.mReadyToShow) {
......
if (showSurfaceRobustlyLocked(w)) {
w.mHasDrawn = true;
w.mLastHidden = false;
}
}
......
}
......
}
~~~
这段代码通过一个for循环来遍历保存在窗口堆栈的每一个WindowState对象,以便可以对系统中的每一个窗口的绘图表面进行更新。注意,只有那些成员变量mSurface的值不等于null的WindowState对象,它们所描述的窗口才具有绘图表面,因此需要对它们进行更新。
在更新WindowState对象w所描述的窗口的绘图表面之前,首先要调用它的成员函数computeShownFrameLocked来确定该窗口实际要显示的大小、位置、Alpha通道和变换矩阵等信息,其中:
1. 窗口实际要显示的大小和X轴、Y轴位置保存在WindowState对象w的成员变量mShownFrame中。
2. 窗口实际要显示的Alpha通道保存在WindowState对象w的成员变量mShownAlpha中。
3. 窗口实际要显示的Z轴位置保存在WindowState对象w的成员变量mAnimLayer中。
4. 窗口实际要使用的变换矩阵保存在WindowState对象w的成员变量mDsDx、mDtDx、mDsDy和mDtDy中。
有了上述信息之后,我们就可以将WindowState对象w所描述的窗口实际要显示的大小、位置、Alpha通道和变换矩阵等信息设置到SurfaceFlinger服务中去了。
我们首先分析WindowState对象w所描述的窗口实际要显示的大小、X轴和Y轴位置的设置过程,接着再分析WindowState对象w所描述的窗口实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵的设置过程。
在调用WindowState对象w的成员函数computeShownFrameLocked来计算它所描述的窗口的大小的时候,是没有考虑该窗口的大小是否设置有缩放因子的。
当WindowState对象w所描述的窗口的大小设置有缩放因子的时候,那么WindowState对象w的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_SCALED位就会等于1,这时候WindowState对象w所描述的窗口实际要显示的大小是保存在它的成员变量mRequestedWidth和mRequestedHeight中的。在这种情况下,这段代码就会执行以下操作:
1. 计算WindowState对象w所描述的窗口实际要显示的大小是否发生了变化。如果发生了变化,那么就将变量resize的值设置为true。注意,WindowState对象w所描述的窗口上一次实际要显示的大小保存在成员变量mLastRequestedWidth和mLastRequestedHeight中,因此,当这两个成员变量与其它两个成员变量mRequestedWidth和mRequestedHeight的值不相等于时,就说明WindowState对象w所描述的窗口实际要显示的大小是否发生了变化。
2. 将WindowState对象w所描述的窗口实际要显示的大小分别更新到成员变量mLastRequestedWidth和mLastRequestedHeight中,以及变量width和height中。
3. 将WindowState对象w的成员变量mShownFrame的值保存在另外一个成员变量mLastShownFrame中,以便可以记录WindowState对象w所描述的窗口上一次实际要显示的大小和X轴、Y轴位置。
4. 将WindowState对象w所描述的窗口的X轴和Y轴位置分别保存到成员变量mSurfaceX和mSurfaceY中,并且调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setPosition来将这两个位置值设置到SurfaceFlinger服务中去。
5. 在设置WindowState对象w所描述的窗口的X轴和Y轴位置到SurfaceFlinger服务中去的过程中,如果出现了异常,那么就说明系统内存资源不足。在这种情况下,如果参数recoveringMemory的值等于false,那么就说明WindowManagerService服务目前不是处于内存资源的回收过程中,于是就会调用WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来执行回收系统内存资源的操作。
当WindowState对象w所描述的窗口的大小没有设置有缩放因子的时候,那么WindowState对象w的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量flags的FLAG_SCALED位就会等于0,这时候WindowState对象w所描述的窗口实际要显示的大小是保存在它的成员变量mShownFrame中的。在这种情况下,这段代码就会执行以下操作:
1. 计算WindowState对象w所描述的窗口实际要显示的大小是否发生了变化。如果发生了变化,那么就将变量resize的值设置为true。注意,这时候只要比较WindowState对象w的成员变量mLastShownFrame和mShownFrame所描述的两个矩形区域的大小是否相等,就可以知道WindowState对象w所描述的窗口实际要显示的大小是否发生了变化,因为WindowState对象w的成员变量mLastShownFrame保存的是窗口上一次实际要显示的大小。
2. 将WindowState对象w所描述的窗口实际要显示的大小分别保存在变量width和height中。
3. 将WindowState对象w的成员变量mShownFrame的值保存在另外一个成员变量mLastShownFrame中,以便可以记录WindowState对象w所描述的窗口上一次实际要显示的大小和X轴、Y轴位置。
执行完成以上操作之后,WindowState对象w所描述的窗口实际要显示的X轴和Y轴位置就保存在成员变量mShownFrame所描述的一个 Rect对象的成员变量left和top中,而实际要显示的大小就显示在变量width和height中。这时候如果变量resize的值等于true,那么就说明WindowState对象w所描述的窗口的大小发生了变化。在这种情况下,就需要执行以下操作:
1. 重新设置WindowState对象w所描述的窗口的大小到SurfaceFlinger服务中去,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setSize来实现的。注意,如果前面计算得到WindowState对象w所描述的窗口的宽度width和高度height的值小于1,那么就需要将它们的值设置为1,因为一个窗口的宽度和高度值是不能小于1的。
2. 重新设置WindowState对象w所描述的窗口在X轴和Y轴上的位置到SurfaceFlinger服务中去,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setPosition来实现的。注意,在设置之前,还会将WindowState对象w所描述的窗口在X轴和Y轴上的位置保存在成员变量mSurfaceX和mSurfaceY中。
3. 在设置WindowState对象w所描述的窗口的大小以及在X轴和Y轴上的位置到SurfaceFlinger服务中去的过程中,如果出现了异常,那么同样需要判断参数recoveringMemory的值来决定是否需要WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来回收系统内存资源。
设置好WindowState对象w所描述的窗口实际要显示的大小、X轴和Y轴位置到SurfaceFlinger服务中去之后,接下来就要继续设置它实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵了,不过只有当WindowState对象w所描述的窗口当前是处于可见状态、并且这些值没有发生变化的情况下才需要这样做。
当WindowState对象w的成员函数isReadyForDisplay的返回值等于false时,就说明WindowState对象w所描述的窗口当前是处于不可见状态的。还有另外一种情况,即当WindowState对象w所描述的窗口是附加在另外一个窗口之上、并且这个被附加的窗口是不可见时,即WindowState对象w的成员变量mAttachedHidden的值等于true时,也是说明WindowState对象w所描述的窗口当前是处于不可见状态的。
在WindowState对象w所描述的窗口当前是处于不可见状态的情况下,如果该窗口在上一次系统UI刷新时是处于可见状态的,即WindowState对象w的成员变量mLastHidden的值等于true,那么这时候就需要将WindowState对象w所描述的窗口隐藏起来,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数hide来实现的。注意,在调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数hide来隐藏窗口之前,需要分别将WindowState对象w的成员变量mLastHidden和mSurfaceShown的值设置为true和false,以便可以正确描述窗口的不可见状态。
在WindowState对象w所描述的窗口当前是处于可见状态的情况下,如果该窗口实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵发生了变化,那么就需要将新的值设置到SurfaceFlinger服务中去,其中:
1. WindowState对象w的成员变量mLastLayer与mAnimLayer的值不相等说明它描述的窗口的Z轴位置发生了变化。
2. WindowState对象w的成员变量mLastAlpha与mShownAlpha的值不相等说明它描述的窗口的Alpha通道发生了变化。
3. WindowState对象w的成员变量mLastDsDx、mLastDtDx、mLastDsDy、 mLastDtDy、mLastHScale、mLastVScale与成员变量mDsDx、mDtDx、mDsDy、 mDtDy、mHScale、mVScale的值不相等说明它描述的窗口的变换矩阵发生了变化。
在WindowState对象w所描述的窗口当前是处于可见状态的情况下,如果该窗口在上一次系统UI刷新时是处于可见状态的,即WindowState对象w的成员变量mLastHidden的值等于true,那么也是需要重新设置WindowState对象w所描述的窗口实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵到SurfaceFlinger服务中去的。
无论如何,当需要重新设置WindowState对象w所描述的窗口实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵到SurfaceFlinger服务中去时,就需要执行以下操作:
1. 重新设置WindowState对象w所描述的窗口的Alpha通道到SurfaceFlinger服务中去,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setAlpha来实现的。在设置之前,还会将WindowState对象w的成员变量mShownAlpha的值同时保存在成员变量mLastAlpha和mSurfaceAlpha中,以便可以记录WindowState对象w所描述的窗口上一次所使用的Alpha通道。
2. 重新设置WindowState对象w所描述的窗口的Z轴位置到SurfaceFlinger服务中去,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setLayer来实现的。在设置之前,还会将WindowState对象w的成员变量mAnimLayer的值同时保存在成员变量mLastLayer和mSurfaceLayer中,以便可以记录WindowState对象w所描述的窗口上一次所使用的Z轴位置。
3. 重新设置WindowState对象w所描述的窗口的变换矩阵到SurfaceFlinger服务中去,这是通过调用WindowState对象w的成员变量mSurface所指向的一个Surface对象的成员函数setMatrix来实现的。在设置之前,还会将WindowState对象w的成员变量成员变量mDsDx、mDtDx、mDsDy、 mDtDy、mHScale、mVScale的值分别保存在成员变量mLastDsDx、mLastDtDx、mLastDsDy、 mLastDtDy、mLastHScale、mLastVScale中,以便可以记录WindowState对象w所描述的窗口上一次所使用的变换矩阵。注意,WindowState对象的成员变量mHScale和mVScale描述的窗口在宽度和高度上的缩放因子,因此,在设置窗口的变换矩阵时,需要乘以这些因子才可以得到正确的变换矩阵参数。
4. 在设置WindowState对象w所描述的窗口的Alpha通道、Z轴位置以及实际要使用的变换矩阵到SurfaceFlinger服务的过程中,如果出现了异常,那么同样需要判断参数recoveringMemory的值来决定是否需要WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来回收系统内存资源。
将WindowState对象w所描述的窗口实际要显示的Alpha通道、Z轴位置以及实际要使用的变换矩阵设置到SurfaceFlinger服务之后,如果WindowState对象w所描述的窗口满足以下条件:
1. 上一次处于不可见状态,即WindowState对象w的成员变量mLastHidden的值等于true;
2. UI已经绘制完成,即WindowState对象w的成员变量mDrawPending和mCommitDrawPending值等于false;
3. 不是处于等待同一个窗口令牌的其它窗口的完成UI绘制的状态,即WindowState对象w的成员变量mReadyToShow的值等于false;
那么就说明现在就是时候要将WindowState对象w所描述的窗口显示出来了,这是通过调用WindowManagerService类的成员函数showSurfaceRobustlyLocked来实现的。如果WindowManagerService类的成员函数showSurfaceRobustlyLocked的返回值等于true,那么就说明WindowManagerService服务已经成功地通知SurfaceFlinger服务将WindowState对象w所描述的窗口显示出来,于是就会分别将WindowState对象w的成员变量mHasDrawn和mLastHidden的值设置为true和false,以便可以表示WindowState对象w所描述的窗口的UI已经绘制完成,并且已经显示出来。
WindowManagerService类的成员函数showSurfaceRobustlyLocked的实现如下所示:
~~~
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
boolean showSurfaceRobustlyLocked(WindowState win) {
try {
if (win.mSurface != null) {
win.mSurfaceShown = true;
win.mSurface.show();
......
}
return true;
} catch (RuntimeException e) {
......
}
reclaimSomeSurfaceMemoryLocked(win, "show");
return false;
}
......
}
~~~
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类的成员函数showSurfaceRobustlyLocked用来通知SurfaceFlinger服务将参数win所描述的窗口显示出来,这是通过调用WindowState对象win的成员变量mSurface所指向的一个Surface对象的成员函数show来实现的。注意,在通知SurfaceFlinger服务将WindowState对象win所描述的窗口显示出来之前,还会将它的成员变量mSurfaceShown的值设置为true。
如果在通知SurfaceFlinger服务将WindowState对象win所描述的窗口显示出来的过程出现了异常,那么WindowManagerService类的成员函数showSurfaceRobustlyLocked就会调用另外一个成员函数reclaimSomeSurfaceMemoryLocked来回收系统内存资源。
从上面分析可以知道,一个窗口的显示和隐藏,以及大小、X轴和Y轴位置、Z轴位置、Alpha通道和变换矩阵设置,是通过调用Java层的Surface类的成员函数show、hide、setSize、setPosition、setLayer、setAlpha和setMatrix来实现的,它们都是一些JNI方法,定义在文件frameworks/base/core/java/android/view/Surface.java中,如下所示:
~~~
public class Surface implements Parcelable {
......
private int mSurfaceControl;
......
/**
* set surface parameters.
* needs to be inside open/closeTransaction block
*/
public native void setLayer(int zorder);
public native void setPosition(int x, int y);
public native void setSize(int w, int h);
public native void hide();
public native void show();
......
public native void setAlpha(float alpha);
public native void setMatrix(float dsdx, float dtdx,
float dsdy, float dtdy);
......
}
~~~
这些JNI方法是由C++层中的函数Surface_show、Surface_hide、Surface_setSize、Surface_setPosition、Surface_setLayer、Surface_setAlpha和Surface_setMatrix来实现的,如下所示:
~~~
static void Surface_setLayer(
JNIEnv* env, jobject clazz, jint zorder)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->setLayer(zorder);
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setPosition(
JNIEnv* env, jobject clazz, jint x, jint y)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->setPosition(x, y);
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setSize(
JNIEnv* env, jobject clazz, jint w, jint h)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->setSize(w, h);
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_hide(
JNIEnv* env, jobject clazz)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->hide();
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_show(
JNIEnv* env, jobject clazz)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->show();
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setAlpha(
JNIEnv* env, jobject clazz, jfloat alpha)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->setAlpha(alpha);
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
static void Surface_setMatrix(
JNIEnv* env, jobject clazz,
jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy)
{
const sp& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
status_t err = surface->setMatrix(dsdx, dtdx, dsdy, dtdy);
if (err<0 && err!=NO_INIT)
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
~~~
这些JNI方法定义在文件frameworks/base/core/jni/android_view_Surface.cpp中。
这些JNI都有一个共同的特点,即先调用函数getSurfaceControl来获得与参数clazz所描述的一个Java层的Surface对象所对应的一个SurfaceControl对象。有了这个SurfaceControl对象之后,就可以分别调用它的成员函数show、hide、setSize、setPosition、setLayer、setAlpha和setMatrix来通知SurfaceFlinger服务来显示和隐藏一个窗口,以及设置一个窗口大小、X轴和Y轴位置、Z轴位置、Alpha通道和变换矩阵。
从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,每一个Activity窗口在Java层都对应有两个Surface对象,其中一个位于应用程序进程这一侧,而另外一个位于WindowManagerService服务这一侧。每一个位于应用程序进程这一侧的Java层的Surface对象在C++层中都对应有一个Surface对象,而每一个位于WindowManagerService服务这一侧的Java层的Surface对象在C++层中都对应有一个SurfaceControl对象,这个C++层的SurfaceControl对象的地址就保存在Java层的Surface对象的成员变量mSurfaceControl中。
从上面的分析可以知道,我们目前正在操作的是正在位于WindowManagerService服务这一侧的Java层的Surface对象,因此,通过调用函数getSurfaceControl就可以在C++层中获得一个对应的SurfaceControl对象,而有了这个SurfaceControl对象之后,就可以用来通知SurfaceFlinger服务更新一个窗口的属性,这一点可以参考前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划和Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划两个系列的文章。
至此,WindowManagerService服务计算窗口Z轴位置的过程就分析完成了,这个过程如下所示:
1. WindowManagerService服务将窗口排列在一个窗口堆栈中;
2. WindowManagerService服务根据窗口类型以及窗口在窗口堆栈的位置来计算得窗口的Z轴位置;
3. WindowManagerService服务通过Java层的Surface类的成员函数setLayer来将窗口的Z轴位置设置到SurfaceFlinger服务中去;
4. Java层的Surface类的成员函数setLayer又是通过调用C++层的SurfaceControl类的成员函数setLayer来将窗口的Z轴位置设置到SurfaceFlinger服务中去的;
通过这篇文章以及前面三篇文章(窗口组织、输入法窗口、壁纸窗口)的学习,我们对WindowManagerService服务对窗口的管理就有一个比较深刻的认识了,在接下来的文章中,我们还将继续分析ActivityWindowManager服务和WindowManagerService服务是如何协作来完成Activity窗口的显示过程的,敬请关注!
';