WindowManagerService服务是如何以堆栈的形式来组织窗口
本文导语: 我们知道,在Android系统中,Activity是以堆栈的形式组织在ActivityManagerService服务中的。与Activity类似,Android系统中的窗口也是以堆栈的形式组织在WindowManagerService服务中的,其中,Z轴位置较低的窗口位于Z轴位置较高的窗口的下...
我们知道,在Android系统中,Activity是以堆栈的形式组织在ActivityManagerService服务中的。与Activity类似,Android系统中的窗口也是以堆栈的形式组织在WindowManagerService服务中的,其中,Z轴位置较低的窗口位于Z轴位置较高的窗口的下面。在本文中,我们就详细分析WindowManagerService服务是如何以堆栈的形式来组织窗口的。
从前面Android应用程序启动过程源代码分析一文可以知道,应用程序进程中的每一个Activity组件在Activity管理服务ActivityManagerService中都对应有一个ActivityRecord对象。从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一文又可以知道,Activity管理服务ActivityManagerService中每一个ActivityRecord对象在Window管理服务WindowManagerService中都对应有一个AppWindowToken对象。
此外,在输入法管理服务InputMethodManagerService中,每一个输入法窗口都对应有一个Binder对象,这个Binder对象在Window管理服务WindowManagerService又对应有一个WindowToken对象。
与输入法窗口类似,在壁纸管理服务WallpaperManagerService中,每一个壁纸窗口都对应有一个Binder对象,这个Binder对象在Window管理服务WindowManagerService也对应有一个WindowToken对象。
在Window管理服务WindowManagerService中,无论是AppWindowToken对象,还是WindowToken对象,它们都是用来描述一组有着相同令牌的窗口的,每一个窗口都是通过一个WindowState对象来描述的。例如,一个Activity组件窗口可能有一个启动窗口(Starting Window),还有若干个子窗口,那么这些窗口就会组成一组,并且都是以Activity组件在Window管理服务WindowManagerService中所对应的AppWindowToken对象为令牌的。从抽象的角度来看,就是在Window管理服务WindowManagerService中,每一个令牌(AppWindowToken或者WindowToken)都是用来描述一组窗口(WindowState)的,并且每一个窗口的子窗口也是与它同属于一个组,即都有着相同的令牌。
上述的窗口组织方式如图1所示:
图1 窗口在WindowManagerService服务中的组织方式
其中,Activity Stack是在ActivityManagerService服务中创建的,Token List和Window Stack是在WindowManagerService中创建的,而Binder for IM和Binder for WP分别是在InputMethodManagerService服务和WallpaperManagerService服务中创建的,用来描述一个输入法窗口和一个壁纸窗口。
图1中的对象的对应关系如下所示:
1. ActivityRecord-J对应于AppWindowToken-J,后者描述的一组窗口是{WindowState-A, WindowState-B, WindowState-B-1},其中, WindowState-B-1是WindowState-B的子窗口。
2. ActivityRecord-K对应于AppWindowToken-K,后者描述的一组窗口是{WindowState-C, WindowState-C-1, WindowState-D, WindowState-D-1},其中, WindowState-C-1是WindowState-C的子窗口,WindowState-D-1是WindowState-D的子窗口。
3. ActivityRecord-N对应于AppWindowToken-N,后者描述的一组窗口是{WindowState-E},其中, WindowState-E是系统当前激活的Activity窗口。
4. Binder for IM对应于WindowToken-I,后者描述的一组窗口是{WindowState-I},其中, WindowState-I是WindowState-E的输入法窗口。
5. Binder for WP对应于WindowToken-W,后者描述的一组窗口是{WindowState-W},其中, WindowState-W是WindowState-E的壁纸窗口。
从图1还可以知道,Window Stack中的WindowState是按照它们所描述的窗口的Z轴位置从低到高排列的。
以上就是WindowManagerService服务组织系统中的窗口的抽象模型,接下来我们将分析AppWindowToken、WindowToken和WindowState的一些增加、移动和删除等操作,以便可以对这个抽象模型有一个更深刻的认识。
1. 增加AppWindowToken
从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一文可以知道,一个Activity组件在启动的过程中,ActivityManagerService服务会调用调用WindowManagerService类的成员函数addAppToken来为它增加一个AppWindowToken,如下所示:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
/**
* Mapping from a token IBinder to a WindowToken object.
*/
final HashMap mTokenMap =
new HashMap();
/**
* The same tokens as mTokenMap, stored in a list for efficient iteration
* over them.
*/
final ArrayList mTokenList = new ArrayList();
......
/**
* Z-ordered (bottom-most first) list of all application tokens, for
* controlling the ordering of windows in different applications. This
* contains WindowToken objects.
*/
final ArrayList mAppTokens = new ArrayList();
......
public void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen) {
......
synchronized(mWindowMap) {
AppWindowToken wtoken = findAppWindowToken(token.asBinder());
if (wtoken != null) {
......
return;
}
wtoken = new AppWindowToken(token);
......
mAppTokens.add(addPos, wtoken);
......
mTokenMap.put(token.asBinder(), wtoken);
mTokenList.add(wtoken);
......
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类有三个成员变量mTokenMap、mTokenList和mAppTokens,它们都是用来描述系统中的窗口的。
成员变量mTokenMap指向的是一个HashMap,它里面保存的是一系列的WindowToken对象,每一个WindowToken对象都是用来描述一个窗口的,并且是以描述这些窗口的一个Binder对象的IBinder接口为键值的。例如,对于Activity组件类型的窗口来说,它们分别是以用来描述它们的一个ActivityRecord对象的IBinder接口保存在成员变量mTokenMap所指向的一个HashMap中的。
成员变量mTokenList指向的是一个ArrayList,它里面保存的也是一系列WindowToken对象,这些WindowToken对象与保存在成员变量mTokenMap所指向的一个HashMap中的WindowToken对象是一样的。成员变量mTokenMap和成员变量mTokenList的区别就在于,前者在给定一个IBinder接口的情况下,可以迅速指出是否存在一个对应的WindowToken对象,而后者可以迅速遍历WindowManagerService服务中的WindowToken对象。
成员变量mAppTokens指向的也是一个ArrayList,不过它里面保存的是一系列AppWindowToken对象,每一个AppWindowToken对象都是用来描述一个Activity组件窗口的,而这些AppWindowToken对象是以它们描述的窗口的Z轴坐标由小到大保存在这个ArrayList中的,这样我们就可以通过这个ArrayList来从上到下或者从下到上地遍历系统中的所有Activity组件窗口。由于这些AppWindowToken对象所描述的Activity组件窗口也是一个窗口,并且AppWindowToken类是从WindowToken继承下来的,因此,这些AppWindowToken对象还会同时被保存在成员变量mTokenMap所指向的一个HashMap和成员变量mTokenList所指向的一个ArrayList中。
理解了WindowManagerService类的这三个成员变量的含义之后,它的成员函数addAppToken的实现就好理解了,其中,参数token指向的便是用来描述正在启动的Activity组件所对应的一个ActivityRecord对象,而参数addPos用来描述该Activity组件在堆栈中的位置,这个位置同时也是接下来要创建的AppWindowToken对象在WindowManagerService类的mTokenList所描述的一个ArrayList中的位置。
WindowManagerService类的成员函数addAppToken首先调用另外一个成员函数findAppWindowToken来在成员变量mTokenMap所描述的一个HashMap检查是否已经存在一个AppWindowToken。如果已经存在的话,那么WindowManagerService类的成员函数addAppToken就什么也不做就返回了,否则的话,就会使用参数token来创建一个AppWindowToken对象,并且会将该AppWindowToken对象分别保存在WindowManagerService类的成员变量mTokenMap、mTokenList和mAppTokens中。
2. 删除AppWindowToken
删除AppWindowToken是通过调用WindowManagerService类的成员函数removeAppTokensLocked来实现的,如下所示:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
private void removeAppTokensLocked(List tokens) {
// XXX This should be done more efficiently!
// (take advantage of the fact that both lists should be
// ordered in the same way.)
int N = tokens.size();
for (int i=0; i= mAppTokens.size()) {
int i = NW;
while (i > 0) {
i--;
WindowState win = mWindows.get(i);
if (win.getAppToken() != null) {
return i+1;
}
}
}
while (tokenPos > 0) {
// Find the first app token below the new position that has
// a window displayed.
final AppWindowToken wtoken = mAppTokens.get(tokenPos-1);
......
if (wtoken.sendingToBottom) {
......
tokenPos--;
continue;
}
int i = wtoken.windows.size();
while (i > 0) {
i--;
WindowState win = wtoken.windows.get(i);
int j = win.mChildWindows.size();
while (j > 0) {
j--;
WindowState cwin = win.mChildWindows.get(j);
if (cwin.mSubLayer >= 0) {
for (int pos=NW-1; pos>=0; pos--) {
if (mWindows.get(pos) == cwin) {
......
return pos+1;
}
}
}
}
for (int pos=NW-1; pos>=0; pos--) {
if (mWindows.get(pos) == win) {
......
return pos+1;
}
}
}
tokenPos--;
}
return 0;
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
参数tokenPos描述的是一个AppWindowToken对象在WindowManagerService类的成员变量mAppTokens所描述的一个ArrayList的位置,WindowManagerService类的成员函数findWindowOffsetLocked的目标就要找到与该AppWindowToken对象所对应的WindowState对象在WindowManagerService服务内部的一个WindowState堆栈的起始偏移位置。有了这个起始偏移位置之后,我们就可以将对应的所有WindowState对象有序地插入到该WindowState堆栈中去。WindowManagerService服务内部的WindowState堆栈是通过WindowManagerService类的成员变量mWindows来描述的。接下来我们就分两种情况来分析这个起始偏移位置的计算过程。
第一种情况是参数tokenPos的值大于WindowManagerService类的成员变量mAppTokens所描述的一个ArrayList的大小。这是一种异常情况,一般来说,参数tokenPos是指向mAppTokens列表的某一个位置的,不过这时候意味着它所描述的AppWindowToken对象的Z轴位置要大于mAppTokens列表的最上面的一个AppWindowToken对象的Z轴位置的。这也就是说,与参数tokenPos所描述的AppWindowToken对象所对应的WindowState对象的要位于与mAppTokens列表的最上面的一个AppWindowToken对象所对应的任一个WindoState对象的上面。因此,就需要找到与mAppTokens列表的最上面的一个AppWindowToken对象所对应的Z轴位置最大的一个WindoState对象在WindowState堆栈中的位置i,然后就可以知道与参数tokenPos所描述的AppWindowToken对象所对应的WindowState对象在WindowState堆栈的起始偏移位置为i+1。
如何找到mAppTokens列表的最上面的一个AppWindowToken对象所对应的Z轴位置最大的一个WindoState对象在WindowState堆栈中的位置i呢?从图1可以可得到一个结论:WindowManagerService服务内部中的所有WindowState对象都是按照Z轴从位置从小到大排列在WindowState堆栈中的,并且在mAppTokens列表中,位于上面的一个AppWindowToken对象所对应的那些WindowState对象的Z轴位置是一定大于位于下面的一个AppWindowToken对象所对应的那些WindowState对象的Z轴位置的。因此,我们只要从WindowState堆栈的顶端开始往下遍历,找到这样的一个WindowState对象,它是属于一个AppWindowToken对象的,即它的成员函数getAppToken的返回值不等于null,那么它在WindowState堆栈中的位置就是我们要找到的位置i。有了这个位置i之后,将它的值加上1,就可以得到参数t所描述的AppWindowToken对象所对应的WindowState对象在WindowState堆栈的起始偏移位置了。
第二种情况是参数tokenPos的值小于WindowManagerService类的成员变量mAppTokens所描述的一个ArrayList的大小。根据前面得到的推论,我们只要在mAppTokens列表中找到一个AppWindowToken对象,它满足以下三个条件:
A. 它在mAppTokens列表中的位置小于tokenPos;
B. 它在WindowState堆栈中对应有WindowState对象;
C. 它不是将要置于WindowState堆栈的底部。
如果一个AppWindowToken对象在WindowState堆栈中对应有WindowState对象,那么这些WindowState对象也会同时按照Z轴从小到大的顺序保存它的成员变量windows所描述的一个ArrayList中,这意味着如果一个AppWindowToken对象满足条件B,那么它的成员变量windows所描述的一个ArrayList的大小就大于0。
如果一个AppWindowToken对象不是将要置于WindowState堆栈的底部,那么它的成员变量sendingToBottom的值就不等于true,这也意味这个AppWindowToken对象满足条件C。
如果能找到满足上述条件的一个AppWindowToken对象wtoken,那么我们只要找到与它所对应的Z轴位置最大的WindowState对象在WindowManagerService服务内部的WindowState堆栈中的位置i,那么将它的值加1,就可以得到与参数tokenPos所描述的AppWindowToken对象所对应的WindowState对象在WindowState堆栈的起始偏移位置了。
那么如何找到与这个AppWindowToken对象wtoken对应的Z轴位置最大的WindowState对象在WindowManagerService服务内部的WindowState堆栈中的位置i呢?从前面的图1可以知道,一个AppWindowToken对象所对应的WindowState对象可以划分为两种类型:第一种类型是父窗口类型的;第二种是子窗口类型的。如果一个WindowState对象所描述的窗口是父窗口,那么它的子窗口就保存在它的成员变量mChildWindows所描述的一个ArrayList中,并且这些子窗口是按照Z轴位置从小到大的顺序排列的,同时,该WindowState对象也会保存在与它所对应的一个AppWindowToken对象的成员变量windows所描述的一个ArrayList中。
有了上述结论,并且假设存在一个能够满足上述三个条件的AppWindowToken对象wtoken,那么就可以从上到下遍历保存在它的成员变量windows所描述的一个ArrayList中的每一个WindowState对象win:
I. 如果WindowState对象win所描述的一个窗口具有子窗口,那么就继续从上到下遍历这些子窗口,即从上到下遍历WindowState对象win的成员变量mChildWindows所描述的一个ArrayList。如果能找到一个WindowState对象cwin,它的成员变量mSubLayer的值大于等于0,那么该WindowState对象cwin在WindowManagerService服务内部的WindowState堆栈中的位置就是我们要得到的位置i。注意,如果WindowState对象cwin的成员变量mSubLayer的值小于0,那么它虽然是一个子窗口,但是它却是位于父窗口的后面的,即它的Z轴位置是小于父窗口的Z轴位置的。
II. 如果WindowState对象win所描述的一个窗口不具有子窗口,即它的成员变量mChildWindows所描述的一个ArrayList的大小等于0,那么它在WindowManagerService服务内部的WindowState堆栈中的位置就是我们要得到的位置i。
得到了位置i之后,将它的值加1,那么就可以得到与参数tokenPos所描述的AppWindowToken对象所对应的WindowState对象在WindowState堆栈的起始偏移位置了。
回到WindowManagerService类的成员函数moveAppToken中,调整好参数token所描述的AppWindowToken对象所对应的WindowState对象在WindowState堆栈中的位置之后,即调用了成员函数reAddAppWindowsLocked之后,这时候系统中的窗口的布局就会发生了变化,即系统中的窗口的Z轴位置关系发生了变化,那么接下来就需要调用成员函数updateFocusedWindowLocked来重新计算系统中的窗口的Z轴位置,并且调用成员函数performLayoutAndPlaceSurfacesLocked来重新布局系统中的窗口。
4. 移动AppWindowToken至顶端
移动AppWindowToken至顶端是通过调用WindowManagerService类的成员函数moveAppTokensToTop来实现的,如下所示:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
public void moveAppTokensToTop(List tokens) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"moveAppTokensToTop()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
final long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
removeAppTokensLocked(tokens);
final int N = tokens.size();
for (int i=0; i 0) {
pos = t.windows.get(0);
}
}
// We now know the index into the apps. If we found
// an app window above, that gives us the position; else
// we need to look some more.
if (pos != null) {
// Move behind any windows attached to this one.
WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
if (atoken != null) {
final int NC = atoken.windows.size();
if (NC > 0) {
WindowState bottom = atoken.windows.get(0);
if (bottom.mSubLayer < 0) {
pos = bottom;
}
}
}
placeWindowBefore(pos, win);
} else {
// Continue looking down until we find the first
// token that has windows.
while (i >= 0) {
AppWindowToken t = mAppTokens.get(i);
final int NW = t.windows.size();
if (NW > 0) {
pos = t.windows.get(NW-1);
break;
}
i--;
}
if (pos != null) {
// Move in front of any windows attached to this
// one.
WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
if (atoken != null) {
final int NC = atoken.windows.size();
if (NC > 0) {
WindowState top = atoken.windows.get(NC-1);
if (top.mSubLayer >= 0) {
pos = top;
}
}
}
placeWindowAfter(pos, win);
placeWindowAfter(pos, win);
} else {
// Just search for the start of this layer.
final int myLayer = win.mBaseLayer;
for (i=0; i myLayer) {
break;
}
}
......
localmWindows.add(i, win);
mWindowsChanged = true;
}
}
这段代码要能冠军WindowManagerService服务内部的一个AppWindowToken列表mAppTokens来在窗口堆栈中找到一个参数位置来保存参数win所描述的一个WindowState对象。
最上面的一个for循环执行完成之后,我们假设变量pos的值不等于null,这时候它与变量i以及变量token的关系如图2所示:
图2 窗口win位于窗口C的下面
这时候位于令牌token上面的令牌在窗口堆栈中对应有WindowState对象。注意,这时候第i+2个令牌在窗口堆栈中不对应有WindowState对象,而第i+3个令牌在窗口堆栈中对应有C和D两个WindowState对象,并且这两个WindowState对象所描述的窗口都不是即将要切换到窗口堆栈的底部的。由于第i+3个令牌位于令牌token的上面,并且这两个令牌之间的其它令牌在窗口堆栈中不对应有WindowState对象,因此,这时候参数win所描述的WindowState对象在窗口堆栈中的位置应该以第i+3个令牌所对应的Z轴位置最小的WindowState对象在窗口堆栈中的位置为参考,即以WindowState对象C在窗口堆栈中的位置为参考,而WindowState对象C也正好是变量pos所指向的WindowState对象。
接下来,上述代码会继续检查WindowState对象C是否附加有SubLayer值小于0的窗口。如果有的话,那么就会将变量pos指向SubLayer值最小的那个WindowState对象,这是因为该WindowState对象是在WindowState对象C的最下面的,并且它与WindowState对象C是同属一个令牌的。最后,上述代码就会调用WindowManagerService类的成员函数placeWindowBefore来将参数win所描述的一个WindowState对象保存窗口堆栈中由变量pos所指向的那个WindowState对象的下面。
假设最上面的一个for循环执行完成之后,变量pos的值等于null,那么就说明位于令牌token上面的令牌在窗口堆栈中都没有对应有WindowState对象,或者说它们所对应的WindowState对象都是即将要切换到窗口堆栈的底部去的,这时候就需要通过位于令牌token上面的令牌来在窗口堆栈中找到一个参考位置来保存参数win所描述的WindowState对象,这是通过中间的while循环来实现的。
中间的while循环执行完成之后,假设变量pos的值不等于null,这时候它与变量i以及变量token的关系如图3所示:
图3 窗口win位于窗口D的上面
这时候位于令牌token上面的令牌在窗口堆栈中没有对应有WindowState对象。注意,这时候第i-1个令牌在窗口堆栈中不对应有WindowState对象,而第i-2个令牌在窗口堆栈中对应有C和D两个WindowState对象。由于第i-2个令牌位于令牌token的下面,并且这两个令牌之间的其它令牌在窗口堆栈中不对应有WindowState对象,因此,这时候参数win所描述的WindowState对象在窗口堆栈中的位置应该以第i-2个令牌所对应的Z轴位置最大的WindowState对象在窗口堆栈中的位置为参考,即以WindowState对象D在窗口堆栈中的位置为参考,而WindowState对象D也正好是变量pos所指向的WindowState对象。
接下来,上述代码会继续检查WindowState对象D是否附加有SubLayer值大于等于0的窗口。如果有的话,那么就会将变量pos指向SubLayer值最大的那个WindowState对象,这是因为该WindowState对象是在WindowState对象D的最上面的,并且它与WindowState对象D是同属一个令牌的。最后,上述代码就会调用WindowManagerService类的成员函数placeWindowAfter来将参数win所描述的一个WindowState对象保存窗口堆栈中由变量pos所指向的那个WindowState对象的上面。
假设中间的while循环执行完成之后,变量pos的值等于null,这时候就说明在窗口堆栈中实在是找不到参考位置来保存参数win所描述的WindowState对象了,因此,就只能通过参数win所描述的WindowState对象的Z轴位置,即它的成员变量mBaseLayer的值来在窗口堆栈中找到一个合适的位置了,如最下面的for循环所示。由于窗口堆栈中的WindowState对象是按照它们的Z轴位置由小到大的顺序来排列的,因此,最下面的for循环只要从下到上找到一个Z轴位置比参数win所描述的WindowState对象的Z轴位置大的一个WindowState对象在窗口堆栈中的位置i,那么就可以将参数win所描述的WindowState对象插入在窗口堆栈的第i个位置上了。
CASE 1.2对应的代码为:
// Figure out where window should go, based on layer.
final int myLayer = win.mBaseLayer;
for (i=N-1; i>=0; i--) {
if (localmWindows.get(i).mBaseLayer = 0) {
......
mWindows.remove(wpos);
mWindowsChanged = true;
reAddWindowLocked(wpos, win);
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
为了得到参数win所描述的WindowState对象的子WindowState对象在窗口堆栈中的起始位置,WindowManagerService类的成员函数reAddWindowToListInOrderLocked首先将参数win所描述的WindowState对象增加到窗口堆栈中,这是通过调用前面所分析的成员函数addWindowToListInOrderLocked来实现的,目的是为了获得它在窗口堆栈的位置。有了这个位置之后,WindowManagerService类的成员函数reAddWindowToListInOrderLocked就可以调用前面所分析的成员函数reAddWindowLocked来将WindowState对象及其所有子WindowState对象增加到窗口堆栈中去了,不过在调用之前,要先将参数win所描述的WindowState对象从窗口中堆栈删除。
13. 将一个WindowToken对象对应的所有WindowState对象及其子WindowState对象增加到窗口堆栈的指定位置上
将一个WindowToken对象对应的所有WindowState对象都增加到窗口堆栈中是通过调用WindowManagerService类的成员函数reAddAppWindowsLocked来实现的,如下所示:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
private final int reAddAppWindowsLocked(int index, WindowToken token) {
final int NW = token.windows.size();
for (int i=0; i
您可能感兴趣的文章:
本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。