WindowManagerService服务是如何以堆栈的形式来组织窗口
我们知道,在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<ibinder, windowtoken> mtokenmap =
new hashmap<ibinder, windowtoken>();
/**
* the same tokens as mtokenmap, stored in a list for efficient iteration
* over them.
*/
final arraylist<windowtoken> mtokenlist = new arraylist<windowtoken>();
......
/**
* 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<appwindowtoken> mapptokens = new arraylist<appwindowtoken>();
......
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<ibinder> 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<n; i++) {
ibinder token = tokens.get(i);
final appwindowtoken wtoken = findappwindowtoken(token);
if (!mapptokens.remove(wtoken)) {
......
i--;
n--;
}
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
windowmanagerservice类的成员函数removeapptokenslocked可以同时删除一组appwindowtoken对象。
参数tokens所描述的是一个ibinder接口列表,与这些ibinder接口所对应的appwindowtoken对象就是接下来要删除的。windowmanagerservice类的成员函数removeapptokenslocked通过一个for循环来依次调用另外一个成员函数findappwindowtoken,以便可以找到保存在列表tokens中的每一个ibinder接口所对应的appwindowtoken对象,然后将该appwindowtoken对象从windowmanagerservice类的成员变量mapptokens所描述的一个arraylist中删除。
注意,windowmanagerservice类的成员函数removeapptokenslocked是在内部使用的,它只是把一个appwindowtoken对象从成员变量mapptokens中删除,而没有从另外两个成员变量mtokenmap和mtokenlist中删除。
3. 移动appwindowtoken至指定位置
移动appwindowtoken至指定位置是通过调用windowmanagerservice类的成员函数moveapptoken来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
public void moveapptoken(int index, ibinder token) {
if (!checkcallingpermission(android.manifest.permission.manage_app_tokens,
"moveapptoken()")) {
throw new securityexception("requires manage_app_tokens permission");
}
synchronized(mwindowmap) {
......
final appwindowtoken wtoken = findappwindowtoken(token);
if (wtoken == null || !mapptokens.remove(wtoken)) {
......
return;
}
mapptokens.add(index, wtoken);
......
final long origid = binder.clearcallingidentity();
......
if (tmpremoveappwindowslocked(wtoken)) {
......
readdappwindowslocked(findwindowoffsetlocked(index), wtoken);
......
updatefocusedwindowlocked(update_focus_will_place_surfaces);
mlayoutneeded = true;
performlayoutandplacesurfaceslocked();
}
binder.restorecallingidentity(origid);
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
参数token描述的是要移动的appwindowtoken对象所对应的一个ibinder接口,而参数index描述的是该appwindowtoken对象要移动到的位置。注意,移动一个appwindowtoken对象到指定的位置是需要android.manifest.permission.manage_app_tokens权限的。
windowmanagerservice类的成员函数moveapptoken首先找到与参数token所对应的appwindowtoken对象,并且将该appwindowtoken对象从windowmanagerservice类的成员变量mapptokens所描述的一个arraylist中移除,这样做的目的是为了接下来可以将该appwindowtoken对象移动至该arraylist中的指定位置上,即参数index所描述的位置上。
注意,上述操作只是将参数token所对应的appwindowtoken对象移动到了windowmanagerservice类的成员变量mapptokens所描述的一个arraylist的指定位置上,接下来还需要同时将与该appwindowtoken对象所对应的windowstate对象移动至windowmanagerservice服务内部的一个windowstate堆栈合适位置上去。
移动对应的windowstate对象的操作同样也是分两步执行的:第一步先调用windowmanagerservice类的成员函数tmpremoveappwindowslocked来将这些windowstate对象从原来的windowstate堆栈位置移除;第二步再调用windowmanagerservice类的成员函数readdappwindowslocked来将这些windowstate对象插入到windowstate堆栈的合适位置去。
对应的windowstate对象被移动到的合适位置是通过调用windowmanagerservice类的成员函数findwindowoffsetlocked来获得的,它的实现如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
/**
* z-ordered (bottom-most first) list of all window objects.
*/
final arraylist<windowstate> mwindows = new arraylist<windowstate>();
......
private int findwindowoffsetlocked(int tokenpos) {
final int nw = mwindows.size();
if (tokenpos >= 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<ibinder> 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<n; i++) {
appwindowtoken wt = findappwindowtoken(tokens.get(i));
if (wt != null) {
mapptokens.add(wt);
if (mnextapptransition != windowmanagerpolicy.transit_unset) {
mtotopapps.remove(wt);
mtobottomapps.remove(wt);
mtotopapps.add(wt);
wt.sendingtobottom = false;
wt.sendingtotop = true;
}
}
}
if (mnextapptransition == windowmanagerpolicy.transit_unset) {
moveappwindowslocked(tokens, mapptokens.size());
}
}
binder.restorecallingidentity(origid);
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
windowmanagerservice类的成员函数moveapptokenstotop可以同时将一组appwindowtoken移至顶端,同时需要调用者具有android.manifest.permission.manage_app_tokens权限。
参数tokens所描述的是一个ibinder接口列表,与这些ibinder接口所对应的appwindowtoken对象就是接下来要移至顶端的。在将保存在参数tokens中的ibinder接口所对应的appwindowtoken对象移至顶端之前,windowmanagerservice类的成员函数首先会调用前面所描述的成员函数removeapptokenslocked来删除这些appwindowtoken对象,然后再依次将它们添加到windowmanagerservice类的成员变量mapptokens所描述的一个arraylist的末尾去。
注意,windowmanagerservice类的成员变量mnextapptransition用来描述系统当前是否正在切换activity窗口。如果是的话,那么它的值就不等于windowmanagerpolicy.transit_unset,这时候就需要:
a. 将所有要移至顶端的appwindowtoken对象都保存在windowmanagerservice类的另外一个成员变量mtotopapps所描述的一个arraylist中去,并且将这些appwindowtoken对象的成员变量sendingtotop的值设置为true。
b. 将所有要移至顶端的appwindowtoken对象所对应windowstate对象都移至windowmanagerservice服务内部的一个windowstate堆栈的顶端去,这是通过调用另外一个成员函数moveappwindowslocked来实现的。
执行完成上述两个操作之后,与要移至顶端的appwindowtoken对象所对应的窗口就会位于窗口堆栈的最上面了。
5. 移动appwindowtoken至底端
移动appwindowtoken至顶端是通过调用windowmanagerservice类的成员函数moveapptokenstobottom来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
public void moveapptokenstobottom(list<ibinder> tokens) {
if (!checkcallingpermission(android.manifest.permission.manage_app_tokens,
"moveapptokenstobottom()")) {
throw new securityexception("requires manage_app_tokens permission");
}
final long origid = binder.clearcallingidentity();
synchronized(mwindowmap) {
removeapptokenslocked(tokens);
final int n = tokens.size();
int pos = 0;
for (int i=0; i<n; i++) {
appwindowtoken wt = findappwindowtoken(tokens.get(i));
if (wt != null) {
mapptokens.add(pos, wt);
if (mnextapptransition != windowmanagerpolicy.transit_unset) {
mtotopapps.remove(wt);
mtobottomapps.remove(wt);
mtobottomapps.add(i, wt);
wt.sendingtotop = false;
wt.sendingtobottom = true;
}
pos++;
}
}
if (mnextapptransition == windowmanagerpolicy.transit_unset) {
moveappwindowslocked(tokens, 0);
}
}
binder.restorecallingidentity(origid);
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
windowmanagerservice类的成员函数moveapptokenstobottom可以同时将一组appwindowtoken移至底端。将一组appwindowtoken移至底端与将一组appwindowtoken移至顶端的实现是类似的,只不过是移动的方向相反而已。因此,windowmanagerservice类的成员函数moveapptokenstobottom的实现可以参考前面所分析的成员函数moveapptokenstotop的实现,这里不再详述。
6. 增加windowtoken
从图1可以知道,如果一个windowstate对象不是与一个appwindowtoken对象对应的,那么它就必须要与一个windowtoken对象对应。例如,用来描述输入法窗口和壁纸窗口的windowstate对象对应的就是windowtoken对象,而不是appwindowtoken对象,因为它们不是activity类型的窗口。
输入法窗口和壁纸窗口分别是由输入法管理服务inputmethodmanagerservice和壁纸管理服务wallpapermanagerservice调用windowmanagerservice类的成员函数addwindowtoken来增加对应的windowtoken对象的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
public void addwindowtoken(ibinder token, int type) {
if (!checkcallingpermission(android.manifest.permission.manage_app_tokens,
"addwindowtoken()")) {
throw new securityexception("requires manage_app_tokens permission");
}
synchronized(mwindowmap) {
windowtoken wtoken = mtokenmap.get(token);
if (wtoken != null) {
slog.w(tag, "attempted to add existing input method token: " + token);
return;
}
wtoken = new windowtoken(token, type, true);
mtokenmap.put(token, wtoken);
mtokenlist.add(wtoken);
if (type == type_wallpaper) {
mwallpapertokens.add(wtoken);
}
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
调用windowmanagerservice类的成员函数addwindowtoken需要具有android.manifest.permission.manage_app_tokens权限。
对于输入法窗口和壁纸窗口来说,参数token指向的是与它们所关联的一个binder对象的ibinder接口,而参数type描述的是要在windowmanagerservice服务内部增加windowtoken对象的窗口的类型。
windowmanagerservice类的成员函数addwindowtoken首先检查在成员变量mtokenmap所描述的一个hashmap检查是否已经存在一个windowtoken对象与参数token对应。如果已经存在的话,那么windowmanagerservice类的成员函数addwindowtoken就什么也不做就返回了,否则的话,就会使用参数token来创建一个windowtoken对象,并且会将该windowtoken对象分别保存在windowmanagerservice类的成员变量mtokenmap和mtokenlist中。
这里有两个地方需要注意:
a. 由于这里增加的是windowtoken对象,而不是appwindowtoken对象,因此,与增加appwindowtoken不同,这里不需要将新创建的windowtoken对象保存在windowmanagerservice类的成员变量mapptokens中。
b. 如果参数type的值等于type_wallpaper,那么就意味着新创建的windowtoken对象是用来描述壁纸窗口的,这时候还需要将新创建的windowtoken对象保存在windowmanagerservice类的成员变量mwallpapertokens所描述的一个arraylist中,以方便管理壁纸窗口。
对于非输入法窗口、非壁纸窗口以及非activity窗口来说,它们所对应的windowtoken对象是在它们增加到windowmanagerservice服务的时候创建的。从前面android应用程序窗口(activity)与windowmanagerservice服务的连接过程分析一文可以知道,增加一个窗口windowmanagerservice服务最终是通过调用windowmanagerservice类的成员函数addwindow来实现的,接下来我们就主要分析与创建windowtoken相关的逻辑,如下所示:
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) {
......
boolean addtoken = false;
windowtoken token = mtokenmap.get(attrs.token);
if (token == null) {
if (attrs.type >= first_application_window
&& attrs.type <= last_application_window) {
......
return windowmanagerimpl.add_bad_app_token;
}
if (attrs.type == type_input_method) {
......
return windowmanagerimpl.add_bad_app_token;
}
if (attrs.type == type_wallpaper) {
......
return windowmanagerimpl.add_bad_app_token;
}
token = new windowtoken(attrs.token, -1, false);
addtoken = true;
}
......
if (addtoken) {
mtokenmap.put(attrs.token, token);
mtokenlist.add(token);
}
......
}
......
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
如果参数attrs所描述的一个windowmanager.layoutparams对象的成员变量token所指向的一个ibinder接口在windowmanagerservice类的成员变量mtokenmap所描述的一个hashmap中没有一个对应的windowtoken对象,并且该windowmanager.layoutparams对象的成员变量type的值不等于type_input_method、type_wallpaper,以及不在first_application_window和last_application_window,那么就意味着这时候要增加的窗口就既不是输入法窗口,也不是壁纸窗口和activity窗口,因此,就需要以参数attrs所描述的一个windowmanager.layoutparams对象的成员变量token所指向的一个ibinder接口为参数来创建一个windowtoken对象,并且将该windowtoken对象保存在windowmanagerservice类的成员变量mtokenmap和mtokenlist中。
7. 删除windowtoken
删除windowtoken是通过调用windowmanagerservice类的成员函数removewindowtoken来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
public void removewindowtoken(ibinder token) {
if (!checkcallingpermission(android.manifest.permission.manage_app_tokens,
"removewindowtoken()")) {
throw new securityexception("requires manage_app_tokens permission");
}
final long origid = binder.clearcallingidentity();
synchronized(mwindowmap) {
windowtoken wtoken = mtokenmap.remove(token);
mtokenlist.remove(wtoken);
if (wtoken != null) {
boolean delayed = false;
if (!wtoken.hidden) {
wtoken.hidden = true;
final int n = wtoken.windows.size();
boolean changed = false;
for (int i=0; i<n; i++) {
windowstate win = wtoken.windows.get(i);
if (win.isanimating()) {
delayed = true;
}
if (win.isvisiblenow()) {
applyanimationlocked(win,
windowmanagerpolicy.transit_exit, false);
changed = true;
}
}
if (changed) {
mlayoutneeded = true;
performlayoutandplacesurfaceslocked();
updatefocusedwindowlocked(update_focus_normal);
}
if (delayed) {
mexitingtokens.add(wtoken);
} else if (wtoken.windowtype == type_wallpaper) {
mwallpapertokens.remove(wtoken);
}
}
......
} else {
slog.w(tag, "attempted to remove non-existing token: " + token);
}
}
binder.restorecallingidentity(origid);
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
调用windowmanagerservice类的成员函数removewindowtoken需要具有android.manifest.permission.manage_app_tokens权限。
windowmanagerservice类的成员函数removewindowtoken首先找到与参数token所描述的binder接口所对应的windowtoken对象,接着再将该windowtoken对象从windowmanagerservice类的成员变量mtokenmap和mtokenlist中删除。
删除了一个windowtoken对象之后,如果该windowtoken对象不是处于不可见的状态,即它的成员变量hidden的值不等于false,那么就意味着它所描述窗口口也有可能是可见的,那么windowmanagerservice类的成员函数removewindowtoken就需要作以下两个检查:
a. 如果该windowtoken对象所描述的窗口的其中一个处于动画显示过程,即用来描述该窗口的一个windowstate对象的成员函数isanimating的返回值等于true,那么就需要该windowtoken对象的状态设置为正在退出状态,即将它保存在windowmanagerservice类的成员变量mexitingtokens所描述的一个arraylist中。
b. 如果该windowtoken对象所描述的窗口是可见的,即用来描述该窗口的一个windowstate对象的成员函数isvisiblenow的返回值等于true,那么就需要调用windowmanagerservice类的成员函数applyanimationlocked来给它应用一个退出动画,该退出动画是通过调用windowmanagerservice类的成员函数performlayoutandplacesurfaceslocked来实现的。当一个窗口退出了之后,系统当前获得焦点的窗口可能会发生变化,这时候就需要调用windowmanagerservice类的成员函数updatefocusedwindowlocked来重新调整系统当前获得焦点的窗口。
注意,如果正在删除的windowtoken对象是用来描述壁纸窗口的,那么还需要将该windowtoken对象从windowmanagerservice类的成员变量mwallpapertokens所描述的一个arraylist中删除。
8. 增加windowstate
从前面android应用程序窗口(activity)与windowmanagerservice服务的连接过程分析一文可以知道,增加一个窗口windowmanagerservice服务最终是通过调用windowmanagerservice类的成员函数addwindow来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
/**
* mapping from an iwindow ibinder to the server's window object.
* this is also used as the lock for all of our state.
*/
final hashmap<ibinder, windowstate> mwindowmap = new hashmap<ibinder, windowstate>();
......
/**
* z-ordered (bottom-most first) list of all window objects.
*/
final arraylist<windowstate> mwindows = new arraylist<windowstate>();
......
public int addwindow(session session, iwindow client,
windowmanager.layoutparams attrs, int viewvisibility,
rect outcontentinsets, inputchannel outinputchannel) {
......
windowstate win = null;
synchronized(mwindowmap) {
......
win = new windowstate(session, client, token,
attachedwindow, attrs, viewvisibility);
......
mwindowmap.put(client.asbinder(), win);
......
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();
}
}
......
}
......
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
windowmanagerservice类有两个成员变量mwindowmap和mwindows是用来保存系统中的windowstate对象。其中,成员变量mwindowmap指向的是一个hashmap,它的关键字是一个ibinder接口,一般这个ibinder接口指向的是一个binder代理对象,引用了运行在应用程序进程这一侧的一个类型为w的binder本地对象,用来描述一个窗口;成员变量mwindows指向的是一个arraylist,保存在它里面的windowstate对象是按照其z轴位置从小到大的顺序排列的。成员变量mwindowmap和mwindows的区别在于,前者给在定一个ibinder接口的情况下,可以快速找到与对应的windowstate对象,而后者用来从上到下或者下到上遍历系统的windowstate对象。由于系统中的windowstate对象是按照其z轴位置从小到大的顺序排列在成员变量mwindows中的,因此,成员变量mwindows所指向的arraylist就是我们在前面图1中所说的window stack。
理解了windowmanagerservice类有两个成员变量mwindowmap和mwindows的作用之后,windowmanagerservice类的成员函数addwindow增加一个windowstate对象的过程就容易理解了。
参数client是一个binder代理对象,引用了运行在应用程序进程这一侧的一个类型为w的binder本地对象,用来描述要增加到windowmanagerservice服务中的一个窗口。windowmanagerservice类的成员函数addwindow首先创建一个windowstate对象win,接着再以参数client所描述的一个binder代理对象的ibinder接口为关键字,将windowstate对象win保存在windowmanagerservice类的成员变量mwindowmap中,最后还会根据要增加到windowmanagerservice服务中的窗口的类型来调用不同的成员函数将windowstate对象win增加到windowmanagerservice类的成员变量mwindows中:
a. 如果要增加的是输入法窗口,即参数attrs所描述的一个windowmanager.layoutparams对象的成员变量type的值等于type_input_method,那么就会调用成员函数addinputmethodwindowtolistlocked来将windowstate对象win增加到windowmanagerservice类的成员变量mwindows中去,并且会将windowstate对象win保存在windowmanagerservice类的成员变量minputmethodwindow中。
b. 如果要增加的是输入法对话框,即参数attrs所描述的一个windowmanager.layoutparams对象的成员变量type的值等于type_input_method_dialog,那么就会调用成员函数addwindowtolistinorderlocked来将windowstate对象win增加到windowmanagerservice类的成员变量mwindows中去,并且会将windowstate对象win保存在windowmanagerservice类的成员变量minputmethoddialogs中,以及调用成员函数adjustinputmethoddialogslocked来调整刚才所添加的输入法窗口在窗口堆栈中的位置,使得它位于系统当前需要输入法窗口的窗口的上面。
c. 如果要增加的是壁纸窗口,即参数attrs所描述的一个windowmanager.layoutparams对象的成员变量type的值等于type_wallpaper,那么就会调用成员函数addwindowtolistinorderlocked来将windowstate对象win增加到windowmanagerservice类的成员变量mwindows中去,并且会调用成员函数adjustwallpaperwindowslocked来调整刚才所添加的壁纸窗口在窗口堆栈中的位置,使得它位于系统当前需要壁纸窗口的窗口的下面。
d . 如果要增加的既不是输入法窗口,也不是输入法对话框和壁纸窗口,那么就只会调用成员函数addwindowtolistinorderlocked来将windowstate对象win增加到windowmanagerservice类的成员变量mwindows中去,但是如果要增加的窗口需要显示壁纸,即参数attrs所描述的一个windowmanager.layoutparams对象的成员变量flags的flag_show_wallpaper位等于1,那么还会继续调用成员函数adjustwallpaperwindowslocked来调整系统中的壁纸窗口在窗口堆栈中的位置,使得它位于刚才所添加的窗口的下面。
在后面的两篇文章中,我们再详细分析windowmanagerservice类的成员函数addinputmethodwindowtolistlocked、adjustinputmethoddialogslocked和adjustwallpaperwindowslocked的实现,其中,前两者是与输入法窗口相关的,而后者是与壁纸窗口相关的。本文主要关注windowmanagerservice类的成员函数addwindowtolistinorderlocked的实现,它会将一个指定的windowstate对象增加到窗口堆栈中的合适位置上去。
9. 增加windowstate到窗口堆栈
从前面的分析可以知道,将一个windowstate对象增加到windowmanagerservice服务内部中的窗口堆栈,即windowmanagerservice类的成员变量mwindows,是通过调用windowmanagerservice类的成员函数addwindowtolistinorderlocked来实现的。
windowmanagerservice类的成员函数addwindowtolistinorderlocked的实现比较复杂,我们先列出它的框架,然后再详细分析它的实现,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private void addwindowtolistinorderlocked(windowstate win, boolean addtotoken) {
final iwindow client = win.mclient;
final windowtoken token = win.mtoken;
final arraylist<windowstate> localmwindows = mwindows;
final int n = localmwindows.size();
final windowstate attached = win.mattachedwindow;
int i;
if (attached == null) {
//case 1:要增加的窗口win没有附加在其它窗口上
int tokenwindowspos = token.windows.size();
if (token.appwindowtoken != null) {
//case 1.1:要增加的窗口win是一个activity窗口
int index = tokenwindowspos-1;
if (index >= 0) {
//case 1.1.1:用来要增加的窗口win的令牌token已存在其它窗口
......
} else {
//case 1.1.2:用来要增加的窗口win的令牌token尚未存在任何窗口
......
}
} else {
//case 1.2:要增加的窗口win不是一个activity窗口
......
}
if (addtotoken) {
token.windows.add(tokenwindowspos, win);
}
} else {
//case 2:要增加的窗口win附加在窗口attached上
......
}
if (win.mapptoken != null && addtotoken) {
win.mapptoken.allappwindows.add(win);
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
我们首先分析一下windowmanagerservice类的成员函数addwindowtolistinorderlocked的几个本地变量的含义:
a. token。本地变量token指向的是参数win所描述的一个windowstate对象的成员变量mtoken所指向一个windowtoken对象,这个windowtoken对象用来描述windowstate对象win所对应的窗口令牌。
b. localmwindows。本地变量localmwindows指向的是windowmanagerservice类的成员变量mwindows所描述的一个arraylist,即一个窗口堆栈,windowmanagerservice类的成员函数addwindowtolistinorderlocked的目标就是要将参数win所描述的一个windowstate对象增加到该窗口堆栈的合适位置上去。
c. attached。本地变量attached指向的是参数win所描述的一个windowstate对象的成员变量mattachedwindow 所指向的一个windowstate对象,如果它的值不等于null,那么就意味参数win所描述的窗口要附加在本地变量attached所描述的窗口上。
d. tokenwindowspos。本地变量tokenwindowspos用来描述与窗口令牌token所对应的窗口的数量。
e. token.appwindowtoken。从前面android应用程序窗口(activity)与windowmanagerservice服务的连接过程分析一文可以知道,如果一个windowtoken对象的成员变量appwindowtoken的值不等于null,那么就意味着该windowtoken对象的实际类型为是appwindowtoken,即它所描述的是一个activity窗口令牌,这种类型的令牌的特点是在activitymanagerservice服务的activity组件堆栈中对应有一个activityrecord对象,如图1所示。
f. index。本地变量index的值等于tokenwindowspos-1,如果它的值大于等于0,那么就意味着窗口令牌tokent已经存在其它窗口,否则的话,就意味着窗口令牌tokent尚未存在任何窗口。
从这些本地变量的含义,我们就可以分情况来将参数win所描述的一个windowstate对象增加到windowmanagerservice服务内部的窗口堆栈的合适位置上去:
case 1:要增加的窗口win没有附加在其它窗口上
----case 1.1:要增加的窗口win是一个activity窗口
----case 1.1.1:用来要增加的窗口win的令牌token已存在其它窗口。这时候意味着窗口win需要保存在其它已经存在的窗口的附近,因此,我们只要找到这些已经存在的窗口在窗口堆栈中的位置,那么再根据其它属性,就可以将窗口win保存在已经存在的窗口的上面或者下面。
----case 1.1.2:用来要增加的窗口win的令牌token尚未存在任何窗口。虽然这时候窗口win在窗口堆栈中没有位置可以参考,但是它毕竟是一个activity窗口,我们可以通过与它所对应的appwindowtoken对象在app token list(即windowmanagerservice类的成员变量mapptokens所描述的一个arraylist)中的位置来获得它窗口堆栈中的位置。回忆我们在前面第3节分析移动appwindowtoken至指定位置的操作时得到的结论:windowmanagerservice服务内部中的所有windowstate对象都是按照z轴从位置从小到大排列在windowstate堆栈中的,并且在mapptokens列表中,位于上面的一个appwindowtoken对象所对应的那些windowstate对象的z轴位置是一定大于位于下面的一个appwindowtoken对象所对应的那些windowstate对象的z轴位置的。因此,我们只要找到用来描述窗口win的一个appwindowtoken对象(token.appwindowtoken)的上一个或者下一个appwindowtoken对象所对应的窗口在窗口堆栈中的位置,那么就可以这个位置为参考,得到窗口win在窗口堆栈中的位置。
----case 1.2:要增加的窗口win不是一个activity窗口。这时候既然要增加的窗口也没有附加在其它窗口上,那么就意味着要增加的窗口win在窗口堆栈中没有位置可以参考,因此,我们就需要根据它的z轴位置来决定它在窗口堆栈的位置。
case 2:要增加的窗口win附加在窗口attached上。这时候就意味着要增加的窗口win要保存在窗口attached的上面,即窗口在窗口堆栈的位置要以窗口attached在窗口堆栈的位置为参考。
从上面的分析就可以知道,case 1.1.1、case 1.1.2和case 2都有一个共同特点,即要增加的窗口win在窗口堆栈的位置有一个参考值,而在case 1.2中,要增加的窗口win在窗口堆栈的位置没有参考值,需要通过其z轴位置来确定。
在分析上述四种情况之前, 我们还需要再说明一下windowmanagerservice类的成员函数addwindowtolistinorderlocked的参数addtotoken的含义。参数addtotoken是一个布尔变量,如果它的值等于true,那么就说明需要将参数win所描述的一个windowstate对象添加用来描述它的窗口令牌token的成员变量windows所描述的一个arraylist中去。注意,窗口令牌token的成员变量windows所描述的一个arraylist里面所保存的windowstate对象是按照z轴位置从小到大的顺序来排列的,因此,在将windowstate对象win保存到这个arraylist之前,首先要按照它的z轴位置计算得到它在这个arraylist中的位置tokenwindowspos。另一方面,在参数addtotoken的值等于true,并且参数win所描述的是一个activity窗口,即它的成员变量mapptoken不等于null的情况下,还需要将参数win所描述的一个windowstate对象保存在用来描述它的窗口令牌,即一个appwindowtoken对象成员变量allappwindows所描述的一个arraylist中去,以便可以知道一个appwindowtoken对象对应的activity窗口都有哪些。
接下来,我们就分别分析这四种情况是如何将窗口win增加窗口堆栈中去的。
case 1.1.1对应的代码为:
if (win.mattrs.type == type_base_application) {
// base windows go behind everything else.
placewindowbefore(token.windows.get(0), win);
tokenwindowspos = 0;
} else {
appwindowtoken atoken = win.mapptoken;
if (atoken != null &&
token.windows.get(index) == atoken.startingwindow) {
placewindowbefore(token.windows.get(index), win);
tokenwindowspos--;
} else {
int newidx = findidxbasedonapptokens(win);
if(newidx != -1) {
//there is a window above this one associated with the same
//apptoken note that the window could be a floating window
//that was created later or a window at the top of the list of
//windows associated with this token.
......
localmwindows.add(newidx+1, win);
mwindowschanged = true;
}
}
}
这段代码又分为三种情况来将参数win所描述的一个windowstate对象添加到窗口堆栈中:
a. 参数win描述的窗口的类型为type_base_application。在一个令牌对应的所有窗口中,类型为type_base_application的窗口位于其它类型的窗口的下面。因此,这段代码就会调用windowmanagerservice类的成员函数placewindowbefore来将参数win所描述的一个windowstate对象保存窗口堆栈中,并且它是位于令牌token的窗口列表的第0个位置的windowstate对象的下面。这时候变量tokenwindowspos的值会被设置为0,表示参数win所描述的一个windowstate对象要保存窗口令牌token的窗口列表的第0个位置上。
b. 参数win描述的一个windowstate对象的成员变量mapptoken的值不等于null,这意味着参数win描述的是一个activity窗口,这时候如果窗口令牌atoken(与token描述的是同一个窗口令牌)的窗口列表的第index个位置(即最上面的一个位置) 的windowstate对象描述的是一个activity启动窗口,即与窗口令牌atoken的成员变量startingwindow描述的是同一个窗口,那么就说明窗口令牌atoken的窗口列表的第index个位置的windowstate对象描述的是窗口win的启动窗口。由于一个窗口的启动窗口总是位于它的上面,因此,这段代码就会调用windowmanagerservice类的成员函数placewindowbefore来将参数win所描述的一个windowstate对象保存窗口堆栈中,并且它是位于令牌atoken的窗口列表的第index个位置的windowstate对象的下面。这时候变量tokenwindowspos的值减少1,即相当于是等于index,表示参数win所描述的一个windowstate对象要插入在窗口令牌token的窗口列表的第index个位置上。
c. 参数win所描述的窗口的类型既不是type_base_application,而且它也没有启动窗口,那么这时候就需要将它保存在窗口令牌token的窗口列表的最上面一个窗口的上面。窗口令牌token的窗口列表的最上面一个窗口在窗口堆栈中的位置newidx是通过调用windowmanagerservice类的成员函数findidxbaseonapptokens来获得的,这时候参数win所描述的一个windowstate对象就应该保存在窗口堆栈,即变量localmwindows所描述的一个arraylist的第newidx+1个位置上。
case 1.1.2对应的代码为:
// figure out where the window should go, based on the
// order of applications.
final int na = mapptokens.size();
windowstate pos = null;
for (i=na-1; i>=0; i--) {
appwindowtoken t = mapptokens.get(i);
if (t == token) {
i--;
break;
}
// we haven't reached the token yet; if this token
// is not going to the bottom and has windows, we can
// use it as an anchor for when we do reach the token.
if (!t.sendingtobottom && t.windows.size() > 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<n; i++) {
windowstate w = localmwindows.get(i);
if (w.mbaselayer > 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 <= mylayer) {
i++;
break;
}
}
if (i < 0) i = 0;
......
localmwindows.add(i, win);
mwindowschanged = true;
由于这时候在窗口堆栈中是没有参考位置来保存参数win所描述的windowstate对象的,因此,这段代码就只能通过参数win所描述的windowstate对象的z轴位置,即它的成员变量mbaselayer的值来在窗口堆栈中找到一个合适的位置了,如这段代码中的for循环所示。由于窗口堆栈中的windowstate对象是按照它们的z轴位置由小到大的顺序来排列的,因此,这段代码中的for循环只要从上到下找到一个windowstate对象,它的z轴位置小于或者等于参数win所描述的windowstate对象的z轴位置,那么该windowstate对象在窗口堆栈中的位置i就可以用插入参数win所描述的windowstate对象了。
case 2对应的代码为:
// figure out this window's ordering relative to the window
// it is attached to.
final int na = token.windows.size();
final int sublayer = win.msublayer;
int largestsublayer = integer.min_value;
windowstate windowwithlargestsublayer = null;
for (i=0; i<na; i++) {
windowstate w = token.windows.get(i);
final int wsublayer = w.msublayer;
if (wsublayer >= largestsublayer) {
largestsublayer = wsublayer;
windowwithlargestsublayer = w;
}
if (sublayer < 0) {
// for negative sublayers, we go below all windows
// in the same sublayer.
if (wsublayer >= sublayer) {
if (addtotoken) {
token.windows.add(i, win);
}
placewindowbefore(
wsublayer >= 0 ? attached : w, win);
break;
}
} else {
// for positive sublayers, we go above all windows
// in the same sublayer.
if (wsublayer > sublayer) {
if (addtotoken) {
token.windows.add(i, win);
}
placewindowbefore(w, win);
break;
}
}
}
if (i >= na) {
if (addtotoken) {
token.windows.add(win);
}
if (sublayer < 0) {
placewindowbefore(attached, win);
} else {
placewindowafter(largestsublayer >= 0
? windowwithlargestsublayer
: attached,
win);
}
}
这段代码要将参数win所描述的windowstate对象附加在变量attached所描述的windowstate对象的上面或者下面,取决于它的成员变量msublayer的值是大于0还是小于0。我们分四种情况来考虑。
第一种情况是参数win所描述的windowstate对象的成员变量msublayer的值小于0,并且这时候在附加在窗口attached的windowstate对象中,存在一个windowstate对象,它的成员变量msublayer的值大于等于参数win所描述的windowstate对象的成员变量msublayer的值,如图4和图5所示:
图4 窗口win插入到窗口b的下面
图5 窗口win插入在窗口attached的下面
在图4和图5中,windowstate对象a和b均是附加在windowstate对象attached中。
在图4中,windowstate对象a和b的成员变量msublayer的值均小于0,而windowstate对象win的成员变量msublayer的值比windowstate对象a的大,但是比windowstate对象b的小,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象b的下面,这是通过调用windowmanagerservice类的成员函数placewindowbefore来实现的。
在图5中,windowstate对象a和b的成员变量msublayer的值均大于0,由于windowstate对象win的成员变量msublayer的值小于0,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象attached的下面,这是通过调用windowmanagerservice类的成员函数placewindowbefore来实现的。
第二种情况是参数win所描述的windowstate对象的成员变量msublayer的值大于0,并且这时候在附加在窗口attached的windowstate对象中,存在一个windowstate对象,它的成员变量msublayer的值大于参数win所描述的windowstate对象的成员变量msublayer的值,如图6所示:
图6 窗口win插入在窗口b的下面
在图6中,windowstate对象a和b均是附加在windowstate对象attached中。其中,windowstate对象a和b的成员变量msublayer的值均大于0,而windowstate对象win的成员变量msublayer的值比windowstate对象a的大,但是比windowstate对象b的小,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象b的下面,这是通过调用windowmanagerservice类的成员函数placewindowbefore来实现的。
第三种情况是参数win所描述的windowstate对象的成员变量msublayer的值小于0,但是在附加在窗口attached的windowstate对象中,找不到一个windowstate对象,它的成员变量msublayer的值比windowstate对象的成员变量msublayer的值大,如图7所示:
图7 窗口win插入在窗口attached的下面
在图7中,windowstate对象a和b均是附加在windowstate对象attached中。其中,windowstate对象a和b以及win的成员变量msublayer的值均小于0,但是windowstate对象win的成员变量msublayer的值比windowstate对象a和b的都要大,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象attached的下面,这是通过调用windowmanagerservice类的成员函数placewindowbefore来实现的。
第四种情况是参数win所描述的windowstate对象的成员变量msublayer的值大于等于0,但是在附加在窗口attached的windowstate对象中,找不到一个windowstate对象,它的成员变量msublayer的值比windowstate对象的成员变量msublayer的值大,如图8和图9所示:
图8 窗口win插入在窗口b的上面
图9 窗口win插入在窗口attached的上面
在图8和图9中,windowstate对象a和b均是附加在windowstate对象attached中。
在图8中,windowstate对象a和b的成员变量msublayer的值均大于0,并且windowstate对象win的成员变量msublayer的值比windowstate对象a和b的都要大,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象b的上面,这是通过调用windowmanagerservice类的成员函数placewindowafter来实现的。
在图9中,windowstate对象a和b的成员变量msublayer的值均小于等于0,而windowstate对象win的成员变量msublayer的值大于0,这时候windowstate对象win在窗口堆栈中就应该位于windowstate对象attached的上面,这是通过调用windowmanagerservice类的成员函数placewindowafter来实现的。
注意,在这四种情况中,如果参数addtotoken的值等于true,那么都需要将参数win所描述的windowstate对象增加到与它所对应的窗口令牌token的窗口列表windows中去。
10. 删除windowstate
删除windowstate是通过调用windowmanagerservice类的成员函数tmpremovewindowlocked来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private int tmpremovewindowlocked(int interestingpos, windowstate win) {
int wpos = mwindows.indexof(win);
if (wpos >= 0) {
if (wpos < interestingpos) interestingpos--;
......
mwindows.remove(wpos);
mwindowschanged = true;
int nc = win.mchildwindows.size();
while (nc > 0) {
nc--;
windowstate cw = win.mchildwindows.get(nc);
int cpos = mwindows.indexof(cw);
if (cpos >= 0) {
if (cpos < interestingpos) interestingpos--;
......
mwindows.remove(cpos);
}
}
}
return interestingpos;
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
windowmanagerservice类的成员函数tmpremovewindowlocked将参数win所描述的窗口及其子窗口从windowmanagerservice服务内部的窗口堆栈中删除,即从 windowmanagerservice类的成员变量mwindows所描述的一个arraylist中删除。
如果每一个被删除的窗口在窗口堆栈中的位置比参数interestingpos的值小,那么windowmanagerservice类的成员函数tmpremovewindowlocked还会将参数interestingpos的值减少1,这相当于是计算当删除参数win所描述的窗口及其子窗口之后,原来位于窗口堆栈中第interestingpos个位置的窗口现在位于窗口堆栈的位置,这个位置最终会作为windowmanagerservice类的成员函数tmpremovewindowlocked的返回值。
11. 在指定位置增加windowstate
在指定位置增加windowstate是通过调用windowmanagerservice类的成员函数readdwindowlocked来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private final int readdwindowlocked(int index, windowstate win) {
final int ncw = win.mchildwindows.size();
boolean added = false;
for (int j=0; j<ncw; j++) {
windowstate cwin = win.mchildwindows.get(j);
if (!added && cwin.msublayer >= 0) {
......
mwindows.add(index, win);
index++;
added = true;
}
......
mwindows.add(index, cwin);
index++;
}
if (!added) {
......
mwindows.add(index, win);
index++;
}
mwindowschanged = true;
return index;
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
参数win描述的即为要增加的windowstate对象,而参数index描述的即为要将参数win所描述的windowstate对象及其子windowstate对象要增加到窗口堆栈中的起始位置。
由于参数win所描述的windowstate对象的子windowstate对象的成员变量msublayer的值可能会小于0,也可能大于0。大于0的子windowstate对象位于参数win所描述的windowstate对象的上面,而小于0的子windowstate对象位于参数win所描述的windowstate对象的下面。因此,windowmanagerservice类的成员函数readdwindowlocked先增加那些小于0的子windowstate对象,接着再增加参数win所描述的windowstate对象,最后增加那些大于0的子windowstate对象。
假设windowmanagerservice类的成员函数readdwindowlocked一共在窗口堆栈中增加了n个windowstate对象,那么它的返回值就等于index + n,这样调用者就可以知道参数win所描述的windowstate对象及其子windowstate对象在窗口堆栈中的最高位置是多少。
基于第9、第10和第11这三操作,可以组合成很多其它的windowstate操作,如接下来的第12、第13、第14和第15个操作所示。
12. 将一个windowstate对象及其所有子windowstate对象增加到窗口堆栈中
将一个windowstate对象及其所有子windowstate对象增加到窗口堆栈中是通过调用windowmanagerservice类的成员函数readdwindowtolistinorderlocked来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private void readdwindowtolistinorderlocked(windowstate win) {
addwindowtolistinorderlocked(win, false);
// this is a hack to get all of the child windows added as well
// at the right position. child windows should be rare and
// this case should be rare, so it shouldn't be that big a deal.
int wpos = mwindows.indexof(win);
if (wpos >= 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<nw; i++) {
index = readdwindowlocked(index, token.windows.get(i));
}
return index;
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
与参数token所描述的windowtoken对象所对应的windowstate对象保存在它的成员变量windows所描述的一个arraylist中。通过遍历这个arraylist,就可以将与参数token所描述的windowtoken对象所对应的windowstate对象及其子windowstate对象都增加到窗口堆栈的指定的起始位置上去,这是通过调用前面所分析的成员函数readdwindowlocked来实现的。
参数index描述的便是最初指定的起始位置,每一次调用windowmanagerservice类的成员函数readdwindowlocked之后,它的值都便会被更新为下一个windowstate对象及其子windowstate对象要增加到窗口堆栈中的位置。
最后,windowmanagerservice类的成员函数readdappwindowslocked将与参数token所描述的windowtoken对象所对应的windowstate对象在窗口堆栈中的最高位置加1后的得到结果返回给调用者。
14. 将一个appwindowtoken对象所对应的windowstate对象及其子 windowstate对象移动到窗口堆栈的指定位置上
将一个appwindowtoken对象所对应的windowstate对象及其子 windowstate对象移动到窗口堆栈的指定位置上是通过调用windowmanagerservice类的成员函数moveappwindowslocked来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private void moveappwindowslocked(appwindowtoken wtoken, int tokenpos,
boolean updatefocusandlayout) {
// first remove all of the windows from the list.
tmpremoveappwindowslocked(wtoken);
// where to start adding?
int pos = findwindowoffsetlocked(tokenpos);
// and now add them back at the correct place.
pos = readdappwindowslocked(pos, wtoken);
if (updatefocusandlayout) {
if (!updatefocusedwindowlocked(update_focus_will_place_surfaces)) {
assignlayerslocked();
}
mlayoutneeded = true;
performlayoutandplacesurfaceslocked();
}
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
参数wtoken描述的是要移动其所对应的windowstate对象的一个appwindowtoken对象,而参数tokenpos描述的是该appwindowtoken对象在windowmanagerservice服务内部的appwindowtoken列表中的新位置。
windowmanagerservice类的成员函数moveappwindowslocked首先调用前面所分析的成员函数tmpremoveappwindowslocked来移除所有与参数wtoken所描述的appwindowtoken对象所对应的windowstate对象,接着再调用也是前面所分析的成员函数findwindowoffsetlocked来获得与参数wtoken所描述的appwindowtoken对象所对应的windowstate对象在窗口堆栈中的起始位置。有了这个起始位置之后,就可以也是前面所分析的成员函数readdappwindowslocked来将与参数wtoken所描述的appwindowtoken对象所对应的windowstate对象及其子windowstate对象移动到窗口堆栈上去了。
最后,如果参数updatefocusandlayout的值等于true,那么windowmanagerservice类的成员函数moveappwindowslocked还会更新系统当前获得焦点的窗口,以及重新计算系统中的所有窗口的z轴位置以及重新布局系统中的所有窗口,这三个操作分别是通过调用windowmanagerservice类的成员函数updatefocusedwindowlocked、assignlayerslocked和performlayoutandplacesurfaceslocked来实现的。
15. 将一组appwindowtoken对象所对应的windowstate对象及其子 windowstate对象移动到窗口堆栈的指定位置上
将一组appwindowtoken对象所对应的windowstate对象及其子windowstate对象移动到窗口堆栈的指定位置上是通过调用windowmanagerservice类的另外一个版本的成员函数moveappwindowslocked来实现的,如下所示:
public class windowmanagerservice extends iwindowmanager.stub
implements watchdog.monitor {
......
private void moveappwindowslocked(list<ibinder> tokens, int tokenpos) {
// first remove all of the windows from the list.
final int n = tokens.size();
int i;
for (i=0; i<n; i++) {
windowtoken token = mtokenmap.get(tokens.get(i));
if (token != null) {
tmpremoveappwindowslocked(token);
}
}
// where to start adding?
int pos = findwindowoffsetlocked(tokenpos);
// and now add them back at the correct place.
for (i=0; i<n; i++) {
windowtoken token = mtokenmap.get(tokens.get(i));
if (token != null) {
pos = readdappwindowslocked(pos, token);
}
}
if (!updatefocusedwindowlocked(update_focus_will_place_surfaces)) {
assignlayerslocked();
}
mlayoutneeded = true;
performlayoutandplacesurfaceslocked();
//dump();
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/windowmanagerservice.java中。
这个操作与前面分析的第14个操作是类似,区别只在于前者是批量地移动一组appwindowtoken对象所对应的windowstate对象及其子 windowstate对象,而后者是只移动一个appwindowtoken对象所对应的windowstate对象及其子windowstate对象,此外,前者总是会调用windowmanagerservice类的成员函数updatefocusedwindowlocked、assignlayerslocked和performlayoutandplacesurfaceslocked来更新系统当前获得焦点的窗口、以及重新计算每一个窗口的z轴位置,并且对这些窗口进行重新布局。
至此,我们就分析完成windowmanagerservice服务组织系统中的窗口的方式了。从分析的过程中,可以得到以下结论:
1. windowmanagerservice服务维护有一个appwindowtoken堆栈和一个windowstate堆栈,它们与activitymanagerservice服务维护的actvity堆栈是有关相同的z轴位置关系的。
2. activitymanagerservice服务中的每一个activityrecord对象在windowmanagerservice服务中都对应有一个appwindowtoken对象,而windowmanagerservice服务中的每一个appwindowtoken对象都对应有一组windowstate对象。
3. 在windowstate堆栈中,appwindowtoken堆栈中的第i+1个appwindowtoken对象所对应的windowstate对象都位于第i个appwindowtoken对象所对应的windowstate对象的上面。
4. 一个windowstate对象可以附加在另外一个windowstate对象上面,此外,一个windowstate对象还可以有子windowstate对象,它们都是与同一个appwindowtoken对象或者windowtoken对象所对应的。
5. windowmanagerservice服务有两个特殊的windowtoken,它们分别用来描述系统中的输入法窗口令牌和壁纸窗口令牌,其中,输入法窗口位于需要输入法的窗口的上面,而壁纸窗口位于需要壁纸的窗口的下面。
最后,我们可以将windowmanagerservice服务中的appwindowtoken理解成一个activity组件令牌,而将它所对应的windowstate对象理解成一个activity窗口。有了这些概念之后,就为学习windowmanagerservice服务的各种实现打下坚实的基础。在接下来的两篇文章中,我们就会在本文的基础上,继续分析windowmanagerservice服务是如何管理系统中的输入法窗口和壁纸窗口的,敬请关注!