3【Android 12】DisplayArea层级结构(二) —— 向DisplayArea层级结构添加窗口

1,462 阅读3分钟

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在父容器中能够按照层级值的大小插入到合适的位置。