【Android 13源码分析】窗口显示第三步:finishDrawingWindow

忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。

-- 服装学院的IT男

一个应用想要将它的UI内容显示到屏幕窗口上,涉及到3个模块: 应用端,SystemService端和SurfaceFlinger端。

在App开发中一个View想要显示需要经过3个步骤,也就是View三部曲:Measure,Layout,Draw 对应的一个Window想要在屏幕上显示也需要经过3个步骤:

    1. addWindow : SystemService端为应用窗口创建对应的WindowState并且挂载到窗口树中
    1. relayoutWindow : 这一步会创建一个Surface返回给应用端进行绘制,并且触发WMS的各个窗口的位置摆放和窗口尺寸计算(relayout)
    1. finishDrawingWindow:这一步是应用端的View绘制完成后,Surface已经有UI信息了,需要通过SurfaceFlinger进行合成

经过前面2步,现在 WMS 中已经有了应用的 WindowState 并且也计算好了窗口的大小,同时还创建了对应的 Surface 供应用 View 进行绘制。

接下来应用端会开始绘制,应用端的 View 绘制完成后就需要显示到屏幕上了,这就需要告知 SurfaceFlinger 进行合成处理了。

下面徐要分析的窗口显三部曲的最后一步:finishDrawingWindow 。

本篇的目的就是梳理 FrameWork 层是如何通知 SurfaceFlinger 应用窗口已经绘制完成。

有2个线索:

    1. FrameWork 中对窗口(WindowState)定义了不同的状态,找到对应状态切换的地方。
    1. FrameWork 通知 SF 做操作,是需要通过 SurfaceControl.Transaction 完成的
    1. 首先看到是应用端完成了绘制,通知到 system_service 来做后续处理
    1. 然后WMS执行 finishDrawingWindow 方法开始处理,处理的事情可以分为2块
    • 2.1 窗口状态的处理
    • 2.2 构建出一个 SurfaceControl.Transaction 用来通知 SurfaceFlinger 显示这个窗口的 Surface
    1. SurfaceFlinger 做后续的处理

这里提到的窗口状态定义在 WindowStateAnimator.java 下面,结合源码的注释和实际场景简单解释一下各个状态:

php 复制代码
# WindowStateAnimator

    /** This is set when there is no Surface */
    // 没有 Surface的时候,说明没有创建或者窗口销毁
    static final int NO_SURFACE = 0;
    /** This is set after the Surface has been created but before the window has been drawn. During
     * this time the surface is hidden. */
    // Surface 刚刚创建但是还没绘制的状态。 也就是 relayoutWindow 流程时设置的
    static final int DRAW_PENDING = 1;
    /** This is set after the window has finished drawing for the first time but before its surface
     * is shown.  The surface will be displayed when the next layout is run. */
    // 窗口第一次完成绘制之后的状态,将在下一次 layout 的时候执行。
    // 是等待提交到SF的状态
    static final int COMMIT_DRAW_PENDING = 2;
    /** This is set during the time after the window's drawing has been committed, and before its
     * surface is actually shown.  It is used to delay showing the surface until all windows in a
     * token are ready to be shown. */
    // 已经提交到SF, 准备显示到屏幕上
    static final int READY_TO_SHOW = 3;
    /** Set when the window has been shown in the screen the first time. */
    // 窗口已经显示
    static final int HAS_DRAWN = 4;

大概流程如下:

    1. 在上一步 relayoutWindow 的时候状态 WindowState 状态已经被设置为 DRAW_PENDING
    1. 应用绘制完成会后触发 finishDrawingWindow 方法,这个方法分为2步
    • 2.1 执行 finishDrawing 方法将窗口状态设置为 COMMIT_DRAW_PENDING
    • 2.2 执行 requestTraversal 触发 layout 流程,这里可能对多次执行。相关的事情都在内部的 applySurfaceChangesTransaction 方法中处理
      • 2.2.1 在 commitFinishDrawingLocked 方法把窗口状态设置为 READY_TO_SHOW
      • 2.2.2 在 performShowLocked 方法把窗口状态设置为 HAS_DRAWN
      • 2.2.3 执行 prepareSurfaces 方法,最终构建窗口 Surface 显示的事务,通知 SurfaceFlinger 做后续的处理

开始撸代码。

1. 应用端处理

首先既然是绘制完成后的处理,触发的地方还是应用端本身,只有应用端绘制完成了才会触发逻辑。

再看一下 ViewRootImpl::setView 的调用链:

arduino 复制代码
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绘制三部曲 --Measure
                        ViewRootImpl::performLayout         --- View绘制三部曲 --Layout  
                        ViewRootImpl::createSyncIfNeeded    --- 第三步:绘制完成 finishDrawingWindow
                            SurfaceSyncer::setupSync
                                SyncSet::init               --- 将回调封装在SyncSet下
                                    ViewRootImpl::reportDrawFinished  -- 等待回调,触发finishDrawingWindow
                                        Session::finishDrawing  -- 触发system_service进程执行finishDrawing流程  -- 开始跨进程(finishDrawing流程)
                        ViewRootImpl::performDraw           --- View绘制三部曲 --Draw    
                        SurfaceSyncer::markSyncReady        --- 触发:finishDrawingWindow
   Session.addToDisplayAsUser                     --- 第一步:addWindow

前面分析relayoutWindow流程 的时候已经分析过ViewRootImpl::performTraversals 方法了,不过当前重点不一样,所以还需要再看一遍这个方法(增加了一些当前流程相关的代码)

ini 复制代码
# ViewRootImpl
    // 创建对象
    private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
    // 是否有同步的内容需要上报
    boolean mReportNextDraw;

    private void performTraversals() {
    ......
    // mWinFrame保存的是当前窗口的尺寸
    Rect frame = mWinFrame;
    ----1.1 硬绘相关----
    // 硬件加速是否初始化
    boolean hwInitialized = false;
    ......
    ----2. relayoutWindow流程----
    // 内部会将经过WMS计算后的窗口尺寸给mWinFrame
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ......
    // 1.2 初始化硬件加速,将Surface与硬件加速绑定
    hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
    ......
    ----  3.1 View绘制三部曲--Measure
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......3.2 View绘制三部曲--Layout
    performLayout(lp, mWidth, mHeight);
    ......
    // 设置 mReportNextDraw = true,表示当前需要上报SurfaceFlinger
    if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
        reportNextDraw(); // 打开应用的时候触发上报
    }
    ----4.1 finishDrawing流程----
    createSyncIfNeeded();
    ...... 3.3 View绘制三部曲--Draw  
    if (!performDraw() && mSyncBufferCallback != null) {
        mSyncBufferCallback.onBufferReady(null);
    }
    ......
    // 4.2 触发执行回调
        mReportNextDraw = false; 
        if (isInLocalSync()) {
            mSurfaceSyncer.markSyncReady(mSyncId);
            mSyncId = UNSET_SYNC_ID;
        }
    ......
   }
    1. 后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
    1. relayoutWindow 相关
    1. 经过第二步 relayoutWindow 后 View 就可以绘制了
    1. 绘制完成后就要通知 SurfaceFlinger 进行合成了,也就是本篇分析的 finishDrawing 流程

当前分析 finishDrawing 流程,首先可以看到 relayoutWindow 方法执行后,会触发3个View绘制的方法,也就是常说的 View 绘制三部曲,但是这里有个奇怪的地方: "4.1 createSyncIfNeeded" 方法是触发 finishDrawingWindow 的,但是这个方法在 "3.3 performDraw"的上面。

这是因为代码的顺序不代表真正的执行顺序,这里的"4.1 createSyncIfNeeded"只是设置了"回调",等时机到了就会触发执行,而这个时机就是 View 绘制完成后,在 "4.2 markSyncReady触发"

这一部分的逻辑有点绕,不过目前分析的是主流程,所以这块逻辑以上的描述当黑盒理解这段的调用: View绘制结束后就会在 4.2 出触发 4.1 内部的执行,进入触发 finishDrawingWindow 流程即可。

后面再单独写一篇详细解释直接的调用逻辑。

1.2 finishDrawingWindow 的触发

在 ViewRootImpl::performTraversals 方法最后会执行 SurfaceSyncer::markSyncReady 方法,最终会触发 ViewRootImpl::createSyncIfNeeded 方法下的 ViewRootImpl::reportDrawFinished 来真正 finishDrawingWindow 流程。

scss 复制代码
# ViewRootImpl
    // 创建对象
    private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
    // 是否有同步的内容需要上报
    boolean mReportNextDraw;

    private void createSyncIfNeeded() {
         // 如果已经在本地进行同步或者没有需要同步的内容
        if (isInLocalSync() || !mReportNextDraw) {
            return;
        }
        
        // 获取当前同步序列号
        final int seqId = mSyncSeqId;
        
        // 调用mSurfaceSyncer::setupSync,并传入一个匿名类
        mSyncId = mSurfaceSyncer.setupSync(transaction -> {
            mHandler.postAtFrontOfQueue(() -> {
                // 合并传入的transaction到mSurfaceChangedTransaction中
                mSurfaceChangedTransaction.merge(transaction);
                // 重点* 报告绘制完成,传入之前获取的序列号
                reportDrawFinished(seqId);
            });
        });
        
        if (DEBUG_BLAST){
            // 打印日志
            Log.d(mTag, "Setup new sync id=" + mSyncId);
        }
        
        // 将mSyncTarget添加到mSyncId对应的同步中
        mSurfaceSyncer.addToSync(mSyncId, mSyncTarget);
        notifySurfaceSyncStarted();
    }

ViewRootImpl::reportDrawFinished 方法如下:

typescript 复制代码
# ViewRootImpl
    private void reportDrawFinished(int seqId) {
        // 日志和Trace相关
        if (DEBUG_BLAST) {
            Log.d(mTag, "reportDrawFinished " + Debug.getCallers(5));
        }
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "finish draw");
        }

        try {
            // 重点* finishDrawing流程
            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, seqId);
            ......
        } ......
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

# Session
    @Override
    public void finishDrawing(IWindow window,
            @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
        if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
        mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
    }

唯一做的一件事就是跨进程触发 WindowManagerService::finishDrawingWindow 。 到这里应用端的事情就处理完了,后面的流程在 system_service 进程。

2. system_service处理

结合文章上面看到的流程图,这里也先把代码主要的调用链整理出来:

php 复制代码
WindowManagerService::finishDrawingWindow
   WindowState::finishDrawing
      WindowStateAnimator::finishDrawingLocked    -- COMMIT_DRAW_PENDING

   WindowPlacerLocked::requestTraversal           -- 触发layout      (可能会执行多次)
      Traverser::run                              -- Runnable异步执行
         WindowSurfacePlacer::performSurfacePlacement
            WindowSurfacePlacer::performSurfacePlacementLoop
               WindowSurfacePlacer::performSurfacePlacementLoop
                  RootWindowContainer::performSurfacePlacement  -- 开始逻辑
                     RootWindowContainer::performSurfacePlacementNoTrace
                        WindowManagerService::openSurfaceTransaction      -- 打开Surface事务
                        RootWindowContainer::applySurfaceChangesTransaction  -- 处理Surface事务
                           DisplayContent::applySurfaceChangesTransaction
                              DisplayContent::performLayout                          -- relayoutWinodw 流程
                              DisplayContent::mApplySurfaceChangesTransaction
                                 WindowStateAnimator::commitFinishDrawingLocked      -- READY_TO_SHOW
                                    WindowState::performShowLocked                   -- HAS_DRAWN(一般第二次菜进入)
                                 ActivityRecord::updateDrawnWindowStates
                                    mTmpUpdateAllDrawn::add                   -- 存入
                              mTmpUpdateAllDrawn::removeLast               -- 取出
                                 ActivityRecord::updateAllDrawn            -- allDrawn = true
                                    DisplayContent.setLayoutNeeded         -- 再来一次layout
                              DisplayContent::prepareSurfaces                        -- Surface 处理
                                 WindowContainer::prepareSurfaces                    -- 遍历每个孩子
                                    WindowState::prepareSurfaces                     -- 忽略其他,只看窗口的实现
                                       WindowStateAnimator::prepareSurfaceLocked
                                          WindowStateAnimator::showSurfaceRobustlyLocked
                                             WindowSurfaceController::showRobustly
                                                WindowSurfaceController::setShown
                                                SurfaceControl.Transaction::show     -- Surface显示
                        WindowManagerService::closeSurfaceTransaction    -- 处理关闭Surface事务
                           SurfaceControl::closeTransaction
                              GlobalTransactionWrapper::applyGlobalTransaction
                                 SurfaceControl::nativeApplyTransaction    -- 触发native

现在开始撸代码:

java 复制代码
# WindowManagerService
    final WindowSurfacePlacer mWindowPlacerLocked;

    void finishDrawingWindow(Session session, IWindow client,
            @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
        if (postDrawTransaction != null) {
            postDrawTransaction.sanitize();
        }

        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                // 获取到对应的WindowState
                WindowState win = windowForClientLocked(session, client, false);
                // log
                ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
                        win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                // 重点* 1. 执行WindowState::finishDrawing
                if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
                    if (win.hasWallpaper()) {
                        win.getDisplayContent().pendingLayoutChanges |=
                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                    }
                    // 将当前WindowState.mLayoutNeeded置为true
                    win.setDisplayLayoutNeeded();
                    // 重点* 2. 请求进行布局刷新
                    mWindowPlacerLocked.requestTraversal();
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

system_service 进程第一个处理的方法就是 WindowManagerService::finishDrawingWindow 这个方法也就做了2件事:

    1. WindowState::finishDrawing 当前分析的主要流程,将状态设置为 COMMIT_DRAW_PENDING
    1. WindowSurfacePlacer::requestTraversal 常见核心函数,触发layout,将状态设置为 READY_TO_SHOW ,HAS_DRAWN ,然后通知到 SurfaceFlinger

这里有上述的2个流程需要分析,首先会执行 WindowState::finishDrawing ,将WindowState状态设置为 COMMIT_DRAW_PENDING ,表示应用端已经绘制完成了,可以提交给SF了。

第一步操作完之后,就会执行 WindowSurfacePlacer::requestTraversal ,这个方法是执行一次 layout 逻辑。

在前面看窗口状态 COMMIT_DRAW_PENDING 的时候,google 注释提过: "会下一次 layout 的时候显示到屏幕上",指的就是在这里触发的 layout。

在第二步 layout 的时候会遍历每个窗口,目前只关心我们当前分析的场景的这个窗口,在这次 layout 会做3件事:

    1. 将窗口状态设置为 READY_TO_SHOW
    1. 将窗口状态设置为 HAS_DRAWN
    1. 执行 SurfaceControl.Transaction 通知 SurfaceFlinger 做显示合成

下面开始在代码中梳理流程。

2.1 WindowState状态 -- COMMIT_DRAW_PENDING

arduino 复制代码
# WindowState
    final WindowStateAnimator mWinAnimator;
    boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {
   		......
   		// 主流程
        final boolean layoutNeeded =
                mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);
        mClientWasDrawingForSync = false;
        // We always want to force a traversal after a finish draw for blast sync.
        return !skipLayout && (hasSyncHandlers || layoutNeeded);
    }

主要是执行了 WindowStateAnimator::finishDrawingLocked ,内部会将 WindowState 的状态设置为 COMMIT_DRAW_PENDING ,这个是非常重要的一步。

typescript 复制代码
# WindowStateAnimator

    boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
            boolean forceApplyNow) {
        ......
        // 只有当前状态是DRAW_PENDING的时候才可以走进逻辑
        if (mDrawState == DRAW_PENDING) {
            ProtoLog.v(WM_DEBUG_DRAW,
                    "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
                    mSurfaceController);
            if (startingWindow) {
                // 如果是StartingWindow还有专门的log
                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
            }
            mDrawState = COMMIT_DRAW_PENDING;
            // 表示需要 layout
            layoutNeeded = true;
        }
        ......
    }

这样第一步就执行完了,流程很简单,只是设置窗口状态为 COMMIT_DRAW_PENDING 。

2.2 layout 流程简述

上一小节只是改了状态,下一个状态是 READY_TO_SHOW ,前面看到google对它有一个注释:The surface will be displayed when the next layout is run.

也就是说在下一次 layout 会触发 Surface 的显示,所以关键流程还是在 "next layout",

那什么是 "next layout" ?

我们知道屏幕上有任何风吹操作都会触发一次 layout 流程,主要就是执行 WindowSurfacePlacer::performSurfacePlacement 这就是 一次 layout 。

WindowPlacerLocked::requestTraversal 触发的 layout 流程就是之前 relayoutWindow 流程看到的 WindowSurfacePlacer::performSurfacePlacement 。这个流程触发的地方非常多,只是当前 finishDrawingWindow 会主动触发一次罢了。对于这种高频率触发的方法,需要留意一下,初学者知道每个主流程会走什么逻辑就好,慢慢的随着知识体系的构建,再看这个流程其实就没那么复杂了。

WindowSurfacePlacer::performSurfacePlacement 的逻辑还是很复杂的,它会遍历屏幕上每一个窗口去,然后让其根据最新情况做对应的处理,比如 relayoutWinodw 流程的时候就会遍历到窗口做 执行 computeFrames 计算窗口大小。

当前分析的场景自然也会遍历窗口,正常逻辑下就是让目标窗口执行完 finishDrawingWindow 流程。

这个流程之前看过了,现在再完整看一遍 layout 流程,会忽略更多无关的的代码。

java 复制代码
# WindowSurfacePlacer
    private final Traverser mPerformSurfacePlacement = new Traverser();

    private class Traverser implements Runnable {
        @Override
        public void run() {
            synchronized (mService.mGlobalLock) {
                performSurfacePlacement();
            }
        }
    }

    void requestTraversal() {
        ......
        mService.mAnimationHandler.post(mPerformSurfacePlacement);
    }

可以看到 WindowPlacerLocked::requestTraversal 其实就是触发了 WindowSurfacePlacer::performSurfacePlacement 方法的调用。

需要注意这边的一个异步的 Runnable 所以打堆栈或打 Trace 的时候需要留意一下

arduino 复制代码
# WindowSurfacePlacer
    final void performSurfacePlacement() {
        performSurfacePlacement(false /* force */);
    }
    // 控制是否需要继续执行 performSurfacePlacementLoop方法
    private boolean mTraversalScheduled;

    final void performSurfacePlacement(boolean force) {
        if (mDeferDepth > 0 && !force) {
            mDeferredRequests++;
            return;
        }
        // 最大次数循环为6次
        int loopCount = 6;
        do {
            // 设置为false
            mTraversalScheduled = false;
            // 重点方法
            performSurfacePlacementLoop();
            mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
            loopCount--;
        } while (mTraversalScheduled && loopCount > 0);
        mService.mRoot.mWallpaperActionPending = false;
    }

这个方法在 relayoutWindow 也详细讲过了, 主要看 performSurfacePlacementLoop

csharp 复制代码
# WindowSurfacePlacer
    private void performSurfacePlacementLoop() {
        ......
        // 重点* 对所有窗口执行布局操作
        mService.mRoot.performSurfacePlacement();
        ......
    }

上面都是一些执行的条件处理,真正的处理还是从 RootWindowContainer::performSurfacePlacement 方法开始

scss 复制代码
# RootWindowContainer

    // 这个方法加上了trace
    void performSurfacePlacement() {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
        try {
            performSurfacePlacementNoTrace();
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
    // 主要干活的还是这个
    void performSurfacePlacementNoTrace() {
        ......
        // Trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
        // 开启Surface事务
        mWmService.openSurfaceTransaction();
        try {
            // 重点* 1. 处理Surface事务
            applySurfaceChangesTransaction();
        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            // 关闭Surface事务
            mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        ......
        // 重点* 2. 处理App事务
        checkAppTransitionReady(surfacePlacer);
        ......
    }

这里就有2个主要流程,也是2个类型的事务处理:

    1. applySurfaceChangesTransaction 方法,执行 Surface 事务

可以看到这行代码前后有 SurfaceTransaction 的打开和关闭,那说明这里的逻辑是会触发 Surface 操作的。

applySurfaceChangesTransaction 方法内部做很多事,比如上一篇的 layoutWindow 流程,当前分析的场景在这个方法里会执行将窗口状态设置为 READY_TO_SHOW ,并且构建一个 Surface 事务来显示当前窗口的 Surface 。

然后会在 WindowManagerService::closeSurfaceTransaction 方法中触发 SurfaceTransaction 的apply 把 Surface 操作的事务提交到 SurfaceFlinger 。

    1. checkAppTransitionReady 方法,执行 App 事务 这个方法也是很常见并且核心的,Framework 层专门定义了 AppTransition 来表示一些 APP 的事务,根据用户具体的操作执行对应的 App 事务。如果事务已经满足执行条件,则会触发对应的 AppTransition 执行,然后也有一些窗口动画的触发。

这个方法知道一下即可,当前流程可以不分析。

2.3 applySurfaceChangesTransaction 方法概览

ini 复制代码
# RootWindowContainer
    private void applySurfaceChangesTransaction() {
        ......
        // 遍历每个屏幕
        final int count = mChildren.size();
        for (int j = 0; j < count; ++j) {
            final DisplayContent dc = mChildren.get(j);
            dc.applySurfaceChangesTransaction();
        }
        ......
    }

不考虑多个屏幕的场景

java 复制代码
# DisplayContent
    // 需要更少是否已经绘制的集合
    private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();

    void applySurfaceChangesTransaction() {
        ......
        // 新的执行,清除数据
        mTmpUpdateAllDrawn.clear();
        ......
        // 重点* 1. layoutWindow 流程
        performLayout(true /* initial */, false /* updateInputWindows */);
        ......
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
        try {
            // 重点* 2. 遍历所有窗口执行 lambda表达式
            forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        // 重点* 3. Surface 操作
        prepareSurfaces();
        ......
        // 如果有需要更新的ActivityRecord则处理
        while (!mTmpUpdateAllDrawn.isEmpty()) {
            final ActivityRecord activity = mTmpUpdateAllDrawn.removeLast();
            // See if any windows have been drawn, so they (and others associated with them)
            // can now be shown.
            // 内部会设置这个ActivityRecord 下的 allDrawn 为true
            activity.updateAllDrawn();
        }
    }

这里有有个3重要的流程:

    1. relayoutWinodw 流程计算窗口大小(已经分析过,当前不管)
    1. 遍历每个窗口,执行 mApplySurfaceChangesTransaction 这个 lambda表达式 ,当前分析的场景是会把目标窗口状态设置为 READY_TO_SHOW
    1. Framework 层对Surface 的操作,当前场景就是会提交一个 Surface 显示的事务

然后还有一个重要的数据结构:mTmpUpdateAllDrawn

这个集合存储的是这次 layout 执行到当前 applySurfaceChangesTransaction 方法时,哪些 ActivityRecord 需要更新 allDrawn 属性了。

ActivityRecord 下面的这个 allDrawn 变量表示当前 ActivityRecord 下面的窗口是否全部绘制。

执行方法前会先把 mTmpUpdateAllDrawn 清空,然后在方法末尾遍历是否有元素。 那这个集合的元素是在哪里添加的呢?

在每个 WindowState 执行 mApplySurfaceChangesTransaction 时,如果符合条件就会加入集合。

2.4 WindowState状态 -- READY_TO_SHOW

arduino 复制代码
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);

这句代码会从上到下遍历每个窗口,然后执行 lambda 表达式, mApplySurfaceChangesTransaction 的定义如下

java 复制代码
# DisplayContent

    private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
	    ......
        final WindowStateAnimator winAnimator = w.mWinAnimator;
        ......
        // 判断当前是否有Surface
        if (w.mHasSurface) {
            // Take care of the window being ready to display.
            // 重点 * 1. 主流程设置为 READY_TO_SHOW
            final boolean committed = winAnimator.commitFinishDrawingLocked();
            ......
        }
        ......
        // 重点 * 2. allDrawn 相关逻辑
        // 拿到这个WindowState所属的ActivityRecord
        final ActivityRecord activity = w.mActivityRecord;
        // 已经请求可见
        if (activity != null && activity.isVisibleRequested()) {
            activity.updateLetterboxSurface(w);
            // 更新绘制状态
            final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
            if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
                // 符合条件加入集合
                mTmpUpdateAllDrawn.add(activity);
            }
        }
        w.updateResizingWindowIfNeeded();
    };

这里有2个逻辑需要注意:

    1. 主流程,将状态设置成 READY_TO_SHOW
    1. allDrawn 属性逻辑

当前还是先看 READY_TO_SHOW 流程,allDrawn相关的后面单独梳理。

mHasSurface 在relayoutWindow 流程创建 Surface 时设置为true,表示当前windowState的是否有 Surface 。 然后就调用其 WindowStateAnimator::commitFinishDrawingLocked 。

csharp 复制代码
# WindowStateAnimator

    boolean commitFinishDrawingLocked() {
        ......
        // 1. 当前状态的判断,不满足则return
        if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
            return false;
        }
        // 2. 日志
        ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
                mSurfaceController);
        // 3. 设置状态
        mDrawState = READY_TO_SHOW;
        ......
        // 4. 系统Window或者StartWindow则会走后续流程设置为 HAS_DRAW
        if (activity == null || activity.canShowWindows()
                || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
            result = mWin.performShowLocked();
        }
        return result;
        ......
    }

这里有4个点,其中后面2个很重要:

    1. 这个方法每次 layout 的时候都可能会执行过来,所以要判断当前窗口的状态是否符合条件执行后面的逻辑
    1. 这个日志是上层分析黑屏问题看状态状态的关键
    1. 窗口状态设置成 READY_TO_SHOW ,这也是当前分析的主要里程碑
    1. 满足3个条件之一就会执行下一步,将窗口状态设置为 HAS_DRAW
    • 4.1 不依赖 Activity 的窗口,一般是状态栏导航栏这种系统窗口,或者应用启动的悬浮窗
    • 4.2 ActivityRecord::canShowWindows 是否可以显示窗口。这个方法的返回值又受3个因素影响,下一小节解释。
    • 4.3 窗口类型为 StartWindow

我们当前分析场景是不是系统窗口和StartWindow,不过是符合了第二个条件的,详细看下一节。

2.5 WindowState状态 -- HAS_DRAW

现在执行到 WindowStateAnimator::commitFinishDrawingLocked 方法,窗口状态已经为 READY_TO_SHOW 了,离最终状态还差一步。 根据上面代码可知,只要条件符合就会进入执行 WindowState::performShowLocked 完整 HAS_DRAW 的设置,根据这3个条件,当前分析的是应用窗口,如果要进入肯定是第二个 ActivityRecord::canShowWindows 返回true 。

先看一下这个方法。

scss 复制代码
# ActivityRecord
    /**
     * @return Whether we are allowed to show non-starting windows at the moment. We disallow
     *         showing windows during transitions in case we have windows that have wide-color-gamut
     *         color mode set to avoid jank in the middle of the transition.
     */
    boolean canShowWindows() {
        return allDrawn && !(isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)
                && hasNonDefaultColorWindow());
    }

根据注释:方法返回当前是否允许显示非启动窗口。不允许在转换过程中显示窗口,以防我们有宽色域的窗口颜色模式设置为避免在过渡中间出现jank。

感觉似懂非懂还是直接看具体代码的3个条件吧:

    1. allDrawn :当前ActivityRecord 下的所有窗口都已经绘制
    1. 第二个条件是窗口没在做动画(除StartWindow外)
    1. 没有设置非默认颜色模式的窗口 (不知道啥意思,不过正常都为true)

如果因为这个方法返回 false 导致没有走到设置 HAS_DRAW 状态,则可以 debug 一下是哪个值异常了,具体情况具体分析。

其中比较常见会导致返回 false 的一般是 allDrawn 这边变量导致的。 这边变量定义在 ActivityRecord 下,表示当前 ActivityRecord 下的所有窗口 是否都绘制完成并准备显示。

2.5.1 allDrawn 属性逻辑

首先 layout 会触发 RootWindowContainer::performSurfacePlacement 方法的调用,而且我们知道一次 layout 内部可能会执行多次 RootWindowContainer::performSurfacePlacement

以我当前的源码版本,这个逻辑大部分情况都是执行2次 (刷过android 14的版本,执行了一次)

来完整看一下这边变量的设置逻辑

scss 复制代码
# DisplayContent
    // 需要更少是否已经绘制的集合
    private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();

    void applySurfaceChangesTransaction() {
        ......
        // 1. 新的执行,清除数据
        mTmpUpdateAllDrawn.clear();
        ......
            // 2. 遍历所有窗口执行 lambda表达式
            forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
        ......
        // Surface 操作
        prepareSurfaces();
        ......
        // 如果有需要更新的ActivityRecord则处理
        while (!mTmpUpdateAllDrawn.isEmpty()) {
            final ActivityRecord activity = mTmpUpdateAllDrawn.removeLast();
            // See if any windows have been drawn, so they (and others associated with them)
            // can now be shown.
            // 3. 内部会设置这个ActivityRecord 下的 allDrawn 为true
            activity.updateAllDrawn();
        }
    }

这里和 allDrawn 相关的操作分三步:

    1. 把 mTmpUpdateAllDrawn 的数据清除
    1. 在执行 mApplySurfaceChangesTransaction 这个 Lambda 的时候把符合条件的 ActivityRecord 添加到集合中
    1. 对集合进行遍历,内部将 allDrawn 设置为 true

先看第二步怎么把数据添加到 mTmpUpdateAllDrawn 的

2.5.1.1 mTmpUpdateAllDrawn 下元素的添加

这部分的代码之前也看过,当前关注下 allDrawn 相关的代码

java 复制代码
# DisplayContent

    private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
	    ......
        final WindowStateAnimator winAnimator = w.mWinAnimator;
        ......
        // 判断当前是否有Surface
        if (w.mHasSurface) {
            // Take care of the window being ready to display.
            // 重点 * 1. 主流程设置为 READY_TO_SHOW
            final boolean committed = winAnimator.commitFinishDrawingLocked();
            ......
        }
        ......
        // 重点 * 2. allDrawn 相关逻辑
        // 拿到这个WindowState所属的ActivityRecord
        final ActivityRecord activity = w.mActivityRecord;
        // 已经请求可见
        if (activity != null && activity.isVisibleRequested()) {
            activity.updateLetterboxSurface(w);
            // 更新绘制状态
            final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
            if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
                // 符合条件加入集合
                mTmpUpdateAllDrawn.add(activity);
            }
        }
        w.updateResizingWindowIfNeeded();
    };

commitFinishDrawingLocked 方法的时候将窗口状态设置成了 READY_TO_SHOW ,下面如果 ActivityRecord::updateDrawnWindowStates 返回 true 就可以添加到 mTmpUpdateAllDrawn 中了

ini 复制代码
# ActivityRecord
    boolean updateDrawnWindowStates(WindowState w) {
        ......
        // 定义返回值
        boolean isInterestingAndDrawn = false;
        ......
            if (w != mStartingWindow) {
                if (w.isInteresting()) {
                    ......
                    if (w.isDrawn()) {
                        ......
                        // 设置为true
                        isInterestingAndDrawn = true;
                    }
                }
            } ......
        ......
        return isInterestingAndDrawn;
    }

应用窗口这个方法走的是这个逻辑,返回值受 WindowState::isDrawn 影响,如果返回 true,则这整个方法也返回 true 了。

csharp 复制代码
# WindowState
    /**
     * Returns true if the window has a surface that it has drawn a complete UI in to. Note that
     * this is different from {@link #hasDrawn()} in that it also returns true if the window is
     * READY_TO_SHOW, but was not yet promoted to HAS_DRAWN.
     */
    public boolean isDrawn() {
        return mHasSurface && !mDestroying &&
                (mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
    }

根据注释和代码,前面的2个条件一般都满足,所以只要窗口状态为 READY_TO_SHOW 或者 HAS_DRAWN 就返回 true 。 然后还提到了,有一个 hasDrawn ,区别就是 hasDrawn 只要状态为 HAS_DRAWN 才返回true 。

csharp 复制代码
# WindowState
    public boolean hasDrawn() {
        return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
    }

当前执行到这的时候窗口状态刚被设置为 READY_TO_SHOW ,所以满足条件返回 true 。

这块逻辑就清楚了。在 layout 的时候,会让每个窗口执行 mApplySurfaceChangesTransaction ,条件满足的话会把窗口状态设置成 READY_TO_SHOW ,并将这个窗口对应的 ActivityRecord 添加到 mTmpUpdateAllDrawn 集合。 然后再遍历集合执行 ActivityRecord::updateAllDrawn 。

2.5.1.2 设置 allDrawn = true

在 DisplayContent::applySurfaceChangesTransaction 方法最后会遍历 mTmpUpdateAllDrawn 的数据来执行 ActivityRecord::updateAllDrawn 方法

scss 复制代码
# ActivityRecord

    void updateAllDrawn() {
        if (!allDrawn) {
            ......
            if (numInteresting > 0 && allDrawnStatesConsidered()
                    && mNumDrawnWindows >= numInteresting && !isRelaunching()) {
                if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawn: " + this
                        + " interesting=" + numInteresting + " drawn=" + mNumDrawnWindows);
                // 重点* 1. 设置为true
                allDrawn = true;
                // Force an additional layout pass where
                // WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
                if (mDisplayContent != null) {
                    // 重点* 2. 还需要layout
                    mDisplayContent.setLayoutNeeded();
                }
                //  发生信息通知 Task 设置了 allDrawn
                mWmService.mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, this).sendToTarget();
            }
        }
    }
    1. 设置 allDrawn = true
    1. 设置标志位,那说明还需要再执行 layout (再一次执行 requestTraversal)

当前分析的是第一次执行 layout 时在执行 WindowStateAnimator::commitFinishDrawingLocked 方法的时候会把 WindowState 状态设置 READY_TO_SHOW ,但是由于此时 allDrawn = false,所以 ActivityRecord::canShowWindows 返回 false ,流程就不会往下走了。 不过这一次 layout 执行到 ActivityRecord::updateAllDrawn 把 allDrawn 设置为 true 了,并又触发了一次 layout 第二次 layout 还是会执行到 这个时候再执行到 WindowStateAnimator::commitFinishDrawingLocked 方法,这一次因为条件已经满足了,所以会执行 WindowState::performShowLocked 来把窗口状态设置成 HAS_DRAW

2.5.2 真正设置 -- HAS_DRAW

经过上面的分析,现在是第二遍执行 layout 走到 WindowStateAnimator::commitFinishDrawingLocked 方法时,条件满足执行到 WindowState::performShowLocked

kotlin 复制代码
# WindowState
    boolean performShowLocked() {
    	......
		// 获取到当前状态
        final int drawState = mWinAnimator.mDrawState;
        // 当前分析过来的条件肯定都是满足的
        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
        	//窗口类型不为启动窗口
            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
                // remonve startWindow 流程
                mActivityRecord.onFirstWindowDrawn(this);
            } else {
                mActivityRecord.onStartingWindowDrawn();
            }
        }
		// 不满足条件则直接返回
        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
            return false;
        }
        ......
        // Force the show in the next prepareSurfaceLocked() call.
        mWinAnimator.mLastAlpha = -1;
        // 日志
        ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
        // 重点* 状态为HAS_DRAWN
        mWinAnimator.mDrawState = HAS_DRAWN;

        mWmService.scheduleAnimationLocked();
        ......
        return true;
    }

这里会对当前床头状态做检查,只有满足条件才会将状态设为 HAS_DRAWN。 然后因为这会窗口已经要显示了,可以移除 StartWindow 了。(以后会单独分析)

到这里窗口的状态已经设置成 HAS_DRAW 表示窗口已经显示了,但是这里只是状态,一个类的变量而已,还没看到实际显示的代码, 因为具体的 Surface 显示逻辑在 prepareSurfaces 中。

2.6 显示Surface

到这里状态状态已经是 READY_TO_SHOW 了,现在需要真正的将窗口显示事务提交到 SurfaceFlinger

java 复制代码
# DisplayContent

    @Override
    void prepareSurfaces() {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
        try {
            // 1. 拿到事务
            final Transaction transaction = getPendingTransaction();
            // 2. 调用父类方法
            super.prepareSurfaces();

            // 3. 把事务merge到全局事务,供后续统一处理
            SurfaceControl.mergeToGlobalTransaction(transaction);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }

这里的主要流程是"super.prepareSurfaces();",不过可以看到前后先是获取到了一个事务,然后再把这个事务merge到全局事务,这个全局事务就是 SurfaceControl 下面的一个类,也是一个 Surface 事务。

那说明这直接的 super.prepareSurfaces() 会有Surface 事务的处理,才需要把它 merge 到全局事务中。

当前逻辑中 super.prepareSurfaces() 内部对Surface 的处理就是将目标创建的 Surface 显示的事务

统一处理 GlobalTransaction 的时机就是在 2.2 小节看 RootWindowContainer::performSurfacePlacementNoTrace 方法的时候在执行 applySurfaceChangesTransaction 完成后处理。

DisplayContent 的父类是 DisplayArea ,不过 DisplayArea::prepareSurfaces 方法也是调用了父类 WindowContainer 的方法,所以直接看 WindowContainer::prepareSurfaces

csharp 复制代码
# WindowContainer
    void prepareSurfaces() {
        ......
        for (int i = 0; i < mChildren.size(); i++) {
            // 遍历孩子
            mChildren.get(i).prepareSurfaces();
        }
    }

WindowContainer::prepareSurfaces 这个方法被很多子类重写,比如前面提到的 DisplayContent 和 DisplayArea ,另外还有像场景的 Task , ActivityRecord , WindowState 等,但是他们内部重写的逻辑也会再调用 "super.prepareSurfaces();"来遍历他的孩子,最终会调用到 DisplayContent 每个容器类,这里的调用略微有点绕,不过也不是很复杂,放慢思路理一下就好了。

其他类的重新不管,当前分析的窗口,所以直接看 WindowState 的实现

scss 复制代码
# WindowState

    @Override
    void prepareSurfaces() {
        ......
        // 主流程
        mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
        // 调用父类,继续遍历它的孩子
        super.prepareSurfaces();
    }

主要是调用了 WindowStateAnimator::prepareSurfaceLocked 方法,注意参数是是获取了一个 Transaction ,说明要开始对 Surface 做操作了。

typescript 复制代码
# WindowStateAnimator
    void prepareSurfaceLocked(SurfaceControl.Transaction t) {
        ......
        // 状态是 HAS_DRAWN 才执行
        if (prepared && mDrawState == HAS_DRAWN) {
            if (mLastHidden) {
                if (showSurfaceRobustlyLocked(t)) {
                    ......      
                }
            }
        }
        ......
    }
    private boolean showSurfaceRobustlyLocked(SurfaceControl.Transaction t) {
        // 主流程
        boolean shown = mSurfaceController.showRobustly(t);
        if (!shown)
            return false;

        t.merge(mPostDrawTransaction);
        return true;
    }

这里又将 Transaction 交到了 WindowSurfaceController 处理,这个类我们在 relayoutWindow 的时候创建窗口 Surface 的时候提过,Window 的 Surface 创建是在这里类控制的,那么提交的逻辑交给他也很合理。

typescript 复制代码
# WindowSurfaceController
    boolean showRobustly(SurfaceControl.Transaction t) {
        // 关键日志
        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
        ......
        // 2. 内部将mSurfaceShown设置为true
        setShown(true);
        // 3. 重点* 真正的提交
        t.show(mSurfaceControl);
        ......
    }
    1. 这个日志很关键,表示 Framework 已经将 Surface 提交到 SurfaceFlinger 了。(严格来说需要等后面事务的apply)
    1. 将 mSurfaceShown 变量设置为true, 这个也是分析黑屏问题dump要看第一个关键变量,如果为 false 说明窗口并没有显示,可能是被遮挡了
    1. 这里看到操作 Surface Transaction 的地方了, 这个show ,就说明需要把 Suface 显示。 也是 finishDrawingWindow 最终的结果。

然后只要找到 Surface Transaction 做 apply 的操作的地方,就说明 Framework 层已经通知 SurfaceFlinger 了。

2.6.1 真正的窗口显示--提交显示Surface事务到SurfaceFlinger

这一步在 2.2 小节提过了,所以直接看 WindowManagerService::closeSurfaceTransaction 方法

typescript 复制代码
# WindowManagerService
    /**
     * Closes a surface transaction.
     * @param where debug string indicating where the transaction originated
     */
    void closeSurfaceTransaction(String where) {
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
            SurfaceControl.closeTransaction();
            mWindowTracing.logState(where);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
# SurfaceControl
    // SurfaceControl下定义的全局事务
    static GlobalTransactionWrapper sGlobalTransaction;

    public static void closeTransaction() {
        synchronized(SurfaceControl.class) {
            ......
            sGlobalTransaction.applyGlobalTransaction(false);
        }
    }

    private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
        void applyGlobalTransaction(boolean sync) {
            ......
            //  重点* apply
            nativeApplyTransaction(mNativeObject, sync);
        }

    }
相关推荐
浩浩乎@19 分钟前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组2 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19962 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸2 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间2 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见2 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android
没有了遇见4 小时前
Android RecycleView 条目进入和滑出屏幕的渐变阴影效果
android
站在巨人肩膀上的码农4 小时前
去掉长按遥控器power键后提示关机、飞行模式的弹窗
android·安卓·rk·关机弹窗·power键·长按·飞行模式弹窗
呼啦啦--隔壁老王4 小时前
屏幕旋转流程
android
人生何处不修行5 小时前
实战:Android 15 (API 35) 适配 & 构建踩坑全记录
android