3 向DisplayArea层级结构添加窗口
根据之前的分析,我们知道了:
-
每种窗口类型,都可以通过WindowManagerPolicy.getWindowLayerFromTypeLw方法,返回一个相应的层级值。
-
DisplayArea层级结构中的每一个DisplayArea,都包含着一个层级值范围,这个层级值范围表明了这个DisplayArea可以容纳哪些类型的窗口。
那么我们可以合理进行推断添加窗口的一般流程:
WMS启动的时候添加DisplayContent的时候,首先是以该DisplayContent为根节点,创建了一个完整的DisplayArea层级结构。后续每次添加窗口的时候,都根据该窗口的类型,在DisplayArea层级结构中为该窗口寻找一个合适的父节点,然后将这个窗口添加到该父节点之下。
另外根据上面的分析还可以知道,在DisplayArea层级结构中,可以直接容纳窗口的父节点,有三种类型:
-
容纳App窗口的TaskDisplayArea
-
容纳输入法窗口的ImeContainer
-
容纳其他非App类型窗口的DisplayArea.Tokens
这里我们分析一般流程,看下非App类型的窗口,如StatusBar、NavigationBar等窗口,是如何添加到DisplayArea中的。
注意,我们说的每一个窗口,指的是WindowState。但是DisplayArea.Tokens的定义是:
/**
* DisplayArea that contains WindowTokens, and orders them according to their type.
*/
public static class Tokens extends DisplayArea<WindowToken> {
DisplayArea.Tokens是WindowToken的容器,因此DisplayArea.Tokens无法直接添加WindowState。
WindowToken则是WindowState的容器,每次新WindowState创建的时候,都会为这个WindowState创建一个WindowToken对象,然后将这个新创建的WindowState放入其中。
因此我们实际分析的,是WindowToken如何添加到DisplayArea层级结构中的。
最后需要修正一下说法,因为WindowToken不再是一个DisplayArea,而是一个WindowContainer。我们之前分析的DisplayArea层级结构,其中每一个节点都是一个DisplayArea,但是现在随着WindowToken的加入,DisplayArea层级结构这个说法需要转化为更通用的结构,也就是WindowContainer层级结构。
3.1 WindowManagerService.addWindow
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
// ......
synchronized (mGlobalLock) {
// ......
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
// ......
if (token == null) {
// ......
if (hasParent) {
// ......
} 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();
}
}
// ......
}
// ......
}
调用WindowToken.Builder.build创建一个WindowToken对象。
3.2 WindowToken.Builder.build
WindowToken build() {
return new WindowToken(mService, mToken, mType, mPersistOnEmpty, mDisplayContent,
mOwnerCanManageAppTokens, mRoundedCornerOverlay, mFromClientToken, mOptions);
}
3.3 WindowToken.init
protected WindowToken(WindowManagerService service, IBinder _token, int type,
boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,
boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {
super(service);
token = _token;
windowType = type;
mOptions = options;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
mRoundedCornerOverlay = roundedCornerOverlay;
mFromClientToken = fromClientToken;
if (dc != null) {
dc.addWindowToken(token, this);
}
}
在WindowToken构造方法中,调用DisplayContent.addWindowToken将WindowToken添加到以DisplayContent为根节点的WindowContainer层级结构中。
3.4 DisplayContent.addWindowToken
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
// ......
mTokenMap.put(binder, token);
if (token.asActivityRecord() == null) {
// Set displayContent for non-app token to prevent same token will add twice after
// onDisplayChanged.
// TODO: Check if it's fine that super.onDisplayChanged of WindowToken
// (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent assigned.
token.mDisplayContent = this;
// Add non-app token to container hierarchy on the display. App tokens are added through
// the parent container managing them (e.g. Tasks).
final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
da.addChild(token);
}
}
窗口可以分为App窗口和非App窗口。对于App窗口,则是由更细致的WindowToken的子类,ActivityRecord来存放。这里我们分析非App窗口的流程。
分两步走:
1)、调用DisplayContent.findAreaForToken为当前WindowToken寻找一个合适的父容器,DisplayArea.Tokens对象。
2)、将WindowToken添加到父容器中。
3.5 DisplayContent.findAreaForToken
/**
* Finds the {@link DisplayArea} for the {@link WindowToken} to attach to.
* <p>
* Note that the differences between this API and
* {@link RootDisplayArea#findAreaForTokenInLayer(WindowToken)} is that this API finds a
* {@link DisplayArea} in {@link DisplayContent} level, which may find a {@link DisplayArea}
* from multiple {@link RootDisplayArea RootDisplayAreas} under this {@link DisplayContent}'s
* hierarchy, while {@link RootDisplayArea#findAreaForTokenInLayer(WindowToken)} finds a
* {@link DisplayArea.Tokens} from a {@link DisplayArea.Tokens} list mapped to window layers.
* </p>
*
* @see DisplayContent#findAreaForTokenInLayer(WindowToken)
*/
DisplayArea findAreaForToken(WindowToken windowToken) {
return findAreaForWindowType(windowToken.getWindowType(), windowToken.mOptions,
windowToken.mOwnerCanManageAppTokens, windowToken.mRoundedCornerOverlay);
}
为传入的WindowToken找到一个DisplayArea对象来添加进去。
3.6 DisplayContent.findAreaForWindowType
DisplayArea findAreaForWindowType(int windowType, Bundle options,
boolean ownerCanManageAppToken, boolean roundedCornerOverlay) {
// TODO(b/159767464): figure out how to find an appropriate TDA.
if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {
return getDefaultTaskDisplayArea();
}
// Return IME container here because it could be in one of sub RootDisplayAreas depending on
// the focused edit text. Also, the RootDisplayArea choosing strategy is implemented by
// the server side, but not mSelectRootForWindowFunc customized by OEM.
if (windowType == TYPE_INPUT_METHOD || windowType == TYPE_INPUT_METHOD_DIALOG) {
return getImeContainer();
}
return mDisplayAreaPolicy.findAreaForWindowType(windowType, options,
ownerCanManageAppToken, roundedCornerOverlay);
}
-
如果是App窗口,那么返回默认的TaskDisplayArea对象。
-
如果是输入法窗口,那么返回ImeContainer。
-
如果是其他类型,继续寻找。
和我们之前分析的逻辑一致。
3.7 DisplayAreaPolicyBuilder.Result.findAreaForWindowType
@Override
public DisplayArea.Tokens findAreaForWindowType(int type, Bundle options,
boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
return mSelectRootForWindowFunc.apply(type, options).findAreaForWindowTypeInLayer(type,
ownerCanManageAppTokens, roundedCornerOverlay);
}
3.8 RootDisplayArea.findAreaForWindowTypeInLayer
/** @see #findAreaForTokenInLayer(WindowToken) */
@Nullable
DisplayArea.Tokens findAreaForWindowTypeInLayer(int windowType, boolean ownerCanManageAppTokens,
boolean roundedCornerOverlay) {
int windowLayerFromType = mWmService.mPolicy.getWindowLayerFromTypeLw(windowType,
ownerCanManageAppTokens, roundedCornerOverlay);
if (windowLayerFromType == APPLICATION_LAYER) {
throw new IllegalArgumentException(
"There shouldn't be WindowToken on APPLICATION_LAYER");
}
return mAreaForLayer[windowLayerFromType];
}
计算出该窗口的类型对应的层级值windowLayerFromType,然后从mAreaForLayer数组中,找到windowLayerFromType对应的那个DisplayArea.Tokens对象。
mAreaForLayer成员变量,定义为:
/** Mapping from window layer to {@link DisplayArea.Tokens} that holds windows on that layer. */
private DisplayArea.Tokens[] mAreaForLayer;
它里面的数据是在2.3.6节中填充,保存的是层级值到对应DisplayArea.Tokens对象的一个映射。
那么只要传入窗口类型,就可以通过WindowManagerPolicy.getWindowLayerFromTypeLw得到该窗口类型对应的层级值,然后根据该层级值从mAreaForLayer拿到容纳当前WindowToken的父容器,一个DisplayArea.Tokens对象。
3.9 DisplayArea.Tokens.addChild
void addChild(WindowToken token) {
addChild(token, mWindowComparator);
}
这里调用了WindowContainer.addChild:
/**
* Adds the input window container has a child of this container in order based on the input
* comparator.
* @param child The window container to add as a child of this window container.
* @param comparator Comparator to use in determining the position the child should be added to.
* If null, the child will be added to the top.
*/
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
if (!child.mReparenting && child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
if (positionToAdd == -1) {
mChildren.add(child);
} else {
mChildren.add(positionToAdd, child);
}
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
传入了一个Comparator:
private final Comparator<WindowToken> mWindowComparator =
Comparator.comparingInt(WindowToken::getWindowLayerFromType);
很明显,在将WindowToken添加到父容器的时候,将新添加的WindowToken的层级值和父容器中的其他WindowToken的层级值进行比较,保证新添加的WindowToken在父容器中能够按照层级值的大小插入到合适的位置。