上篇文章讨论了应用程序与WindowManagerService相互通信的建立过程。 应用程序向系统中添加窗口,就会调用到WindowManagerService的addWindow方法,本章来看看该方法的内部实现。添加窗口在应用进程中的代码实现以后再介绍,本节只看WMS内部实现。
窗口相关类介绍
在Android系统中窗口可分为3大类:应用窗口,子窗口和系统窗口。其中应用窗口即Activity的显示窗口。子窗口是依附于应用窗口或者系统窗口的窗口类型,它们一般随着所依附的窗口一起出现或者切换到后台。每种类型的窗口有很多种类型。 其中应用窗口的类型定义:
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
/**
* Window type: an application window that serves as the "base" window
* of the overall application; all other application windows will
* appear on top of it.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_BASE_APPLICATION = 1;
/**
* Window type: a normal application window. The {@link #token} must be
* an Activity token identifying who the window belongs to.
* In multiuser systems shows only on the owning user's window.
*/
//Activity的顶层窗口
public static final int TYPE_APPLICATION = 2;
/**
* Window type: special application window that is displayed while the
* application is starting. Not for use by applications themselves;
* this is used by the system to display something until the
* application can show its own windows.
* In multiuser systems shows on all users' windows.
*/
//应用启动时系统创建的窗口,此时应用窗口还未显示出来
public static final int TYPE_APPLICATION_STARTING = 3;
/**
* Window type: a variation on TYPE_APPLICATION that ensures the window
* manager will wait for this window to be drawn before the app is shown.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_DRAWN_APPLICATION = 4;
/**
* End of types of application windows.
*/
public static final int LAST_APPLICATION_WINDOW = 99;
子窗口的类型定义:
/**
* Start of types of sub-windows. The {@link #token} of these windows
* must be set to the window they are attached to. These types of
* windows are kept next to their attached window in Z-order, and their
* coordinate space is relative to their attached window.
*/
public static final int FIRST_SUB_WINDOW = 1000;
/**
* Window type: a panel on top of an application window. These windows
* appear on top of their attached window.
*/
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
/**
* Window type: window for showing media (such as video). These windows
* are displayed behind their attached window.
*/
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
/**
* Window type: a sub-panel on top of an application window. These
* windows are displayed on top their attached window and any
* {@link #TYPE_APPLICATION_PANEL} panels.
*/
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
/** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
* of the window happens as that of a top-level window, <em>not</em>
* as a child of its container.
*/
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
/**
* Window type: window for showing overlays on top of media windows.
* These windows are displayed between TYPE_APPLICATION_MEDIA and the
* application window. They should be translucent to be useful. This
* is a big ugly hack so:
* @hide
*/
@UnsupportedAppUsage
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
/**
* Window type: a above sub-panel on top of an application window and it's
* sub-panel windows. These windows are displayed on top of their attached window
* and any {@link #TYPE_APPLICATION_SUB_PANEL} panels.
* @hide
*/
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
/**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
系统窗口的类型定义非常多就不列举了。可以查看源码frameworks/base/core/java/android/view/WindowManager.java中的内部类LayoutParams中的定义。
WindowState 在WMS中代表一个窗口,保存了窗口相关的信息,向系统添加窗口时,在WMS的addWindow方法会创建此类的对象,它控制窗口的显示和行为。
WindowToken 一组相关窗口的容器,一个窗口的子窗口通常具有相同的token。其中变量windowType,代表窗口的类型。
AppWindowToken 继承了WindowToken,专门用于应用程序activity的显示窗口,主要是Activity创建的顶层窗口。其中的很多变量跟应用程序Activity管理相关。每个应用程序都有一个唯一的AppWindowToken
下面看看WMS的addWindow方法的代码
1. 参数检查
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, Rect outAttachedFrame, float[] outSizeCompatScale) {
Arrays.fill(outActiveControls, null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
// 检查应用是否有权限
if (res != ADD_OKAY) {
return res;
}
WindowState parentWindow = null;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
//获取要添加窗口的类型
final int type = attrs.type;
synchronized (mGlobalLock) {
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
if (displayContent == null) {
ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does " + "not exist: %d. Aborting.", displayId);
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//检查是否有权限访问该display
if (!displayContent.hasAccess(session.mUid)) {
ProtoLog.w(WM_ERROR, "Attempted to add window to a display for which the application " + "does not have access: %d. Aborting.", displayContent.getDisplayId());
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//是否添加过窗口,不能重复添加
if (mWindowMap.containsKey(client.asBinder())) {
ProtoLog.w(WM_ERROR, "Window %s is already added", client);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
//如果要添加的类型是子窗口
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
//添加的是子窗口,则父窗口必须存在。
if (parentWindow == null) {
ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
//父窗口的type不能是子窗口类型,即不能添加子窗口的子窗口
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
//TYPE_PRIVATE_PRESENTATION 表示私有的演示窗口,该窗口类型是专门为演示场景设计的,可以优化屏幕显示效果,提高演示效果和用户体验
//如果窗口类型是私有,显示设备必须是私有类型
if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
ProtoLog.w(WM_ERROR, "Attempted to add private presentation window to a non-private display. " + "Aborting.");
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {
ProtoLog.w(WM_ERROR, "Attempted to add presentation window to a non-suitable display. " + "Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
int userId = UserHandle.getUserId(session.mUid);
if (requestUserId != userId) {
try {
mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId, false /*allowAll*/, ALLOW_NON_FULL, null, null);
} catch (Exception exp) {
ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d",
requestUserId);
return WindowManagerGlobal.ADD_INVALID_USER;
}
// It's fine to use this userId
userId = requestUserId;
}
ActivityRecord activity = null;
final boolean hasParent = parentWindow != null;
// Use existing parent window token for child windows since they go in the same token
// as there parent window so we can apply the same policy on them.
//获取父窗口的token
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
// If this is a child window, we want to apply the same type checking rules as the
// parent window type.
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
final IBinder windowContextToken = attrs.mWindowContextToken;
//没有获取到token
if (token == null) {
if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type, rootType, attrs.token, attrs.packageName)) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
//没有获取到token但是有父窗口,子窗口使用已存在的父窗口token
//没有父窗口则创建WindowToken
if (hasParent) {
// Use existing parent window token for child windows.
token = parentWindow.mToken;
} else if (mWindowContextListenerController.hasListener(windowContextToken)) {
// Respect the window context token if the user provided it.
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 {
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) {
//获取到token,添加的窗口类型是应用窗口
activity = token.asActivityRecord();
if (activity == null) { //必须要有app token,否则退出
ProtoLog.w(WM_ERROR, "Attempted to add window with non-application token " + ".%s Aborting.", token);
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (activity.getParent() == null) {
ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token " + ".%s Aborting.", token);
return WindowManagerGlobal.ADD_APP_EXITING;
} else if (type == TYPE_APPLICATION_STARTING) {
if (activity.mStartingWindow != null) {//如果是app启动窗口,并已经添加过,则退出
ProtoLog.w(WM_ERROR, "Attempted to add starting window to " + "token with already existing starting window");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
if (activity.mStartingData == null) {
ProtoLog.w(WM_ERROR, "Attempted to add starting window to " + "token but already cleaned");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
}
} else if (rootType == TYPE_INPUT_METHOD) { //窗口类型是输入法窗口
if (token.windowType != TYPE_INPUT_METHOD) { //窗口类型不匹配
ProtoLog.w(WM_ERROR, "Attempted to add input method window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (rootType == TYPE_VOICE_INTERACTION) {//窗口类型是语音交互层的窗口
if (token.windowType != TYPE_VOICE_INTERACTION) { //窗口类型不匹配
ProtoLog.w(WM_ERROR, "Attempted to add voice interaction window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (rootType == TYPE_WALLPAPER) {//窗口类型是壁纸窗口
if (token.windowType != TYPE_WALLPAPER) {//窗口类型不匹配
ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
ProtoLog.w(WM_ERROR, "Attempted to add Accessibility overlay window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_TOAST) {//toast窗口
// Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName, callingUid, parentWindow);
if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
ProtoLog.w(WM_ERROR, "Attempted to add a toast window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_QS_DIALOG) {
if (token.windowType != TYPE_QS_DIALOG) {//窗口类型不匹配
ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with bad token " + "%s. Aborting.", attrs.token);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (token.asActivityRecord() != null) {// 如果是系统窗口但是存在app token,则使用新token替换。
ProtoLog.w(WM_ERROR, "Non-null activity for system window of rootType=%d", rootType);
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
token = new WindowToken.Builder(this, client.asBinder(), type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.build();
}
以上代码主要是根据传入的参数进行参数检查,确保传入的参数类型一致,主要检查的是有应用窗口,toast,壁纸窗口,输入法窗口,子窗口等。相关窗口的WindowToken在调用addWindow方法前已经创建好,所以这里需要检查一致性。其他合法窗口则需要创建新的WindowToken 参数检查完成后则开始创建窗口对象
2. 创建窗口对象
首先创建窗口对象WindowState,接下来检查窗口类型,调整flag参数,确保指定窗口不能获取焦点,检查窗口权限是否能被添加到系统中等。然后将创建的窗口对象保存到WMS的mWindowMap中。
//创建窗口对象WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
ProtoLog.w(WM_ERROR, "Adding window client %s"
+ " that is dead, aborting.", client.asBinder());
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
// DisplayPolicy用于提供UI显示的基本行为和状态
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
// 调整layout参数,确保某些类型的窗口不能获取焦点,如类型为TYPE_SYSTEM_OVERLAY,TYPE_SECURE_SYSTEM_OVERLAY,TYPE_TOAST的窗口
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
//FLAG_SLIPPERY标记需要检查是否有ALLOW_SLIPPERY_TOUCHES权限,没有则去除FLAG_SLIPPERY标记
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
//设置INPUT_FEATURE_SPY标记需要MONITOR_INPUT权限,没有则抛异常
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid, callingPid);
win.setRequestedVisibilities(requestedVisibilities);
//根据窗口类型检查窗口是否能添加到系统中
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
return res;
}
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
if (type == TYPE_TOAST) {
if (!displayContent.canAddToastWindowForUid(callingUid)) {
ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time.");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
if (addToastWindowRequiresToken
|| (attrs.flags & FLAG_NOT_FOCUSABLE) == 0
|| displayContent.mCurrentFocus == null
|| displayContent.mCurrentFocus.mOwnerUid != callingUid) {
mH.sendMessageDelayed(
mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
win.mAttrs.hideTimeoutMilliseconds);
}
}
// Switch to listen to the {@link WindowToken token}'s configuration changes when
// adding a window to the window context. Filter sub window type here because the sub
// window must be attached to the parent window, which is attached to the window context
// created window token.
if (!win.isChildWindow() && mWindowContextListenerController.hasListener(windowContextToken)) {
final int windowContextType = mWindowContextListenerController.getWindowType(windowContextToken);
final Bundle options = mWindowContextListenerController.getOptions(windowContextToken);
if (type != windowContextType) {
ProtoLog.w(WM_ERROR, "Window types in WindowContext and" + " LayoutParams.type should match! Type from LayoutParams is %d,"+ " but type from WindowContext is %d", type, windowContextType);
// We allow WindowProviderService to add window other than windowContextType,
// but the WindowProviderService won't be associated with the window's
// WindowToken.
if (!isWindowProviderService(options)) {
return WindowManagerGlobal.ADD_INVALID_TYPE;
}
} else {
mWindowContextListenerController.registerWindowContainerListener(
windowContextToken, token, callingUid, type, options);
}
}
// From now on, no exceptions or errors allowed!
res = ADD_OKAY;
if (mUseBLAST) {
res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
}
if (displayContent.mCurrentFocus == null) {
displayContent.mWinAddedSinceNullFocus.add(win);
}
if (excludeWindowTypeFromTapOutTask(type)) {
displayContent.mTapExcludedWindows.add(win);
}
win.attach();
// 将当前创建的窗口WindowState对象win,添加到保存窗口对象的map中。
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
...
3. 更新配置信息,子视图分配层级
...
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
// Check if we need to prepare a transition for replacing window first.
if (!win.mTransitionController.isShellTransitionsEnabled()
&& activity != null && activity.isVisible()
&& !prepareWindowReplacementTransition(activity)) {
// If not, check if need to set up a dummy transition during display freeze
// so that the unfreeze wait for the apps to draw. This might be needed if
// the app is relaunching.
prepareNoneTransitionForRelaunching(activity);
}
if (displayPolicy.areSystemBarsForcedConsumedLw()) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
}
if (mInTouchMode) {
res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;//加入支持触屏标志
}
if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;//加入应用可见标志
}
//设置更新输入法窗口标志
displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
boolean focusChanged = false;
if (win.canReceiveKeys()) {
//如果窗口能接受输入,计算是否会引起焦点变化 。
//调用updateFocusedWindowLocked方法重新确定系统的焦点位置
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
//计算当前窗口中需要与输入法交互的目标窗口,并将该目标窗口设置为输入法的目标窗口。
displayContent.computeImeTarget(true /* updateImeTarget */);
}
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
//为当前窗口的子视图分配层级,以便确定子视图的显示顺序和覆盖关系。
win.getParent().assignChildLayers();
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}
//更新输入法窗口的信息
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
boolean needToSendNewConfiguration =
win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
if (win.providesNonDecorInsets()) {
needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
}
if (needToSendNewConfiguration) {
displayContent.sendNewConfiguration(); //更新系统的配置
}
// This window doesn't have a frame yet. Don't let this window cause the insets change.
displayContent.getInsetsStateController().updateAboveInsetsState(
false /* notifyInsetsChanged */);
outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
getInsetsSourceControls(win, outActiveControls);
if (win.mLayoutAttached) {
outAttachedFrame.set(win.getParentWindow().getFrame());
if (win.mInvGlobalScale != 1f) {
outAttachedFrame.scale(win.mInvGlobalScale);
}
} else {
// Make this invalid which indicates a null attached frame.
outAttachedFrame.set(0, 0, -1, -1);
}
outSizeCompatScale[0] = win.getCompatScaleForClient();
}
Binder.restoreCallingIdentity(origId);
return res;
addWindow方法中首先根据窗口type类型进行参数的检查,确保窗口能正确添加,接下来创建窗口对象WindowState,该对象保存了窗口相关信息。WindowState代表一个窗口,WMS中管理所有窗口,其中一个map对象mWindowMap中会保存所有窗口的对象,当WindowState对象创建完成后,会被加入到mWindowMap中。然后更新输入法标记,系统配置等信息,为当前窗口的子视图分配层级等。可以看到在这个方法中对输入法窗口,toast,壁纸等窗口这些特殊窗口做了一些处理。 Android系统中的窗口都按照次序排列在Z轴上,新建的窗口需要确定好插入到Z轴中的位置。每个窗口根据不同的类型和情况会计算一个值,它的大小即是Z轴中出现的位置。
下一章我们再仔细介绍一下窗口Z轴位置的确定
源码地址:
DisplayContent: frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java WindowToken:frameworks/base/services/core/java/com/android/server/wm/WindowToken.java WindowState:frameworks/base/services/core/java/com/android/server/wm/WindowState.java WindowManagerService:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java WindowManager.LayoutParams:frameworks/base/core/java/android/view/WindowManager.java