【Android 14源码分析】WMS-窗口显示-第二步:relayoutWindow -2

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
                  
                  
                                           – 服装学院的IT男

本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS

正文

窗口显示流程一共分为以下5篇:

窗口显示-流程概览与应用端流程分析

窗口显示-第一步:addWindow

窗口显示-第二步:relayoutWindow -1

窗口显示-第二步:relayoutWindow -2

窗口显示-第三步:finishDrawingWindow

上篇说过 relayoutWindow 流程主要做了两件事:

    1. 通过 createSurfaceControl 创建SurfaceControl
    1. 通过 WindowSurfacePlacer::performSurfacePlacement 计算窗口大小和摆放 Surface

这篇主要分析核心方法:WindowSurfacePlacer::performSurfacePlacement

根据上篇的分析,relayoutWindow 流程会触发 WindowSurfacePlacer::performSurfacePlacement 的执行,需要注意的是会触发执行这个方法的逻辑非常多,为什么呢?

比如写 App 的时候界面有变化了,或者我们手动执行“View::requestLayout”就会触发界面重绘,也就是执行 View 绘制三部曲,其中第二步就是 layout。那为什么要执行 layout 呢?

因为界面上有 View 的添加移除,或者横竖屏切换等情况,那其他 View 的位置很可能就会受影响,所以为了保证界面上 View 所在位置的正确,就会触发一次 layout 重新计算每个 View 所在的位置。

触发 layout 是 ViewRootImpl 触发的,他只需要对整个 View 树的 RootView(DecorView)触发layout就行,然后 RootView(DecorView)内部会递归触发整个 View 树的 layout逻辑,从而保证整个 View 树的每一个 View 都出在正确的位置。

这里提取2个 View 层 layout 逻辑的信息:

    1. 目的是确保界面 View 树中的各个 View 处在正确的位置
    1. 触发逻辑是从 RootView(DecorView) 开始递归执行

其实 WMS 对窗口的管理也是和 View 管理的一样的,窗口有添加移除,或者屏幕旋转等场景,为了确保手机屏幕上各个窗口处在正确的位置,显示正确的大小。所以会触发执行 WindowSurfacePlacer::performSurfacePlacement 方法。

作为 WMS 的核心方法之一,WindowSurfacePlacer::performSurfacePlacement 方法做的事情其实也远不仅这些,但是当前是分析 relayoutWindow 流程进入了这个方法做的事,所以还是只关心 relayoutWindow 流程触发需要做哪些事情就好。

然后对这个方法有个印象,因为以后会经常看这个方法,毕竟界面上有点风吹操作都要触发 WindowSurfacePlacer::performSurfacePlacement 方法的执行。

本篇的调用链和时序图如下:

WindowSurfacePlacer::performSurfacePlacement
    WindowSurfacePlacer::performSurfacePlacementLoop
        RootWindowContainer::performSurfacePlacement
            RootWindowContainer::performSurfacePlacementNoTrace
                RootWindowContainer::applySurfaceChangesTransaction
                    DisplayContent::applySurfaceChangesTransaction
                        DisplayContent::performLayout 
                            DisplayContent::performLayoutNoTrace
                                DisplayContent::mPerformLayout 
                                    DisplayPolicy::layoutWindowLw 
                                        WindowLayout::computeFrames   -- 计算窗口大小,保存在 sTmpClientFrames中
                                        WindowState::setFrames        -- 将计算结果 sTmpClientFrames 的数据设置给窗口

在这里插入图片描述

1. WindowSurfacePlacer::performSurfacePlacement

# WindowSurfacePlacer

    // 控制是否需要继续执行 performSurfacePlacementLoop方法
    private boolean mTraversalScheduled;
    // 延迟layout就会+1
    private int mDeferDepth = 0;

    final void performSurfacePlacement(boolean force) {
        if (mDeferDepth > 0 && !force) {
            mDeferredRequests++;
            return;
        }
        // 最大次数循环为6次
        int loopCount = 6;
        do {
            // 设置为false
            mTraversalScheduled = false;
            // 重点方法
            performSurfacePlacementLoop();
            // 移除Handler的处理
            mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
            loopCount--;
        // 结束条件为 mTraversalScheduled 不为false 和 loopCount大于0 ,也就最多6次
        } while (mTraversalScheduled && loopCount > 0);
        mService.mRoot.mWallpaperActionPending = false;
    }

relayoutWindow 方法调用的是传递的参数是 true ,那第一个if是走不进去的,主要看后面的逻辑控制。

里面的 performSurfacePlacementLoop() 方法是重点,在分析这个方法之前,先确认下停止循环的2个条件:

    1. loopCount > 0 这个条件比较简单,这个循环最多执行6次,为什么是6咱也不知道,查了一下说是google工程师根据经验设置,如果执行6次循环还没处理好Surface那肯定是出现问题了。(debug发现一般就执行1次,最多看到执行2次的情况)
    1. mTraversalScheduled 这个变量但是执行循环的时候就设置为 false ,说明正常情况下执行一次就不需要再执行了。

这里也其实要注意,bool 类型默认是 false ,所以找到在是哪里将 mTraversalScheduled 设置为 true,其实就是找到了什么情况下需要执行 performSurfacePlacementLoop 方法。

继续下一步 performSurfacePlacementLoop 方法的内容。

# WindowSurfacePlacer
    private void performSurfacePlacementLoop() {

        ......
        // 重点*1. 对所有窗口执行布局操作
        mService.mRoot.performSurfacePlacement();
        // 布局完成
        mInLayout = false;
        // 若需要布局,(Root检查每个DC是否需要)
        if (mService.mRoot.isLayoutNeeded()) {
            if (++mLayoutRepeatCount < 6) {
                // 重点*2. 布局次数小于6次,则需要再次请求布局
                requestTraversal();
            } else {
                Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
                mLayoutRepeatCount = 0;
            }
        } else {
            mLayoutRepeatCount = 0;
        }   
    }
    1. 执行 RootWindowContainer::performSurfacePlacement 这个代表对屏幕进行一次 layout ,后续的分析都在这
    1. 如果“mService.mRoot.isLayoutNeeded()”满足就执行 requestTraversal ,这个方法会将 mTraversalScheduled 变量设置为 true
    1. “mService.mRoot.isLayoutNeeded()” 的返回其实受 DisplayContent 下的 mLayoutNeeded 变量控制,有任意一个屏幕为 true 就说明还需要一次 layout

主流程等会再看,先看一下 WindowSurfacePlacer::requestTraversal 的逻辑。

# WindowSurfacePlacer
    void requestTraversal() {
        if (mTraversalScheduled) {
            return;
        }

        // Set as scheduled even the request will be deferred because mDeferredRequests is also
        // increased, then the end of deferring will perform the request.
        // 还需要一次
        mTraversalScheduled = true;
        if (mDeferDepth > 0) {
            mDeferredRequests++;
            if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));
            return;
        }
        // 通过Handler触发
        mService.mAnimationHandler.post(mPerformSurfacePlacement);
    }

当前不必纠结于执行几次的逻辑,这里的东西还挺复杂的,还会涉及到延迟 layout 等待,当前这块以简单的场景看就好了。

给个不是很精确但是覆盖百分之95以上场景的结论:

performSurfacePlacement 就是一次窗口的 layout ,是不是还有再执行的条件控制在 DisplayContent 下的 mLayoutNeeded 变量控制。如果这个变量为 false 则说明这次 layout 还有事情没完成,还要再来一次。

具体几次6次8次的没必要过于纠结,但是需要注意 “mService.mRoot.isLayoutNeeded()”如果不满足,则就不会再次触发了,对这些有个印象即可,主要看 RootWindowContainer::performSurfacePlacement 方法。

1.1 layout操作–RootWindowContainer::performSurfacePlacement

# RootWindowContainer

    // 这个方法加上了trace
    void performSurfacePlacement() {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
        try {
            performSurfacePlacementNoTrace();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
    // 主要干活的还是这个
    void performSurfacePlacementNoTrace() {
        ......
        // 1. 如果需要,则更新焦点
        if (mWmService.mFocusMayChange) {
            mWmService.mFocusMayChange = false;
            mWmService.updateFocusedWindowLocked(
                    UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
        }
        ......
        // Trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
        // 开启事务
        mWmService.openSurfaceTransaction();
        try {
            // 2.. 处理事务(执行窗口尺寸计算,surface状态变更等操作)
            applySurfaceChangesTransaction();
        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            // 关闭事务,做事务提交
            mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        ......
        // 3. Activity 切换事务处理,
        // 条件满足也会将窗口状态设置为HAS_DRAW 流程
        checkAppTransitionReady(surfacePlacer);
        ......
        // 再次判断是否需要处理焦点变化
        if (mWmService.mFocusMayChange) {
            mWmService.mFocusMayChange = false;
            mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
                    false /*updateInputWindows*/);
        }
        ......
        // 4. 如果过程中size或者位置变化,则通知客户端重新relayout
        handleResizingWindows();
        ......
        // 5. 销毁不可见的窗口
        i = mWmService.mDestroySurface.size();
        if (i > 0) {
            do {
                i--;
                WindowState win = mWmService.mDestroySurface.get(i);
                win.mDestroying = false;
                final DisplayContent displayContent = win.getDisplayContent();
                if (displayContent.mInputMethodWindow == win) {
                    displayContent.setInputMethodWindowLocked(null);
                }
                if (displayContent.mWallpaperController.isWallpaperTarget(win)) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
                win.destroySurfaceUnchecked();
            } while (i > 0);
            mWmService.mDestroySurface.clear();
        }
        ......
    }

这个方法处理的事情非常多

    1. 焦点相关
    1. applySurfaceChangesTransaction 这个是当前分析的重点,主要是处理窗口大小和Surface的
    1. checkAppTransitionReady 是处理 Activity 切换的事务
    1. 如果这次 layout 有窗口尺寸改变了,就需要窗口进行 resize 操作
    1. 销毁掉不需要的窗口

这个方法比较长,干的事也比较多,而且执行的频率也很高。(可以自己加个log或者抓trace看一下)

其他的流程目前不看,只关心 applySurfaceChangesTransaction 方法。

可以看到在执行 applySurfaceChangesTransaction 方法的前后都 SurfaceTransaction 的打开和关闭,那说明这个方法内部肯定是有 Surface 事务的处理。

后面的 RootWindowContainer::applySurfaceChangesTransaction 方法是 relayoutWindow 流程的核心,在看后面之前,先对前面这块比较混乱流程整理一个流程图:

在这里插入图片描述

2. RootWindowContainer::applySurfaceChangesTransaction

# RootWindowContainer
    private void applySurfaceChangesTransaction() {
        ......
        // 正常情况就一个屏幕
        final int count = mChildren.size();
        for (int j = 0; j < count; ++j) {
            final DisplayContent dc = mChildren.get(j);
            dc.applySurfaceChangesTransaction();
        }
        ......
    }

遍历每个屏幕执行 DisplayContent::applySurfaceChangesTransaction

# DisplayContent
    private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();

    void applySurfaceChangesTransaction() {
        ......
        // 置空
        mTmpUpdateAllDrawn.clear();
        ......
        // 重点* 1. 执行布局,该方法最终会调用performLayoutNoTrace,计算窗口的布局参数
        performLayout(true /* initial */, false /* updateInputWindows */);
        pendingLayoutChanges = 0;

        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyPostLayoutPolicy");
        try {
            mDisplayPolicy.beginPostLayoutPolicyLw();
            // 对所有窗口执行布局策略
            forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
            mDisplayPolicy.finishPostLayoutPolicyLw();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        ......
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
        try {
            // 重点* 2. 遍历所有窗口,主要是改变窗口状态设置为READY_TO_SHOW,当前逻辑不满足,不会执行最终设置
            forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        // 重点* 3. finishDrawing()流程,条件满足触发提交Surface到SurfaceFlinger 
        prepareSurfaces();
        ......
    }

这个方法其实有3个重点,但是因为当前分析的是窗口执行 relayoutWindow 过来的逻辑,窗口下的 View 应用端都还没进行绘制,所以后面2个重点内部都会因为条件不满足被 return 。
不过2个重点也是后面学习需要分析的流程,所以先留下个印象。

Framework的流程很复杂,基本上没有一行代码是多余的,如果每个代码都看,每个分支都认真分析,那可能只有AI能完成了,所以分析某个流程只关心流程中主要代码就可以了。
当前流程主要关注1个重点:

    1. performLayout :最终会执行创建大小的计算

2.1 layout之-计算窗口大小

# DisplayContent
    // 标记是否需要执行layout
    private boolean mLayoutNeeded;

    boolean isLayoutNeeded() {
        return mLayoutNeeded;
    }
    // 根据前面流程,如果为false则表示不会执行循环
    private void clearLayoutNeeded() {
        if (DEBUG_LAYOUT) Slog.w(TAG_WM, "clearLayoutNeeded: callers=" + Debug.getCallers(3));
        mLayoutNeeded = false;
    }
    
    void performLayout(boolean initial, boolean updateInputWindows) {
        // 加上trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
        try {
            performLayoutNoTrace(initial, updateInputWindows);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
    // 主要方法
    private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
        // 判断是否需要布局,不需要则直接返回,内部是通过mLayoutNeeded判断
        if (!isLayoutNeeded()) {
            return;
        }
        // 将mLayoutNeeded设置为flase
        clearLayoutNeeded();
        ......
        // 重点* 1. 对所有顶级窗口进行布局
        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

        // 重点* 2. 处理子窗口的布局
        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
        ......
    }

DisplayContent::performLayout 方法也是为了加 trace 所以还是得看 DisplayContent::performLayoutNoTrace 方法,主要就是2个forAllWindows,这个方法在之前 Activity 启动的流程讲过类似的,就是将按照第二个参数的顺序,从上到下或者从下至上遍历每个 Window 让其执行第一个参数的 lambda 表达式,所以只要看看具体的 lambda 表达式即可。

2.2 mPerformLayout 和 mPerformLayoutAttached

# DisplayContent
    private final Consumer<WindowState> mPerformLayout = w -> {
        // 如果当前窗口为子窗口则直接返回
        if (w.mLayoutAttached) {
            return;
        }

        // 先判断当前窗口是否会不可见
        final boolean gone = w.isGoneForLayout();
        // 如果窗口不是不可见的,或者窗口没有框架,或者窗口需要布局
        if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {
            ......
            // 重点*1. 调用DisplayPolicy::layoutWindowLw
            getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
            ......
            if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrame()
                    + " mParentFrame=" + w.getParentFrame()
                    + " mDisplayFrame=" + w.getDisplayFrame());
        }
    };

    private final Consumer<WindowState> mPerformLayoutAttached = w -> {
        // 如果不是子窗口则返回
        if (!w.mLayoutAttached) {
            return;
        }

        if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
                || w.mLayoutNeeded) {
            ......
            getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
            w.mLayoutSeq = mLayoutSeq;
            if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
                    + " mParentFrame=" + w.getParentFrame()
                    + " mDisplayFrame=" + w.getDisplayFrame());
        }
    };

2.2.1 mLayoutAttached 变量的意义

这2个 lambda 表达式基本上是一样的主要就是执行“getDisplayPolicy().layoutWindowLw”,但是区别在于方法最前面对 w.mLayoutAttached 的判断,这个属性是什么意思呢?
这个属性在WindowState构造的时候赋值。

# WindowState
    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
            PowerManagerWrapper powerManagerWrapper) {
                ......
                if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
                    ......
                    mLayoutAttached = mAttrs.type !=
                            WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
                    ......
                } else {
                    ......
                    mLayoutAttached = false;
                    ......
                }
                ......
            }

其实直接理解为:只要不是子窗口都为 false 就可以,根据我的 debug 和堆栈信息也确实如此。那也就是说 mPerformLayoutAttached 是不会执行的,真正执行的是 mPerformLayout 这个lambda 表达式,然后内部走到 DisplayPolicy::layoutWindowLw 方法。

2.3 计算窗口大小 – DisplayPolicy::layoutWindowLw

# DisplayPolicy

    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
        // 判断是否需要跳过布局
        if (win.skipLayout()) {
            return;
        }

        // 获取DisplayFrames
        displayFrames = win.getDisplayFrames(displayFrames);
        // 获取某个方向的窗口布局参数
        final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
        final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;

        // If this window has different LayoutParams for rotations, we cannot trust its requested
        // size. Because it might have not sent its requested size for the new rotation.
        final boolean trustedSize = attrs == win.mAttrs;
        // 应用端请求的宽高信息
        final int requestedWidth = trustedSize ? win.mRequestedWidth : UNSPECIFIED_LENGTH;
        final int requestedHeight = trustedSize ? win.mRequestedHeight : UNSPECIFIED_LENGTH;
        // 重点* 1. 调用WindowLayout.computeFrames计算窗口布局大小
        mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
                win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
                win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);
        // 重点* 2. 将计算的布局参数赋值给windowFrames
        win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
    }
    1. WindowLayout::computeFrames 计算窗口大小的。里面非常的复杂了,有一套计算规则
    1. WindowState::setFrames 然后将计算好后的大小设置给 WindowState ,这个方法好像也不复杂就3个参数,其实2个还是宽高

2.3.1 真正计算-- computeFrames

首先要说明2点:

    1. 执行这个方法的逻辑不止上面分析的这一处,在 ViewRootImpl::setView 方法里也出现过
    1. 这个方法里是具体计算窗口大小和位置的,计算规则也很复杂,个人觉得适当了解即可,遇到窗口的大小和位置显示异常的问题再详细研究这个方法就好。

下面的代码很复杂,对于这种复杂代码,学习阶段一般建议以黑盒的方式掌握即可,不需要知道具体的执行,更不用记代码,google 没准哪天改了这里的代码就白记了。
我是没记住下面的逻辑,但是我把相关的注释加在代码中,至少我知道了这个方法是干什么的大概做了什么事,遇到问题的时候知道往这看。

知道这个方法就计算窗口位置,然后还考虑了一下刘海屏的情况就差不多了。

# WindowLayout

    //参数:
    // attrs: 窗口参数
    // state: Insets状态
    // displayCutoutSafe: 剪裁区域
    // windowBounds: 窗口的边界
    // windowingMode: 窗口模式
    // requestedWidth,requestedHeight: 请求的宽高
    // requestedVisibleTypes: 请求显示的内边距类型
    // compatScale:兼容性缩放因子
    // frames : 输出的窗口位置信息
    public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
            Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
            int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
            float compatScale, ClientWindowFrames frames) {
        // 获取窗口的类型、标志和私有标志,以便后面的计算使用
        final int type = attrs.type;
        final int fl = attrs.flags;
        final int pfl = attrs.privateFlags;
        // 判断窗口是否指定了 FLAG_LAYOUT_IN_SCREEN 标志,该标志表示窗口是否应该扩展到屏幕的尺寸
        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        // 获取输出的各个位置信息,包括显示区域、父窗口区域和窗口区域
        final Rect attachedWindowFrame = frames.attachedFrame;
        final Rect outDisplayFrame = frames.displayFrame;
        final Rect outParentFrame = frames.parentFrame;
        final Rect outFrame = frames.frame;
        
        // 通过 InsetsState 计算窗口的 Insets 值。
        // Compute bounds restricted by insets
        final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
                attrs.isFitInsetsIgnoringVisibility());
        // 获取窗口在各个方向上是否需要计算 Insets 值,然后根据这些方向计算出对应方向的 Insets 值
        final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
        final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
        final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
        final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
        final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
        // 将显示区域的边界计算出来,它是应用窗口在整个屏幕上的可见区域。其中,
        //windowBounds表示应用窗口的位置和大小,left、top、right和bottom表示计算得到的窗口与屏幕边界的间距。
        outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
                windowBounds.right - right, windowBounds.bottom - bottom);
        // 计算出应用窗口父容器的边界
        if (attachedWindowFrame == null) {
            // 如果应用窗口未附加到其他窗口上,则父容器边界与显示区域相同。
            outParentFrame.set(outDisplayFrame);
            if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
                final InsetsSource source = state.peekSource(ID_IME);
                if (source != null) {
                    outParentFrame.inset(source.calculateInsets(
                            outParentFrame, false /* ignoreVisibility */));
                }
            }
        } else {
            // 如果应用窗口附加到其他窗口上,则父容器边界要根据应用窗口所附加的窗口的位置和大小进行调整
            outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
        }
        //计算出在显示区域中可能被切割的区域,即如果屏幕上有凸起的部分(如前置摄像头等)则需要将此部分从显示区域中去除
        // Compute bounds restricted by display cutout
        // 其中,cutoutMode 表示窗口如何处理屏幕凸起部分
        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
        // cutout表示屏幕凸起部分的信息
        final DisplayCutout cutout = state.getDisplayCutout();
        // displayCutoutSafe表示在应用窗口可见区域中,不会被凸起部分遮挡的区域
        final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
        displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
        frames.isParentFrameClippedByDisplayCutout = false;
        // 处理窗口与显示屏中的刘海屏幕(也称为"切口")之间的关系,以确保窗口不会被刘海屏幕覆盖
        // 在Android系统中,刘海屏幕的信息由DisplayCutout类来表示,而这个类的实例通常可以从InsetsState对象中获取
        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
            // Ensure that windows with a non-ALWAYS display cutout mode are laid out in
            // the cutout safe zone.
            final Rect displayFrame = state.getDisplayFrame();
            if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
                if (displayFrame.width() < displayFrame.height()) {
                    displayCutoutSafeExceptMaybeBars.top = MIN_Y;
                    displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                } else {
                    displayCutoutSafeExceptMaybeBars.left = MIN_X;
                    displayCutoutSafeExceptMaybeBars.right = MAX_X;
                }
            }
            final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
            if (layoutInScreen && layoutInsetDecor
                    && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
                final Insets systemBarsInsets = state.calculateInsets(
                        displayFrame, systemBars(), requestedVisibleTypes);
                if (systemBarsInsets.left > 0) {
                    displayCutoutSafeExceptMaybeBars.left = MIN_X;
                }
                if (systemBarsInsets.top > 0) {
                    displayCutoutSafeExceptMaybeBars.top = MIN_Y;
                }
                if (systemBarsInsets.right > 0) {
                    displayCutoutSafeExceptMaybeBars.right = MAX_X;
                }
                if (systemBarsInsets.bottom > 0) {
                    displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                }
            }
            // 刘海屏处理结束
            // 处理输入法类型窗口
            if (type == TYPE_INPUT_METHOD
                    && displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
                    && state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
                // 这是为了确保输入法窗口不会被底部切口遮挡,同时又能够利用底部空间。
                // The IME can always extend under the bottom cutout if the navbar is there.
                displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
            }
            // 用 attachedWindowFrame 和 layoutInScreen 两个变量判断窗口是否连接到了父级并且没有被放置在屏幕之外
            // 即窗口在父级内进行了布局
            // 如果两个条件都满足,说明窗口没有超出父级边界,不需要被剪裁,因此不需要处理刘海屏幕问题
            final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;

            // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
            // get cropped / shifted to the displayFrame in WindowState.
            // floatingInScreenWindow 变量判断窗口是否为非全屏窗口,同时被放置在屏幕之内,且窗口类型不是 TYPE_BASE_APPLICATION
            // 如果窗口类型不是 TYPE_BASE_APPLICATION,则可以认为该窗口是浮动窗口,需要考虑刘海屏幕问题。如果是全屏窗口,则不需要考虑刘海屏幕问题。
            final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
                    && type != TYPE_BASE_APPLICATION;

            // Windows that are attached to a parent and laid out in said parent already avoid
            // the cutout according to that parent and don't need to be further constrained.
            // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
            // They will later be cropped or shifted using the displayFrame in WindowState,
            // which prevents overlap with the DisplayCutout.
            // 如果窗口不连接到父级,且不是浮动窗口,则可以认为窗口会被刘海屏遮挡
            // 做裁剪处理
            if (!attachedInParent && !floatingInScreenWindow) {
                mTempRect.set(outParentFrame);
                outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
                frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
            }
            outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        }
        // noLimits 表示是否启用无限制的布局
        final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
        // inMultiWindowMode 表示窗口是否处于多窗口模式
        final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);

        // 启用了无限制的布局、窗口不是系统错误类型(TYPE_SYSTEM_ERROR)
        // 并且窗口不处于多窗口模式,那么将窗口的显示范围设置为整个屏幕
        if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
            outDisplayFrame.left = MIN_X;
            outDisplayFrame.top = MIN_Y;
            outDisplayFrame.right = MAX_X;
            outDisplayFrame.bottom = MAX_Y;
        }
        // compatScale 表示兼容比例
        final boolean hasCompatScale = compatScale != 1f;
        // 父级窗口的宽度和高度
        final int pw = outParentFrame.width();
        final int ph = outParentFrame.height();
        // 表示窗口是否扩展到刘海区
        final boolean extendedByCutout =
                (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
        // 请求的窗口宽度和高度等
        int rw = requestedWidth;
        int rh = requestedHeight;
        // x,y,w,h 后面将用于计算窗口的位置和大小
        float x, y;
        int w, h;

        // 处理窗口的宽度和高度
        if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
            // 窗口的宽度和高度是UNSPECIFIED_LENGTH,且扩展到刘海区就重新计算高度
            // 计算结果为如果窗口的宽度 attrs.width 大于等于0,就使用 attrs.width 作为窗口的宽度,
            // 否则使用父容器的宽度 pw 作为窗口的宽度
            rw = attrs.width >= 0 ? attrs.width : pw;
        }
        if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
            rh = attrs.height >= 0 ? attrs.height : ph;
        }
        // 根据窗口的属性计算窗口的宽度和高度
        if ((attrs.flags & FLAG_SCALED) != 0) {
            if (attrs.width < 0) {
                w = pw;
            } else if (hasCompatScale) {
                w = (int) (attrs.width * compatScale + .5f);
            } else {
                w = attrs.width;
            }
            if (attrs.height < 0) {
                h = ph;
            } else if (hasCompatScale) {
                h = (int) (attrs.height * compatScale + .5f);
            } else {
                h = attrs.height;
            }
        } else {
            if (attrs.width == MATCH_PARENT) {
                w = pw;
            } else if (hasCompatScale) {
                w = (int) (rw * compatScale + .5f);
            } else {
                w = rw;
            }
            if (attrs.height == MATCH_PARENT) {
                h = ph;
            } else if (hasCompatScale) {
                h = (int) (rh * compatScale + .5f);
            } else {
                h = rh;
            }
        }
        // 处理是否需要缩放
        if (hasCompatScale) {
            x = attrs.x * compatScale;
            y = attrs.y * compatScale;
        } else {
            x = attrs.x;
            y = attrs.y;
        }
        // 如果窗口处于多窗口模式且不是全屏任务
        // PRIVATE_FLAG_XX标志位表示窗口是否应该布局在其父窗口的框架内,而不是在父窗口的内容内
        // 在多窗口模式下,如果子窗口不在父窗口的框架内,则需要确保它适合于父窗口的大小,因为父窗口可能是非全屏的任务
        // 所以如果满足条件则需要调整子窗口的大小以适应父窗口的大小。
        if (inMultiWindowMode
                && (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
            // Make sure window fits in parent frame since it is in a non-fullscreen task as
            // required by {@link Gravity#apply} call.
            w = Math.min(w, pw);
            h = Math.min(h, ph);
        }

        // 如果不在多窗口模式下,或者不是TYPE_BASE_APPLICATION类型,且noLimits属性为false,
        // 则需要将窗口大小限制在显示器内。fitToDisplay变量用于表示是否需要将窗口大小限制在显示器内
        final boolean fitToDisplay = !inMultiWindowMode
                || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);

        // 计算出窗口在父视图中的矩形框的位置和大小,并将结果保存在outFrame变量中
        Gravity.apply(attrs.gravity, w, h, outParentFrame,
                (int) (x + attrs.horizontalMargin * pw),
                (int) (y + attrs.verticalMargin * ph), outFrame);

        // 计算出窗口在整个屏幕范围内的位置和大小,并将结果保存在outFrame变量中
        if (fitToDisplay) {
            Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
        }

        if (extendedByCutout) {
            extendFrameByCutout(displayCutoutSafe, outDisplayFrame, outFrame,
                    mTempRect);
        }

        if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
                + " frames=" + frames
                + " windowBounds=" + windowBounds.toShortString()
                + " requestedWidth=" + requestedWidth
                + " requestedHeight=" + requestedHeight
                + " compatScale=" + compatScale
                + " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
                + " displayCutoutSafe=" + displayCutoutSafe
                + " attrs=" + attrs
                + " state=" + state
                + " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
    }

2.4 计算结束后赋值

WindowLayout::computeFrames 方法很长,最后一个参数:frames 传进去的是 DisplayPolicy 下的静态变量 sTmpClientFrames 。也就是说根据一套计算规则计算后,sTmpClientFrames 这个变量中就保存了最新的窗口大小信息。

然后调用 WindowState::setFrames 方法把数据设置给 WindowState。

2.4.1 参数解释

第一个参数有正确的大小信息,后面2个参数是请求的宽高,那是谁请求的呢?是应用端请求的。 和 View 绘制一样,子 View 会有一个自己期望的宽高,但是计算规则会根据实际情况来设置 View最终的宽高。
那么窗口也是一样,应用端会在执行 relayoutWindow 流程的时候就把它期望的宽高传递了过来,但是 WMS 毕竟管理的是整个手机上各个窗口,它需要根据实际情况来设置最终的窗口大小,并返回给应用端。

道理知道了,现在从代码中来确认。
DisplayPolicy::layoutWindowLw 方法中是这样调用的

win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);

后面2个参数也是在 WindowState 内。

# WindowState
    /**
     * The window size that was requested by the application.  These are in
     * the application's coordinate space (without compatibility scale applied).
     * 应用程序请求的窗口大小
     */
    int mRequestedWidth;
    int mRequestedHeight;
    // 赋值
    void setRequestedSize(int requestedWidth, int requestedHeight) {
        if ((mRequestedWidth != requestedWidth || mRequestedHeight != requestedHeight)) {
            mLayoutNeeded = true;
            mRequestedWidth = requestedWidth;
            mRequestedHeight = requestedHeight;
        }
    }

那其实只有看哪里调用 setRequestedSize 方法就好了,答案就是 WindowManagerService::relayoutWindow 方法,这个其实上一篇代码里有了,再看一眼:

# WindowManagerService

   public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
                    ......
                    if (viewVisibility != View.GONE) {
                    // 把应用端请求的大小,保存到WindowState下
                    win.setRequestedSize(requestedWidth, requestedHeight);
                    }
                    ......
                    if (shouldRelayout) {
                        try {
                            // 重点* 1. 创建SurfaceControl
                            result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                        } catch (Exception e) {
                            ......
                            return 0;
                        }
                    }

                    // 重点* 2. 计算窗口的大小 (极其重要的方法)
                    mWindowPlacerLocked.performSurfacePlacement(true /* force */);
                    ......
                    // 重点* 3. 给应用端SurfaceControl赋值
                    winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
                    ......
                    // 重点* 4. 填充WMS计算好后的数据,返回应用端
                    win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
                            false /* useLatestConfig */, shouldRelayout);
                    ......
            }

现在知道这3个参数是什么了开始看 WindowState::setFrames 方法。

2.4.2 设置窗口大小–WindowState::setFrames

窗口 WindowState 下有个成员变量 mWindowFrames 是 WindowFrames 类型,保存在窗口尺寸信息。先看看这个类

# WindowFrames
    /**
     * The frame to be referenced while applying gravity and MATCH_PARENT.
     * 父容器矩形位置
     */
    public final Rect mParentFrame = new Rect();

    /**
     * The bounds that the window should fit.
     * 屏幕的大小,包括状态栏导航栏这些
     */
    public final Rect mDisplayFrame = new Rect();

    /**
     * "Real" frame that the application sees, in display coordinate space.
     * 表示应用程序或视图在屏幕上的实际可见区域。
     * 窗口的大小就是这个变量,应用端绘制的大小也是这个
     */
    final Rect mFrame = new Rect();

    /**
     * mFrame but relative to the parent container.
     * 这个矩形与mFrame类似,但它是相对于父容器的坐标系统而不是屏幕坐标。
     */
    final Rect mRelFrame = new Rect();

有个印象就好,开始看具体方法

同 WindowLayout::computeFrames 方法一样,也是大量代码,而且也会被 google 修改,我还是建议黑盒形式知道这个方法会把计算出的尺寸设置给窗口 WindowState 就好了。

# WindowState
// clientWindowFrames: 经过WindowLayout::computeFrames 方法计算出来的窗口尺寸
// requestedWidth: 应用端请求的宽度
// requestedHeight : 应用端请求的高度
void setFrames(ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
        // 获取当前窗口的框架信息实例
        final WindowFrames windowFrames = mWindowFrames;
        // 将父窗口框架信息复制到临时变量 mTmpRect
        mTmpRect.set(windowFrames.mParentFrame);

        // 3个变量的赋值
        windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
        windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
        windowFrames.mFrame.set(clientWindowFrames.frame);

        // 设置兼容框架信息与窗口框架相同
        windowFrames.mCompatFrame.set(windowFrames.mFrame);
        // 如果需要兼容缩放或者硬件调整大小功能启用
        if (hasCompatScale()/** M: Add for App Resolution Tuner @{ */
            || mNeedHWResizer/** @}*/) {
            // Also, the scaled frame that we report to the app needs to be adjusted to be in
            // its coordinate space.
            对windowFrames.mCompatFrame缩放,确保在应用坐标空间内正确展示
            windowFrames.mCompatFrame.scale(mInvGlobalScale);
        }

        // 设置是否父窗口框架被显示切割区域裁剪标志位
        windowFrames.setParentFrameWasClippedByDisplayCutout(
                clientWindowFrames.isParentFrameClippedByDisplayCutout);

        // Calculate relative frame
        // 计算相对框架信息
        windowFrames.mRelFrame.set(windowFrames.mFrame);
        // 将窗口框架转换为相对于父容器的坐标系
        WindowContainer<?> parent = getParent();
        int parentLeft = 0;
        int parentTop = 0;
        if (mIsChildWindow) {
            // 如果当前窗口是子窗口,则根据其父窗口计算偏移量
            parentLeft = ((WindowState) parent).mWindowFrames.mFrame.left;
            parentTop = ((WindowState) parent).mWindowFrames.mFrame.top;
        } else if (parent != null) {
            // 若当前窗口不是子窗口但有父容器,则从父容器边界获取偏移量
            final Rect parentBounds = parent.getBounds();
            parentLeft = parentBounds.left;
            parentTop = parentBounds.top;
        }
        windowFrames.mRelFrame.offsetTo(windowFrames.mFrame.left - parentLeft,
                windowFrames.mFrame.top - parentTop);

        // 检查请求的宽高是否改变,以及父窗口框架是否有变化
        if (requestedWidth != mLastRequestedWidth || requestedHeight != mLastRequestedHeight
                || !mTmpRect.equals(windowFrames.mParentFrame)) {

            // 更新最后请求的宽高值,并标记内容发生变化
            mLastRequestedWidth = requestedWidth;
            mLastRequestedHeight = requestedHeight;
            windowFrames.setContentChanged(true);
        }

        // 如果窗口类型为 DOCK_DIVIDER 类型
        // 目前我知道的是分屏应用中间的分割线是这个类型
        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
            if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
                // 如果当前帧与上次帧不相等,则表示窗口移动过
                mMovedByResize = true;
            }
        }

        // 如果当前窗口是壁纸
        if (mIsWallpaper) {
            final Rect lastFrame = windowFrames.mLastFrame;
            final Rect frame = windowFrames.mFrame;
            if (lastFrame.width() != frame.width() || lastFrame.height() != frame.height()) {
                mDisplayContent.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
            }
        }
        // 更新源框架信息
        updateSourceFrame(windowFrames.mFrame);

        ......
        // 如果存在 ActivityRecord 并且当前窗口不是子窗口,则调用 layoutLetterbox 方法
        if (mActivityRecord != null && !mIsChildWindow) {
            mActivityRecord.layoutLetterbox(this);
        }
        // 标记需要进行 Surface 的放置操作
        mSurfacePlacementNeeded = true;
        // 标记已经具有有效的帧信息
        mHaveFrame = true;
    }

3. 返回窗口大小给应用端

经过 WindowState::setFrames 方法后,窗口自己就有了一个确定的尺寸了,但是绘制内容是在应用端,所以现在需要把这个窗口尺寸传递给应用端。
早 WindowManagerService::relayoutWindow 方法中知道,经过 WindowSurfacePlacer::performSurfacePlacement 方法计算出窗口尺寸后, 会执行 WindowState::fillClientWindowFramesAndConfiguration 方法就尺寸信息填充到参数 outFrames 中,也就是传递给应用端。

# WindowState
    /**
     * Fills the given window frames and merged configuration for the client.
     *
     * @param outFrames The frames that will be sent to the client.
     * @param outMergedConfiguration The configuration that will be sent to the client.
     * @param useLatestConfig Whether to use the latest configuration.
     * @param relayoutVisible Whether to consider visibility to use the latest configuration.
     */
    void fillClientWindowFramesAndConfiguration(ClientWindowFrames outFrames,
            MergedConfiguration outMergedConfiguration, boolean useLatestConfig,
            boolean relayoutVisible) {
        // 尺寸信息设置给应用端
        outFrames.frame.set(mWindowFrames.mCompatFrame);
        outFrames.displayFrame.set(mWindowFrames.mDisplayFrame);

        // 缩放处理
        if (mLayoutAttached) {
            if (outFrames.attachedFrame == null) {
                outFrames.attachedFrame = new Rect();
            }
            outFrames.attachedFrame.set(getParentWindow().getFrame());
            if (mInvGlobalScale != 1f) {
                outFrames.attachedFrame.scale(mInvGlobalScale);
            }
        }
        ......
        // 标记已向客户端报告过配置信息
        mLastConfigReportedToClient = true;
    }

这个 outFrames 就是应用端 ViewRootImpl执行 relayout 方法触发WMS::relayoutWindow 传递的参数 mTmpFrames。

4. 小结

relayoutWindow 流程就分析完了,代码很多流程很长,而且中间很多核心方法还有其他的分支,但是只当前主流程还是比较清晰的。

在这里插入图片描述
上一篇分析了Surface的创建,本篇分析了窗口大小的计算和保存, 代码虽多但是其实 WindowLayout::computeFrames 和 WindowState::setFrames 这2个方法知道是干啥的就可以了,完全不需要记住。等实际解决问题的时候知道在哪看就好了。

4.1 调用链分析

4.1.1 Activity 启动触发2个事务的调用链

其中 ResumeActivityItem 会触发 ViewRootImpl::setView 执行,也是触发relayoutWindow 的逻辑

LaunchActivityItem::execute
   ActivityThread::handleLaunchActivity
         ActivityThread::performLaunchActivity
            Instrumentation::newActivity      --- 创建Activity
            Activity::attach                  --- 创建Window
               Window::init
               Window::setWindowManager
            Instrumentation::callActivityOnCreate  
               Activity::performCreate
                  Activity::onCreate       --- onCreate

ResumeActivityItem::execute
   ActivityThread::handleResumeActivity
        ActivityThread::performResumeActivity   
            Activity::performResume     
               Instrumentation::callActivityOnResume
                  Activity::onResume       --- onResume
        WindowManagerImpl::addView           --- 创建ViewRootImpl
            WindowManagerGlobal::addView   
               ViewRootImpl::setView        --- 与WMS通信触发窗口的显示逻辑

4.1.2 应用端 ViewRootImpl::setView

ViewRootImpl::setView 后续的调用链如下:

ViewRootImpl::setView
   ViewRootImpl::requestLayout
      ViewRootImpl::scheduleTraversals             
            ViewRootImpl.TraversalRunnable::run          --- Vsync相关--scheduleTraversals
               ViewRootImpl::doTraversal
                  ViewRootImpl::performTraversals 
                     ViewRootImpl::relayoutWindow        --- 第二步:relayoutWindow
                        Session::relayout                --- 跨进程调用
                        ViewRootImpl::updateBlastSurfaceIfNeeded
                           Surface::transferFrom         -- 应用端Surface赋值
                     ViewRootImpl::performMeasure        --- View绘制三部曲
                     ViewRootImpl::performLayout
                     ViewRootImpl::performDraw        
                     ViewRootImpl::createSyncIfNeeded    --- 第三步:绘制完成 finishDrawingWindow
   Session.addToDisplayAsUser                     --- 第一步:addWindow

其中窗口显示的三步都是这里触发的

所以说 ViewRootImpl 真的很重要,其实没有Activity,没有Window, 只通过 ViewRootImpl 也能在屏幕上显示一个窗口

4.1.3 system_service端调用链


WindowManagerService::relayoutWindow
   WindowManagerService::createSurfaceControl
      WindowStateAnimator::createSurfaceLocked -- 创建“Buff” 类型Surface  (上一篇)
         WindowStateAnimator::resetDrawState   -- 设置窗口状态为DRAW_PENDING
         WindowSurfaceController::init
            SurfaceControl.Builder::build
               SurfaceControl::init
    WindowSurfacePlacer::performSurfacePlacement  -- 计算窗口大小 (本篇)
        WindowSurfacePlacer::performSurfacePlacementLoop
            RootWindowContainer::performSurfacePlacement
                RootWindowContainer::performSurfacePlacementNoTrace
                    RootWindowContainer::applySurfaceChangesTransaction
                        DisplayContent::applySurfaceChangesTransaction
                            DisplayContent::performLayout 
                                DisplayContent::performLayoutNoTrace
                                    DisplayContent::mPerformLayout 
                                        DisplayPolicy::layoutWindowLw 
                                            WindowLayout::computeFrames   -- 计算窗口大小,保存在 sTmpClientFrames中
                                            WindowState::setFrames        -- 将计算结果 sTmpClientFrames 的数据设置给窗口
   WindowSurfaceController::getSurfaceControl  -- 给应用端Surface赋值
   WindowState::fillClientWindowFramesAndConfiguration  -- 给应用端窗口大小赋值

4.2 流程图

一次layout中,关于一个屏幕下所有窗口大小计算的流程图如下:

在这里插入图片描述

这部分也是 relayoutWindow 流程中关于窗口计算的核心流程。

4.3 下节预告

窗口显示第一步:addWindow 创建对应的WindowState并挂载到窗口树
窗口显示第二步:relayoutWindow 会创建出"Buff"类型的Surface给应用端和计算好窗口的大小

现在应用端已经有可以存放U数据的Surface了,并且也已经知道自己的窗口大小了,就可以开始绘制View信息了,等View绘制好之后就可以触发下一步流程了:

窗口显示第三步:绘制完成,通知SurfaceFlinger合成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值