Android 的 WMS(Window Manager Service)是一个关键组件,负责管理窗口的创建、显示、布局和交互等。Window 的操作有两大部分,一部分是 WindowManager 来处理,一部分是 WMS 来处理,如下图所示:
WindowManager 中,通过 WindowManagerGlobal 创建 ViewRootImpl ,也就是 View 的根。在 ViewRootImpl 中完成对 View 的绘制等操作,然后通过 IPC 获取到 Session,最终通过 WMS 来进行处理。WindowManager 部分的管理流程前面已经介绍,这里我们来看一下 WMS 对 Window 的管理。
一、调用流程
1、WM到WMS
我们都知道 Window 的添加最后是通过 ViewRootImpl.addTodisplay 方法来完成的,我们先来看一下:
ViewRootImpl
源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java
final IWindowSession mWindowSession;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
synchronized (this) {
if (mView == null) {
……
try {
……
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
}
……
}
}
}
这里调用了 mWindowSession.addToDisplayAsUser 来完成最后的添加,可以看到 IWindowSession 是一个接口类,真正实现该接口的是 Session 类。
Session
源码位置:
/frameworks/base/services/core/java/com/android/server/wm/Session.java
final WindowManagerService mService;
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}
可以看到,最终是通过 WMS 来完成添加的。需要注意的是,WMS 并不关心 View 的具体内容,他只关心各个应用显示的界面大小、层级值等,这些数据到包含在 WindowManager.LayoutParams 中。也就是上面的 atrs 属性。
addWindow 的第二个参数是一个 IWindow 类型,这是 App 暴露给 WMS 的抽象实例,在 ViewRootImp 中实例化,与 ViewRootImpl 一一对应,同时也是 WMS 向 App 端发送消息的 Binder 通道。
二、WMS窗口添加
WindowManagerService
源码位置:/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
Arrays.fill(outActiveControls, null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
// 1.检查权限,mPolicy的实现类是PhoneWindowManager。
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
if (res != ADD_OKAY) {
return res;
}
……
synchronized (mGlobalLock) {
……
// 2.通过displayId获取Window要添加到哪个DisplayContent。
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
if (displayContent == null) {
// 没有找到对应的显示屏幕
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
……
// 3.判断type的窗口类型(100-1999),如果是子类型,必须要有父窗口,并且父窗口不能是子窗口类型。
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) {
// 试图添加带有非窗口令牌的窗口
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
// 试图添加带有子标记的窗口
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
……
ActivityRecord activity = null;
final boolean hasParent = parentWindow != null;
// 4.对子窗口使用与父窗口使用的令牌,因此我们可以对它们应用相同的策略。
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
// 如果这是一个子窗口,与父窗口类型相同的检查规则。
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
// 5. token为null。
if (token == null) {
if (hasParent) {
// 对子窗口使用现有的父窗口令牌。
token = parentWindow.mToken;
} else if (mWindowContextListenerController.hasListener(windowContextToken)) {
// 如果用户提供的话,尊重窗口上下文令牌
final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
final Bundle options = mWindowContextListenerController.getOptions(windowContextToken);
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.setFromClientToken(true)
.setOptions(options)
.build();
} else {
// 6.且不是应用窗口或者是其他类型的窗口,则窗口就是系统类型(例如 Toast)。
// 进行隐式创建 WindowToken,这说明我们添加窗口时是可以不向WMS提供WindowToken的。
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.build();
}
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
// 7、判断是否为应用窗口,如果是,将WindowToken转换为应用程序窗口的ActivityRecord。
activity = token.asActivityRecord();
if (activity == null) {
// 试图添加带有非应用程序令牌的窗口
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (activity.getParent() == null) {
// 试图添加具有退出应用程序的窗口
return WindowManagerGlobal.ADD_APP_EXITING;
} else if (type == TYPE_APPLICATION_STARTING) {
if (activity.mStartingWindow != null) {
// 试图用已经存在的开始窗口向令牌添加开始窗口
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
if (activity.mStartingData == null) {
// 试图向令牌添加起始窗口,但已被清除
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
}
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
// 试图添加带有错误令牌的输入法窗口
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
……
// 8.创建WindowState,保存窗口的所有状态信息,在WMS中,WindowState与窗口是一一对应的关系。
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
// 9.判断请求添加窗口的客户端是否已经死亡,如果死亡则不会执行下面逻辑。
if (win.mDeathRecipient == null) {
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
// 10.根据窗口的type类型对窗口的LayoutParams的一些成员变量进行修改。
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
win.updateRequestedVisibility(requestedVisibility);
// 11.准备将窗口添加到系统中
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
return res;
}
……
// 12.将WindowState添加到mWindowMap中,mWindowMap是各种窗口的集合。
mWindowMap.put(client.asBinder(), win);
……
boolean imMayMove = true;
// 13.添加窗口。将WindowState添加到对应的WindowToken中(实际上就是保存在WindowToken的父类 WindowContainer),这样WindowToken就包含了相同组件的WindowState。
win.mToken.addWindow(win);
}
return res;
}
WMS 的 addWindow 方法返回的是 addWindow 的各种状态,例如添加成功、失败、无效的 display 等,这些状态定义在 WindowManagerGloabl 中 。
通过上面的流程,App 到 WMS 注册窗口的流程就完了,WMS 为窗口创建了用来描述状态的 WindowState,接下来就会为新建的窗口显示次序,然后再去申请 Surface,才算是真正的分配了窗口。
这里对 WMS 的 addWindow 流程做一个总结 :
首先检查权限
接着从 mRoot(RootWindowContainer)中获取 DisplayContent ,如果没有就会根据 displayId 创建一个新的 DisplayContent
接着就是 type 类型的判断,如果是子类型,就必须要获取到他的父窗口,
接着使用 DisplayContent 获取当前或者父窗口获取 token,如果为 null 就排除子窗口和其他的窗口,剩下的就是可以不用携带 token 的窗口,WMS 会隐式的创建窗口 token。如果不等于 null 就判断是应用窗口就将 token 转为 ActivityRecord,后面还有一大堆窗口判断,只要是不满足就直接 return。
类型判断完成后,就会创建 WindowState,并且传入 WMS、IWindow、token 等。WindowState 里面保存了窗口的所有信息。WindowState 与窗口一一对应。
接着就执行调用了 WindowState 的 attache 、initAppOpsState 等方法。WindowState 创建完成后就会被添加到 mWindowMap 中,可以 IWindow 的 Binder 为 key,WindowState 为 value 添加进去。
最后就是 win.mToken.addWindow(win) ,然后将 WindowState 添加到 WindowToken 中。因为 WindowToken 是可以复用的,所以这里的关系就是,每个 WindowToken 都会保存对应的 WindowState,而每个 WindowState 也都会都持有 WindowToekn。