WindowManagerService (2)认识wms - l-feng/l-feng.github.io GitHub Wiki
一:WMS的窗口管理结构 1:添加窗口的过程:
public int addWindow(Session session, IWindowclient, int seq,
WindowManager.LayoutParams attrs, int viewVisibility,int displayId
Rect outContentInsets, InputChannel outInputChannel) {
// 首先检查权限,没有权限的客户端不能添加窗口
intres = mPolicy.checkAddPermission(attrs);
......
// 当为某个窗口添加子窗口时,attachedWindow将用来保存父窗口的实例
WindowState attachedWindow = null;
//win就是即将被添加的窗口了
WindowState win = null;
......
finalint type = attrs.type;
synchronized(mWindowMap){ ......
//①获取窗口要添加到的DisplayContent
/* 在添加窗口时,必须通过displayId参数指定添加到哪一个DisplayContent。
*/
finalDisplayContent displayContent = getDisplayContentLocked(displayId);
if(displayContent == null) {
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
......
// 如果要添加的窗口是另一个的子窗口,就要求父窗口必须已经存在
// 注意, attrs.type表示了窗口的类型,attrs.token则表示了窗口所隶属的对象
// 对于子窗口来说,attrs.token表示了父窗口
if(type >= FIRST_SUB_WINDOW &&.type <= LAST_SUB_WINDOW) {
attachedWindow = windowForClientLocked(null, attrs.token, false);
if (attachedWindow == null) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
//在这里还可以看出WMS要求窗口的层级关系最多为两层
if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
&&attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
booleanaddToken = false;
// ②WindowToken出场!根据客户端的attrs.token取出已注册的WindowToken
WindowToken token = mTokenMap.get(attrs.token);
// 下面的if语句块初步揭示了WindowToken和窗口之间的关系
if(token == null) {
// 对于以下几种类型的窗口,必须通过LayoutParams.token成员为其指定一个已经
// 添加至WMS的WindowToken
if (type >= FIRST_APPLICATION_WINDOW
&& type<= LAST_APPLICATION_WINDOW) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_INPUT_METHOD) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_WALLPAPER) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_DREAM) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
// 其他类型的窗口则不需要事先向WMS添加WindowToken因为WMS会在这里隐式地创
// 建一个。注意最后一个参数false,这表示此WindowToken由WMS隐式创建。
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
} else if (type >= FIRST_APPLICATION_WINDOW
&&type <= LAST_APPLICATION_WINDOW) {
// 对于APPLICATION类型的窗口,要求对应的WindowToken的类型也为APPLICATION
// 并且是WindowToken的子类:AppWindowToken
AppWindowToken atoken = token.appWindowToken;
if (atoken == null) {
return WindowManagerImpl.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
returnWindowManagerImpl.ADD_APP_EXITING;
}
if (type==TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn){
return WindowManagerImpl.ADD_STARTING_NOT_NEEDED;
} } else if (type == TYPE_INPUT_METHOD) {
// 对于其他几种类型的窗口也有类似的要求:窗口类型必须与WindowToken的类型一致
if (token.windowType != TYPE_INPUT_METHOD) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_WALLPAPER) {
if (token.windowType != TYPE_WALLPAPER) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_DREAM) {
if (token.windowType != TYPE_DREAM) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
// ③WMS为要添加的窗口创建了一个WindowState对象
// 这个对象维护了一个窗口的所有状态信息
win= new WindowState(this, session, client, token,
attachedWindow,seq, attrs, viewVisibility, displayContent);
......
// WindowManagerPolicy出场了。这个函数的调用会调整LayoutParams的一些成员的取值
mPolicy.adjustWindowParamsLw(win.mAttrs);
res= mPolicy.prepareAddWindowLw(win, attrs);
if(res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
// 接下来将刚刚隐式创建的WindowToken添加到mTokenMap中去。通过这行代码应该
if(addToken) {
mTokenMap.put(attrs.token, token);
}
win.attach();
// 然后将WindowState对象加入到mWindowMap中
mWindowMap.put(client.asBinder(),win);
}
}
该代码中涉及到了WindowToken和WindowState以及displayContent 之间的关系:
除子窗口外,添加到任何一个窗口的都必须指明所属的WindowToken;窗口在wms中通过一个WindowState实例进行管理,
同时也需要在窗口中申明其所属于的displayContent,以便于确定窗口将被显示到哪一个屏幕上面。
2:理解windowToken
2.1 意义
1)将属于一个应用组件的窗口组织在一起。应用组件可以是Activity,InputMethod,WallPaper,Dream。
2)具有令牌的作用。对于应用组件的行为进行规范管理。它是由应用组件或者管理者负责向WMS申明并持有的。
再开始的时候进行权限检查的时候需要SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW权限才能创建系统类型的窗口。
2.2 声明WindowToken
只要是一个Binder对象,都可以作为Token向wms作为申明,对于wms客户端来说,Token只是一个Binder对象。
使用addWindowToken()函数声明Token,将会在wms创建一个WindowToken实例,并且添加到 mTokenMap中。
键值为客户端用于声明Token的Binder的实例。
addWindowToken()告诉我们,WindowToken其实有两个含义:
1)对于显示组件(客户端)而言的token,是任意一个Binder的实例,对于显示组件而言(客户端)来说,
仅仅是一个创建窗口的令牌,没有其他的作用。
2)对于wms而言的WindowToken,这是一个在WindowToken类的实例,保存了对应于客户端一侧的Token(Binder实例)
,并以这个Token为键,存储于mTokenMap中。
关于WindowToken在wallpaper以及activiry中的应用,可以查看《深入理解android 卷三》。
3: WindowState理解
mTokenMap 和mWindowMap这两个hashMap维护了WMS中最重要的两类数据,它们的键都是IBinder,
区别是:mTokenMap的键值可能是IAppWindowToken的Bp端(使用addAppToken声明),或者是其他
任意一个binder的Bp端(使用addWindowToken进行声明);而mWindowToken的键值一定是IWindow
的bp端
4:理解displayContent
windowToken是根据窗口之间的逻辑关系进行分组的,而displayContent则是根据窗口的
显示位置将其分组。隶属于同一个的displayContent的窗口将被显示在同一个屏幕上面。每
一个displayContent都有唯一的一个ID。
二:理解窗口的主次顺序
简单的来说,窗口的显示顺序=主序(mBaseLayer)+子序(mSubLayer)。主序主要描述的是
主窗口以及其子窗口在所有窗口的显示位置,子序主要是描述的是一个子窗口在其他兄弟窗
口之间的位置。
主序越大,在窗口和其子窗口的显示位置越靠前,
子序越大,则子窗口相对于其他兄弟窗口的位置越靠前
通过子序和主序计算窗口顺序的时候有两个关键地方:
addWindowToListInOrderLocked()将新建的WindowState按照一定的顺序插入到当前的displayedContent的mWindow列表中
assignlayersLocked()将根据mWindows的存储顺序对于所有的WindowState的主序和子序进行调整。
在进行顺序完成之后,WMS的动画系统通过Surface.setlayer()设置进Surflinger。