窗口管理使用到的 DisplayContent,WindowToken 和 WindowState。
DisplayContent
用来管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个 DisplayContent。使用 displayId 来区分。
处于不同 DisplayContent 的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合。因此,就这几个方面来说,DisplayContent 就像一个孤岛,所有这些操作都可以在其内部独立执行。
DisplayContent 类声明:
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>{ // Mapping from a token IBinder to a WindowToken object on this display. private final HashMap mTokenMap = new HashMap();}
DisplayContent 的子容器是其内部类 DisplayChildWindowContainer
DisplayContent 内部使用:IBinder 为 key,WindowToken 为 value 的键值对保存在 HashMap 中。
DisplayContent 是在 Window 添加到 WMS 的时候初始化的。
public int addWindow(Session session, ..){ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId); ..}
mRoot 是 RootWindowContainer 类型的对象,看名字就知道其是窗口容器的根。说明在 Window 体系中,RootWindowContainer 节点是容器最顶端的父容器。
class RootWindowContainer extends WindowContainer<DisplayContent> { DisplayContent getDisplayContentOrCreate(int displayId) { DisplayContent dc = getDisplayContent(displayId); if (dc == null) { final Display display = mService.mDisplayManager.getDisplay(displayId); if (display != null) { dc = createDisplayContent(display); } } return dc; } DisplayContent getDisplayContent(int displayId) { for (int i = mChildren.size() - 1; i >= 0; --i) { final DisplayContent current = mChildren.get(i); if (current.getDisplayId() == displayId) { return current; } } return null; }}
其继承了 WindowContainer,这里的 DisplayContent 是一个泛型声明,表示其子容器的类型是 DisplayContent, 在 getDisplayContent 方法中也可知,其 mChildren 列表是 DisplayContent 的集合。这也变相的说明 DisplayContent 也是一个容器。
WindowToken
类声明:
class WindowToken extends WindowContainer<WindowState>
表明 WindowToken 也是子容器,其子容器是 WindowState,所以 WindowState 也是一个容器。
WindowToken 在窗口体系中有两个作用:
应用组件标识:将属于同一个应用组件的窗口组织在一起,这里的应用组件可以是:Activity、InputMethod、Wallpaper 以及 Dream。WMS 在对窗口的管理过程中用 WindowToken 来指代一个应用组件。例如在进行窗口的 Z-Order 排序时,属于同一个 WindowToken 的窗口会被安排在一起。
令牌作用:WindowToken 由应用组件或其管理者负责向 WMS 声明并持有,应用组件在需要更新窗口时,需要向 WMS 提供令牌表明自己的身份, 并且窗口的类型必须与所持有的 WindowToken 的类型一致。
但是系统窗口是个例外,并不需要提供 token,WMS 会隐式声明一个WindowToken。那是不是说谁都可以添加系统窗口了呢?非也,在 addWindow 开始处就会调用下面代码:
mPolicy.checkAddPermission()
它要求客户端必须拥有 SYSTEM_ALERT_WINDOW 或INTERNAL_SYSTEM_WINDOW 权限才能创建系统类型的窗口。Window 和WindowToken 关系如下:
class WindowState extends WindowContainer
表明 WindowState 也是一个 WindowContainer 容器,但是其子容器也是WindowState,一般窗口有子窗口 SUB_WINDOW 的情况下,WindowState 才有子容器节点。WindowState 在 WMS 中表示一个 Window 窗口状态属性,其内部保存了一个 Window 所有的属性信息。其与 View 以及 WindowToken 关系如下:
如何查看当前设备 Window 窗口状态命令?
adb shell dumpsys window windows
Window #9 Window{884cb45 u0 com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}: mDisplayId=0 stackId=3 mSession=Session{f1b7b8e 4307:u0a10065} mClient=android.os.BinderProxy@a512fbc mOwnerUid=10065 mShowToOwnerOnly=true package=com.android.messaging appop=NONE mAttrs={(0,36)(828xwrap) gr=BOTTOM CENTER sim={adjust=pan forwardNavigation} ty=APPLICATION fmt=TRANSLUCENT wanim=0x7f130015 fl=DIM_BEHIND ALT_FOCUSABLE_IM HARDWARE_ACCELERATED vsysui=LIGHT_STATUS_BAR LIGHT_NAVIGATION_BAR} Requested w=828 h=290 mLayoutSeq=220 mBaseLayer=21000 mSubLayer=0 mToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}} mAppToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}}...
下面笔者以窗口的添加操作为例讲解 WMS 的窗口管理。
窗口的添加操作
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { ... int res = mPolicy.checkAddPermission(attrs, appOp);//1 ... synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2 if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { parentWindow = windowForClientLocked(null, attrs.token, false);//3 } ... WindowToken token = displayContent.getWindowToken( hasParent ? parentWindow.mAttrs.token : attrs.token);//4 if (token == null) { final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); token = new WindowToken(this, binder, type, false, displayContent, session.mCanAddInternalSystemWindow);//5 } ... final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, session.mCanAddInternalSystemWindow);//6 ... mPolicy.adjustWindowParamsLw(win.mAttrs);//7 ... if (openInputChannels) { win.openInputChannel(outInputChannel);//8 } ... mWindowMap.put(client.asBinder(), win);//9 ... win.mToken.addWindow(win);//10 ... displayContent.assignWindowLayers(false /* setLayoutNeeded */);//11 //12 if (focusChanged) { mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/); } mInputMonitor.updateInputWindowsLw(false /*force*/); } ... return res;}
注释1:检查当前 Window 的 token 等权限合法性。
注释2:这在介绍 DisplayContent 已经说过,使用 RootWindowContainer 的子容器中获取一个 DisplayContent,如果子容器集合中不存在,则去获取一个,并添加到 child 集合中
注释3:如果是 Dialog 等子窗口,则获取父窗口,没有就报找不到父窗口的异常。
注释4:使用 attr.token 去 displayContent 的键值对 mTokenMap 中获取对应的 WindowToken,WindowToken 中保存了一组 Window。
注释5:如果4中 WindowToken 为 null,则创建一个 WindowToken,传入 app 层传入的 attr.token 以及 displayContent 对象,内部会将这个创建的 WindowToken 保存到 displayContent 中
注释6:创建一个 WindowState,并传入所有关于 Window 相关的属性,这样 WindowState 在 WMS 中就是以一个 Window 性质存在了、WindowState 构造过程中会将其添加到 WindowToken 中去。
注释7:根据 mPolicy 调整 window 的 attr 属性,mPolicy 的实现类是PhoneManagerPolicy。
注释8:执行 WindowState 的 openInputChannel,这里主要是打通和 Input 系统的通道,用于接收 IMS 的输入事件请求。
注释9:将客户端 app 层的 Window 对象和 WindowState 关联上,这样 WMS 就可以通过 Window 找到 WMS 中的 WindowState 对象。
注释10:win.mToken 是前面创建的 WindowToken 对象,所以此处就是将WindowState 加入到 WindowToken 的子容器集合中。
注释11:分配窗口的层级,这里的 setLayoutNeeded 参数为 false,说明不需要进行 Layout 操作。
这里小结下 addWindow 方法,主要就是创建了一个和 Window 一一对应的 WindowState 对象,并将 WindowState 插入到父容器 WindowToken 的子容器集合中,而 WindowToken 又保存在 DisplayContent 的键值对集合中。三种关系可以简单总结如下: