• WindowInsets 分发 & WindowInsets 相关类


    WindowInsets 分发

    ViewRootImpl 在 performTraversals 时会调用 dispatchApplyInsets 方法

    private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        ...
        Rect frame = mWinFrame;
        if (mFirst) {
            ...
            dispatchApplyInsets(host);
        } else {
            ...
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ViewRootImpl::dispatchApplyInsets 定义如下

    public void dispatchApplyInsets(View host) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
        mApplyInsetsRequested = false;
        WindowInsets insets = getWindowInsets(true /* forceConstruct */);
        if (!shouldDispatchCutout()) {
            // Window is either not laid out in cutout or the status bar inset takes care of
            // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
            insets = insets.consumeDisplayCutout();
        }
        host.dispatchApplyWindowInsets(insets);//1
        mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all()));
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注释 1 处的 host,其实就是在调用 ViewRootImpl::setView 时传递进来的 View 对象,通常来说,是一个 ViewGroup 对象
    ViewGroup::dispatchApplyWindowInsets

    @Override
    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
        insets = super.dispatchApplyWindowInsets(insets);//1
        if (insets.isConsumed()) {
            return insets;
        }
        if (View.sBrokenInsetsDispatch) {
            return brokenDispatchApplyWindowInsets(insets);
        } else {
            return newDispatchApplyWindowInsets(insets);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注释 1 处先执行 View 的 dispatchApplyWindowInsets 方法
    View::dispatchApplyWindowInsets

    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
        try {
            mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
            if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {//1
                return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);//2
            } else {
                return onApplyWindowInsets(insets);//3
            }
        } finally {
            mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在注释 1 处判断,是否存在 mOnApplyWindowInsetsListener,该 Listener 可以通过 View:: setOnApplyWindowInsetsListener 来设置,如果设置过 mOnApplyWindowInsetsListener 则走注释 2 的代码,即调用 mOnApplyWindowInsetsListener 的 onApplyWindowInsets 方法;若未设置 mOnApplyWindowInsetsListener,则走注释 3 的代码,即继续执行 View::onApplyWindowInsets 的逻辑。
    注意注释 2 和注释 3 是互斥的,如果调用 View::setOnApplyWindowInsetsListener 设置了 listener,则不会走注释 3 的逻辑。
    先来看不设置 listener 的情况,即注释 2 处的代码

    onApplyWindowInsets 分支

    View::onApplyWindowInsets

    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
                && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
            return onApplyFrameworkOptionalFitSystemWindows(insets);//1
        }
        if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
            // We weren't called from within a direct call to fitSystemWindows,
            // call into it as a fallback in case we're in a class that overrides it
            // and has logic to perform.
            if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
                return insets.consumeSystemWindowInsets();//2
            }
        } else {
            // We were called from within a direct call to fitSystemWindows.
            if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
                return insets.consumeSystemWindowInsets();//3
            }
        }
        return insets;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    View::onApplyWindowInsets 根据两种情况来讨论

    分支 1

    注释 1 所在分支是第一种情况,DecorView 满足 (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0,参考下面代码
    PhoneWindow::installDecor

    private void installDecor() {
         if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
    
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeFrameworkOptionalFitsSystemWindows();
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    View::makeFrameworkOptionalFitsSystemWindows

    public void makeFrameworkOptionalFitsSystemWindows() {
        mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS;
    }
    
    • 1
    • 2
    • 3

    (mViewFlags & FITS_SYSTEM_WINDOWS) != 0 可以通过View::setFitsSystemWindows 控制
    View::setFitsSystemWindows

    public void setFitsSystemWindows(boolean fitSystemWindows) {
        setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
    }
    
    • 1
    • 2
    • 3

    Android S 上 DecorView 默认不具备 FITS_SYSTEM_WINDOWS Flag

    分支 2

    分支 1 不满足以后,第二个 if 语句用于判断是使用 fitSystemWindows 还是使用 fitSystemWindowsInt
    View

    protected boolean fitSystemWindows(Rect insets) {
        if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
            if (insets == null) {
                // Null insets by definition have already been consumed.
                // This call cannot apply insets since there are none to apply,
                // so return false.
                return false;
            }
            // If we're not in the process of dispatching the newer apply insets call,
            // that means we're not in the compatibility path. Dispatch into the newer
            // apply insets path and take things from there.
            try {
                mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
                return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();
            } finally {
                mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
            }
        } else {
            // We're being called from the newer apply insets path.
            // Perform the standard fallback behavior.
            return fitSystemWindowsInt(insets);
        }
    }
    
    private boolean fitSystemWindowsInt(Rect insets) {
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1
            Rect localInsets = sThreadLocal.get();
            boolean res = computeFitSystemWindows(insets, localInsets);//2
            applyInsets(localInsets);//3
            return res;
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    fitSystemWindows 是老方法,在 SDK 20 以后就已经不推荐使用了,主要研究 fitSystemWindowsInt
    在注释 1 处,判断当前 View 是否设置了 android:fitsSystemWindows=”true” 如果设置了,则在注释 2 处计算 Insets,并在注释 3 处应用该 Insets
    View::computeFitSystemWindows

    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
        WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
                outLocalInsets);//1
        inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
        return innerInsets.isSystemWindowInsetsConsumed();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    computeFitSystemWindows 调用 computeSystemWindowInsets 计算 insets

    public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
        boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
                || (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;//1
        if (isOptionalFitSystemWindows && mAttachInfo != null) {
            OnContentApplyWindowInsetsListener listener =
                    mAttachInfo.mContentOnApplyWindowInsetsListener;//2
            if (listener == null) {
                // The application wants to take care of fitting system window for
                // the content.
                outLocalInsets.setEmpty();
                return in;
            }
            Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(this, in);//3
            outLocalInsets.set(result.first.toRect());
            return result.second;
        } else {
            outLocalInsets.set(in.getSystemWindowInsetsAsRect());
            return in.consumeSystemWindowInsets().inset(outLocalInsets);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注释 1 的判断是针对 DecorView 的,DecorView 带有 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
    注释 2 的 mContentOnApplyWindowInsetsListener,是 PhoneWindow 根据mDecorFitsSystemWindows 调用过判断而给 ViewRootImpl 设置的监听器,用户可以通过调用 PhoneWindow::setDecorFitsSystemWindows 手动设置该值。
    PhoneWindow

    @Override
    public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
        mDecorFitsSystemWindows = decorFitsSystemWindows;
        applyDecorFitsSystemWindows();
    }
    
    private void applyDecorFitsSystemWindows() {
        ViewRootImpl impl = getViewRootImplOrNull();
        if (impl != null) {
            impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows
                    ? sDefaultContentInsetsApplier
                    : null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注释 3 在 mContentOnApplyWindowInsetsListener 不为 null 的前提下,根据 ContentOnApplyWindowInsetsListener::onContentApplyWindowInsets 计算 insets
    对于 DecorView 来说,这里调用的是下面的方法

    /**
     * @see Window#setDecorFitsSystemWindows
     */
    private static final OnContentApplyWindowInsetsListener sDefaultContentInsetsApplier =
            (view, insets) -> {
                if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) {
                    return new Pair<>(Insets.NONE, insets);
                }
                Insets insetsToApply = insets.getSystemWindowInsets();
                return new Pair<>(insetsToApply,
                        insets.inset(insetsToApply).consumeSystemWindowInsets());
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于 computeSystemWindowInsets 方法来说,它的 outLocalInsets 参数指应用的 insets 矩形,返回值用于标记该 insets 是否被消费过,如果 in.consumeSystemWindowInsets() 则说明该 insets 被消费了
    回到 computeFitSystemWindows 方法

    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
        WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
                outLocalInsets);//1
        inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
        return innerInsets.isSystemWindowInsetsConsumed();//2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在注释 2 处根据当前 insets 是否被消费过,返回 true/false
    View::fitSystemWindowsInt

    private boolean fitSystemWindowsInt(Rect insets) {
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1
            Rect localInsets = sThreadLocal.get();
            boolean res = computeFitSystemWindows(insets, localInsets);//2
            applyInsets(localInsets);//3
            return res;
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    回到 View::fitSystemWindowsInt 的注释 3,在这里将应用 Insets
    View::applyInsets

    private void applyInsets(Rect insets) {
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        mUserPaddingLeftInitial = insets.left;
        mUserPaddingRightInitial = insets.right;
        internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里将通过 internalSetPadding 将 insets 以 padding 的形式给 View 设置上
    后续,回到 View::onApplyWindowInsets

    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
                && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
            return onApplyFrameworkOptionalFitSystemWindows(insets);//1
        }
        if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
            // We weren't called from within a direct call to fitSystemWindows,
            // call into it as a fallback in case we're in a class that overrides it
            // and has logic to perform.
            if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
                return insets.consumeSystemWindowInsets();//2
            }
        } else {
            // We were called from within a direct call to fitSystemWindows.
            if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
                return insets.consumeSystemWindowInsets();//3
            }
        }
        return insets;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    若 View::fitSystemWindowsInt 的注释 2 返回 true (即被消耗),则也会让 View::onApplyWindowInsets 的参数也变为被消耗的 Insets (注释 2、注释 3)
    回到 ViewGroup::dispatchApplyWindowInsets

    @Override
    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
        insets = super.dispatchApplyWindowInsets(insets);//1
        if (insets.isConsumed()) {//2
            return insets;
        }
        if (View.sBrokenInsetsDispatch) {//3
            return brokenDispatchApplyWindowInsets(insets);
        } else {
            return newDispatchApplyWindowInsets(insets);//4 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    若 insets 在注释 2 处判断为被消耗,则不会进行分发 WindowInsets,否则会继续执行注释 3 的逻辑判断是否中断分发, sBrokenInsetsDispatch 为 true 的条件如下

    sBrokenInsetsDispatch = targetSdkVersion < Build.VERSION_CODES.R;
    
    • 1

    在 Android S 系统中,该值为 false,所以继续看注释 4 处的逻辑
    ViewGroup::newDispatchApplyWindowInsets

    private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).dispatchApplyWindowInsets(insets);//1
        }
        return insets;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在注释 1 处,开始调用子 View 的 dispatchApplyWindowInsets 方法,让 WindowInsets 继续向子分发
    对于低于 R 版本的系统来说,替代 newDispatchApplyWindowInsets 的是 brokenDispatchApplyWindowInsets
    ViewGroup::brokenDispatchApplyWindowInsets

    private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
            if (insets.isConsumed()) {
                break;
            }
        }
        return insets;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果 app targetSdkVersion < 30 ,如果某个节点消费了 Insets,所有没遍历到的节点都不会收到 WindowInsets 的分发;
    当 app 运行在 Android 11 以上版本的设备上且 targetSdkVersion >= 30,如果某个节点消费了 Insets,该节点的所有子节点不会收到 WindowInsets 分发。
    区别在于一个节点消费了 WindowInsets 分发,其兄弟节点似乎否还能收到分发。

    WindowInsets 相关类

    InsetsSource

    表示为单个类型 insets 的具体信息,例如 Insets 大小、是否可见等信息

    InsetsState

    InsetsState 持有当前系统 insets 的状态信息,其内部存有一个 InsetsSource 类型的数组 mSources
    android.view.InsetsState#mSources

    private final InsetsSource[] mSources = new InsetsSource[SIZE];
    
    • 1

    该数组用于保存当前屏幕中所有类型的 insets
    当一个 Window 在客户端被添加、更新的时候,ViewRootImpl 将会调用 relayoutWindow 方法,从 WMS 获取当前 Window 的 InsetsState 信息
    android.view.ViewRootImpl#relayoutWindow

    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
    
        float appScale = mAttachInfo.mApplicationScale;
        boolean restore = false;
        if (params != null && mTranslator != null) {
            restore = true;
            params.backup();
            mTranslator.translateWindowLayout(params);
        }
    
        if (params != null) {
            if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
    
            if (mOrigWindowType != params.type) {
                // For compatibility with old apps, don't crash here.
                if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    Slog.w(mTag, "Window type can not be changed after "
                            + "the window is added; ignoring change of " + mView);
                    params.type = mOrigWindowType;
                }
            }
        }
    
        long frameNumber = -1;
        if (mSurface.isValid()) {
            frameNumber = mSurface.getNextFrameNumber();
        }
    
        int relayoutResult = mWindowSession.relayout(mWindow, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize);//1
        mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
        if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                mSurface.copyFrom(mSurfaceControl);
            } else {
                final Surface blastSurface = getOrCreateBLASTSurface();
                // If blastSurface == null that means it hasn't changed since the last time we
                // called. In this situation, avoid calling transferFrom as we would then
                // inc the generation ID and cause EGL resources to be recreated.
                if (blastSurface != null) {
                    mSurface.transferFrom(blastSurface);
                }
            }
            if (mAttachInfo.mThreadedRenderer != null) {
                if (HardwareRenderer.isWebViewOverlaysEnabled()) {
                    addPrepareSurfaceControlForWebviewCallback();
                    addASurfaceTransactionCallback();
                }
                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
            }
        } else {
            destroySurface();
        }
    
        mPendingAlwaysConsumeSystemBars =
                (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
    
        if (restore) {
            params.restore();
        }
    
        if (mTranslator != null) {
            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
            mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
            mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
        }
        setFrame(mTmpFrames.frame);//2
        mWillMove = false;
        mWillResize = false;
        mInsetsController.onStateChanged(mTempInsets);//3
        mInsetsController.onControlsChanged(mTempControls);//4
        return relayoutResult;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    在注释 1 处,调用 IWindowSession::relayout 方法,IWindowSession 是 ViewRootImpl 与 WindowMangerService 的代理,从注释 1 处得到该当前 Window 的框架大小 mTmpFrames、以及 Insets 情况(mTempInsets、mTempControls),并在注释 2 、注释 3 、注释 4 将这些属性同步给 ViewRootImpl
    注释 3、4 中的 mInsetsController 是 InsetsController 类型的实例,它实现了 WindowInsetsController 接口

    WindowInsetsController

    WindowInsetsController 是客户端用于控制 Window 的管理器,它在客户端的实现类是 InsetsController,每个 Window 会有一个 ViewRootImpl,每个 ViewRootImpl 都将持有一个 InsetsController 实例,客户端程序可以通过 View::getWindowInsetsController 得到该实例,对当前 Window 的 insets 进行控制
    android.view.View#getWindowInsetsController

    /**
     * Retrieves the single {@link WindowInsetsController} of the window this view is attached to.
     *
     * @return The {@link WindowInsetsController} or {@code null} if the view is neither attached to
     *         a window nor a view tree with a decor.
     * @see Window#getInsetsController()
     */
    public @Nullable WindowInsetsController getWindowInsetsController() {
        if (mAttachInfo != null) {
            return mAttachInfo.mViewRootImpl.getInsetsController();
        }
        ViewParent parent = getParent();
        if (parent instanceof View) {
            return ((View) parent).getWindowInsetsController();
        } else if (parent instanceof ViewRootImpl) {
            // Between WindowManager.addView() and the first traversal AttachInfo isn't set yet.
            return ((ViewRootImpl) parent).getInsetsController();
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    InsetsSourceControl

    用于控制单独 InsetsSource 的控制器,被 InsetsController 所持有,同样也是在 ViewRootImpl:: relayoutWindow 时从 WMS 中获得

    InsetsSourceConsumer

    对单一 InsetsSource 的消费者,其内部持有 InsetsSourceControl,可以控制其leash的可见性和动画。
    InsetsSourceControl 持有 InsetsSourceConsumer 的创造器,会在 InsetsController::getSourceConsumer 时创建实例
    android.view.InsetsController#getSourceConsumer

    public InsetsController(Host host) {
        this(host, (controller, type) -> {
            if (type == ITYPE_IME) {
                return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller);
            } else {
                return new InsetsSourceConsumer(type, controller.mState, Transaction::new,
                        controller);
            }
        }, host.getHandler());//1
    }
    
    @VisibleForTesting
    public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {
        InsetsSourceConsumer controller = mSourceConsumers.get(type);
        if (controller != null) {
            return controller;
        }
        controller = mConsumerCreator.apply(this, type);//2
        mSourceConsumers.put(type, controller);
        return controller;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    注释 2 处实际调用的是注释 1 的箭头函数

    InsetsStateController

    负责在服务端管理全局 insets 状态信息,InsetsStateController 中的 mState 代表当前屏幕的所以类型的 Insets 状态信息
    InsetsStateController 还持有不同类型的 InsetsSourceProvider,通常 DisplayContent、InsetsPolicy 等模块会通过 InsetsStateController 访问某一类型的 InsetsSourceProvider 实例

    InsetsSourceProvider

    负责在服务端根据 WindowState 配置、生成特定类型的 InsetsSource
    com.android.server.wm.InsetsSourceProvider#getSource

    /**
     * Updates the window that currently backs this source.
     *
     * @param win The window that links to this source.
     * @param frameProvider Based on display frame state and the window, calculates the resulting
     *                      frame that should be reported to clients.
     * @param imeFrameProvider Based on display frame state and the window, calculates the resulting
     *                         frame that should be reported to IME.
     */
    void setWindow(@Nullable WindowState win,
            @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
            @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
        if (mWin != null) {
            if (mControllable) {
                mWin.setControllableInsetProvider(null);
            }
            // The window may be animating such that we can hand out the leash to the control
            // target. Revoke the leash by cancelling the animation to correct the state.
            // TODO: Ideally, we should wait for the animation to finish so previous window can
            // animate-out as new one animates-in.
            mWin.cancelAnimation();
            mWin.mProvidedInsetsSources.remove(mSource.getType());
        }
        ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
        mWin = win;
        mFrameProvider = frameProvider;
        mImeFrameProvider = imeFrameProvider;
        if (win == null) {
            setServerVisible(false);
            mSource.setFrame(new Rect());
            mSource.setVisibleFrame(null);
        } else {
            mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
            if (mControllable) {
                mWin.setControllableInsetProvider(this);
                if (mPendingControlTarget != null) {
                    updateControlForTarget(mPendingControlTarget, true /* force */);
                    mPendingControlTarget = null;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    InsetsSource getSource() {
        return mSource;
    }
    
    • 1
    • 2
    • 3

    InsetsPolicy

    被 DisplayContent 创建并持有,InsetsPolicy 负责管理不同类型 Inests 的 InsetsControlTarget 实例,同时 InsetsPolicy 还持有 InsetsStateController 类型实例,也提供了一些 Inests 相关行为策略
    DisplayContent 对应一块屏幕的内容,一块屏幕对应一个 DisplayContent,一个 DisplayContent 下持有且仅一个 InsetsStateController 实例与 InsetsPolicy 实例,

    InsetsControlTarget

    InsetsControlTarget 是一个接口,代表可以控制 Insets 状态的对象,WindowState 实现了该接口,由于 WindowState 是每个 Window 在 WMS 中的状态描述,所以每个 Window 都具有控制 Insets 的能力,不同类型的 Insets 可能被不同的 Window 控制,可以通过 dump InsetsStateController 的信息查看当前 Insets 被谁控制
    com.android.server.wm.InsetsStateController#dump

    void dump(String prefix, PrintWriter pw) {
        pw.println(prefix + "WindowInsetsStateController");
        prefix = prefix + "  ";
        mState.dump(prefix, pw);
        pw.println(prefix + "Control map:");
        for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
            pw.print(prefix + "  ");
            pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
                    + mTypeControlTargetMap.valueAt(i));
        }
        pw.println(prefix + "InsetsSourceProviders:");
        for (int i = mProviders.size() - 1; i >= 0; i--) {
            mProviders.valueAt(i).dump(pw, prefix + "  ");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    编程入门之字符编码与乱码
    Compose预处理组件大比拼:性能、应用场景和可视化对比总结
    30 华三华为STP
    Linux系统编程系列之互斥锁和读写锁
    创建vMix虚拟集
    系统开发系列 之java反射以及应用
    xctf攻防世界 MISC之CatFlag
    Google Earth Engine(GEE)——基于 MCD64A1 的 GlobFire 日常火灾数据集
    MySQL:读写分离原理和实践
    实现文档AI搜索,提高问题解决效率
  • 原文地址:https://blog.csdn.net/lzs781/article/details/127927082