【Android 14源码分析】BLASTSyncEngine 设计剖析

1,243 阅读19分钟

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

                        -- 服装学院的IT男

1. 概览

1.1 设计目的

最开始看到这个类是之前看分屏逻辑的时候,由于分屏涉及到2个 Task 的操作,为了避免黑屏等异常显示,最好的处理方式就是将2个 Task 及其子容器对 Surface 的操作(通过Transition) 一起提交给 SF 。

所有我理解 BLASTSyncEngine 设计的目的就是收集所有参与同步操作容器的 Transition 最后一起同步处理。

非权威的解释代码中各种 BLAST 的意义,比如 BLASTSyncEngine , BLASTBufferQueue 等类的命名都使用了 “BLAST” BLAST 是 Basic Local Alignment Search Tool 的缩写,意即碱基局部对准检索工具 ,对应到 framework 框架设计就是处理局部容器同步事务的工具 说人话就是,在系统框架运行期间,某些逻辑需要容器 SurfaceTransition 一起执行,也就是“同步执行”,所以专门设计了一套处理方式,就是 BLASTSyncEngine 。

1.2 设计模型

为了达到同步的目的, google 对于 BLASTSyncEngine 引擎的工作模型如图:

一级框图-BLASTSyncEngine引擎.png

    1. BLASTSyncEngine 是一个 WMS 下的成员变量
    1. BLASTSyncEngine 下维护了一个集合 mActiveSyncs ,这个集合里的元素是 SyncGroup
    1. SyncGroup 代表一次同步任务组,也就是系统中某个逻辑,需要使用同步引擎完成时,就会为这次操作创建一个 SyncGroup
    1. SyncGroup 下维护了一个集合 mRootMembers ,内部保存的是容器,但是一般都是 Task 或者 ActivityRecord 级别。因为一般需要同步操作时,改变操作的也是这个级别的容器,命名为 “root”是因为虽然添加进集合的是它们,但是它们内部还有子容器,一般指的是 WindowState
    1. SyncGroup 创建的时候接收一个接口回调: TransactionReadyListener ,这个回调也将被保存在成员变量 mListener 中。作用是这次同步任务结束后,通过接口方法回调给使用者
    1. SyncGroup 下 mRootMembers 集合所有容器极其子容器都完成同步后,这个同步组的同步任务也就完成了,就会回调 TransactionReadyListener::onTransactionReady 方法回调给调用者
    1. TransactionReadyListener::onTransactionReady 方法参数有一个 SurfaceControl.Transaction ,这个也是 BLASTSyncEngine 的核心
    • 7.1 参与同步的容器都有对应的 SurfaceControl.Transaction ,比如图中的 ST1 ST2 ST3 ,这些事务中包含了这次操作各个容器对应的 Surface 操作
    • 7.2 当同步任务完成时,会收集参与容器的 SurfaceControl.Transaction 合并成一个 SurfaceControl.Transaction 作为 TransactionReadyListener::onTransactionReady 的参数回调出去,也就是图中的 ST 4
    • 7.3 发起同步任务者,收到回调的同时,也拿到了 SurfaceControl.Transaction 的合集,就可以做对应的行为了

一次同步,可以分为以下5个步骤:

流程图-同步总流程.png

后面的将围绕着这5步,详细分析。

2. 第一步:开始一个同步组

2.1 概念介绍

想要启动一个同步操作首先就需要为这次操作创建一个同步组: SyncGroup ,后续才能将需要参与同步的容器添加进这个 SyncGroup 。 另外,既然是一次同步操作,发起者肯定是需要知道什么时候同步结束的,所以创建同步组的时候需要传递对应的一个接口用于回调。 同时创建的这个同步组,也会有唯一的一个 ID 与其对应,这 id 来自 BLASTSyncEngine 内部变量 mNextSyncId 递增。

框图-第一步-创建同步组.png

实现这一目的,分为3步:

    1. 传入 TransactionReadyListener 并构建出一个 SyncGroup 对象,创建后会有唯一 ID
    1. 添加新建的 SyncGroup 到 mActiveSyncs 集合
    1. 触发超时处理。 一次操作同步逻辑需要在一定时间内完成,否则视为超时,目前默认是 5000 毫秒,超时则结束当前这个同步组

2.2 代码介绍

为了实现第一步,google 当前代码提供的 API 如下:

# BLASTSyncEngine

    // 同步组ID,自增,对应一个SyncGroup
    private int mNextSyncId = 0;
    
    // 保存正在执行的 SyncGroup
    private final ArrayList<SyncGroup> mActiveSyncs = new ArrayList<>();

    // 重载 A
    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
            boolean parallel) {

        // 创建一个 SyncGroup
        final SyncGroup s = prepareSyncSet(listener, name);
        // 重载
        startSyncSet(s, timeoutMs, parallel);
        // 返回新建同步组的ID
        return s.mSyncId;
    }

    // 重点 1. 构造一个 SyncGroup 
    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
        return new SyncGroup(listener, mNextSyncId++, name);
    }

    // 重载 B
    void startSyncSet(SyncGroup s) {
        startSyncSet(s, BLAST_TIMEOUT_DURATION, false /* parallel */);
    }

    // 重载 C
    void startSyncSet(SyncGroup s, long timeoutMs, boolean parallel) {
        ......
        // 重点 2. 添加到正常处理的同步事务组集合中
        mActiveSyncs.add(s);
        ......
        // 日志
        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started %sfor listener: %s",
                s.mSyncId, (parallel && alreadyRunning ? "(in parallel) " : ""), s.mListener);
        // 重点 3. 超时处理
        // 也就是说开始一个同步组后,如果一段时间内(0.5s)没完成,则做超时处理器(结束当前同步组),不影响后续逻辑
        scheduleTimeout(s, timeoutMs);
    }

关键代码都加上了注释,这里会打印一下日志:

打印日志: WindowManager: SyncGroup 40: Started for listener: TransitionRecord{6e037e id=-1 type=OPEN flags=0x0}

输出了同步的 ID ,和创建传递进来的监听者(一般也是同步任务的发起者)。

TransactionReadyListener 是 BLASTSyncEngine 内部定义的一个接口,就一个方法,2个参数。

我目前 U 的代码只有 Transition 和 WindowOrganizerController 2个类实现了这个接口。

# BLASTSyncEngine
    interface TransactionReadyListener {
        void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
    }

2.3 使用方式

这里 startSyncSet 有3个重载,这里标记为重载 A B C 和一个创建 SyncGroup 的方法: SyncGroup::prepareSyncSet 。 google 提供了这4个方法,那么要实现启动一个同步组的操作的调用可以有以下方式;

    1. 直接调用 startSyncSet 方法重载 A ,内部会调用 SyncGroup::prepareSyncSet 并执行重载 C
    1. 先调用 SyncGroup::prepareSyncSet 再调用重载 B ,在 重载 B 内部又会调用重载 C
    1. 先调用 SyncGroup::prepareSyncSet 再调用重载 C

也就是说 SyncGroup::prepareSyncSet 和 startSyncSet 重载 C 方法是必定需要执行的。 而且肯定还是需要传递 TransactionReadyListener 接口的。

流程图-第一步-创建同步组.png

3. 第二步:添加容器进同步组

3.1 概念介绍

同步组创建好后,第二步则需要将需要参与同步的容器添加进同步组。

3.2 代码介绍

这一步 google 只提供了一个 API :BLASTSyncEngine::addToSyncSet

# BLASTSyncEngine
    // 同步组ID,自增,对应一个SyncGroup
    private int mNextSyncId = 0;
    
    // 保存正在执行的 SyncGroup
    private final ArrayList<SyncGroup> mActiveSyncs = new ArrayList<>();

    void addToSyncSet(int id, WindowContainer wc) {
        // 1. 根据ID从 mActiveSyncs 集合找到对应的 SyncGroup
        getSyncGroup(id).addToSync(wc);
    }

# BLASTSyncEngine&SyncGroup

        // 保存参与事务的 root 成员容器
        final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();

        private void addToSync(WindowContainer wc) {
            if (mRootMembers.contains(wc)) {
                // 已经添加了,则直接return
                return;
            }
            // 日志
            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
            final SyncGroup dependency = wc.getSyncGroup();
            if (dependency != null && dependency != this && !dependency.isIgnoring(wc)) {
                ......// 组冲突处理
            } else {
                // 2. 添加到集合
                mRootMembers.add(wc);
                // 3. 调用容器方法
                wc.setSyncGroup(this);
            }
            // 4. 预处理,设置容器同步状态
            wc.prepareSync();

            if (mReady) {
                // 触发一次layout
                mWm.mWindowPlacerLocked.requestTraversal();
            }
        }

不考虑异常情况这里分为4步:

    1. 根据 id 在 mActiveSyncs 里找出对应的 SyncGroup
    1. 将容器保存到 SyncGroup 下的 mRootMembers 集合,这个集合里保存的容器级别是 ActivityRecord ,Task 。他们还有自己的子元素,所以命名叫 “root”
    1. 设置容器属于当前同步组 (3.2.1)
    1. 设置容器及其孩子的同步状态(3.2.2)

打印日志: WindowManager: SyncGroup 40: Adding to group: ActivityRecord{55c10df u0 com.android.dialer/.main.impl.MainActivity t-1}

3.2.1 容器设置同步组

# WindowContainer

    // 当前容器属于哪个同步组
    BLASTSyncEngine.SyncGroup mSyncGroup = null;

    void setSyncGroup(@NonNull BLASTSyncEngine.SyncGroup group) {
        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
        ......// 异常处理
        // 设置当前容器所属同步组
        mSyncGroup = group;
    }

打印日志: WindowManager: setSyncGroup #19 on ActivityRecord{41d841a u0 com.android.dialer/.main.impl.MainActivity t-1}

3.2.2 预处理容器同步状态

同步组的参与者是容器,当容器被添加进同步组后,则会调用 WindowContainer::prepareSync 来设置容器的同步状态。

容器如图有3个同步状态:

容器同步状态.png

默认为 SYNC_STATE_NONE 表示没有参与同步,不过目前容器已经被添加进同步组了,所以要修改其同步状态。

# WindowContainer

    /**
    * 准备同步操作。
    * 检查当前容器是否已经处于同步状态,并尝试将其子元素设置为同步准备状态。
    * 
    * @return 如果当前容器未参与同步且成功准备所有子元素,则返回true;否则返回false。
    */
    boolean prepareSync() {

        // 当前同步状态
        @SyncState int mSyncState = SYNC_STATE_NONE;

        // 重点 1. 如果不为默认状态,则说明已经在同步了,返回false
        if (mSyncState != SYNC_STATE_NONE) {
            return false;
        }
        
        // 遍历所有的子元素,从最后一个开始向前遍历
        for (int i = getChildCount() - 1; i >= 0; --i) {
            // 获取当前索引的子元素,它应该是一个WindowContainer类型的对象。
            final WindowContainer child = getChildAt(i);
            
            // 重点 2. 调用子元素的prepareSync方法,使其也进入同步准备状态。
            child.prepareSync();
        }
        
        // 重点 3. 将当前容器的同步状态设置为READY,表示已准备好进行同步。
        mSyncState = SYNC_STATE_READY;
        
        // 返回true,表示当前容器及其子元素已经准备好进行同步。
        return true;
    }
    1. 如果状态不为 SYNC_STATE_NONE ,则说明已经在同步了,返回false,如果当前状态是 SYNC_STATE_NONE 则说明还没有执行准备工作,后续逻辑会为当前容器设置同步状态
    1. 遍历子元素执行 prepareSync 方法,只有 WindowToken 和 WindowState 重写了该方法
    1. 返回值,只要不是 SYNC_STATE_NONE 状态,都会返回 true , 并且将状态设置为 SYNC_STATE_READY

对于 ActivityRecord , Task 这种级别的容器,自身是没有 UI 绘制的,所以一开始就可以将同步状态设置为 SYNC_STATE_READY ,但是他如果子元素有 WindowState ,那实现将不一样了。WindowState 的同步状态会被设置成 SYNC_STATE_WAITING_FOR_DRAW 。

# WindowState

    // 同步请求ID 递增
    int mSyncSeqId = 0;
    
    @Override
    boolean prepareSync() {
        ......
        if (!super.prepareSync()) {
            return false;
        }
        ......
        // 其他容器直接标记为就绪状态,因为只需要等待子窗口绘制,自己本身不用绘制
        // 而当前是 WindowState ,需要绘制完才能标记为就绪状态
        // * 标记同步状态为等待绘制
        mSyncState = SYNC_STATE_WAITING_FOR_DRAW;

        ......
        // 同步请求ID 递增
        mSyncSeqId++;
        ...... //根据同步方法的不同,请求重新绘制窗口
        return true;
    }

3.2.3 小结

这一步将容器添加进同步组 ,这点比较好理解,毕竟同步组要完成同步,那肯定需要参参与者。

稍微难理解的可能是添加进同步组后执行 WindowContainer::prepareSync 设置容器状态的逻辑。

prepareSync预处理容器状态.png

执行 BLASTSyncEngine::addToSyncSet 添加进同步组的容器级别一般是 ActivityRecord ,Task ,这类级别的容器因为本身没有 UI 所以可以直接设置为 SYNC_STATE_READY 状态,而它们一般都有孩子,最下面的孩子是 WindowState ,这个容器是需要绘制的,所以 WindowState 的状态要被设置为 SYNC_STATE_WAITING_FOR_DRAW ,表示当前状态为等待绘制。

3.3 使用方式

调用 BLASTSyncEngine::addToSyncSet 方法,传递对应的同步组 ID 和容器即可。

流程图-第二步-添加容器.png

4. 第三步:容器应用配置

开发者使用同步引擎就是业务逻辑需要修改多个容器的属性,配置时会触发容器重绘,而业务逻辑需要等所有容器重绘后,拿到各个容器等事务一起处理。

现在第一步,第二步分别创建好同步组,并且相关容器也被添加进同步组后,开发人员就可以触发原来的主要逻辑了,也就是触发相关容器的改变。

5. 第四步:设置同步组准备完毕

5.1 概念介绍

第一,二步是同步组的准备工作,第三步时触发原业务逻辑,现在一切都处理好了,只需要等各个容器应用属性,配置的改变,然后重绘完成后回调一个总的 SurfaceControl.Transaction 了。

不过在这之前,还需要执行一个步骤:设置同步组准备完毕 。

5.2 代码介绍

流程图-第四步-设置准备.png

这一步骤google 目前最想源码中提供了2个方法供开发者调用。

# BLASTSyncEngine

    void setReady(int id) {
        setReady(id, true);
    }

    boolean setReady(int id, boolean ready) {
        // 拿到对应的Group 
        return getSyncGroup(id).setReady(ready);
    }

// 设置对应同步组的准备情况
# BLASTSyncEngine&SyncGroup

            boolean mReady = false;

            private boolean setReady(boolean ready) {
            // 状态一致无需执行
            if (mReady == ready) {
                return false;
            }
            // 日志
            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
            mReady = ready;
            if (ready) {
                // 如果为true 则请求一次 layout
                mWm.mWindowPlacerLocked.requestTraversal();
            }
            return true;
        }

早期 SyncGroup::setReady 方法是没有参数的,也就是说只要调用了就会把 SyncGroup 下的 mReady 状态设置为 true 。 后面在处理 Bug: 169035022 的时候才增加了参数。

不考虑特殊场景,第四步会做这2件事:

    1. 设置 mReady = true
    1. 触发一次刷新

设置为 true 好理解,第二步为什么要触发 layout 呢? 窗口显示流程中,应用端绘制完成后,还是在 system_server 端的一次 layout 完成最终提交到 SF 的逻辑。

所以既然同步组目前已经就绪了,则可以触发一次 layout ,后面的第五步,也是同步引擎的最后一步,就是在每一次 layout 的时候都检查一下,参与同步的容器是不是都绘制好了。

打印日志: WindowManager: SyncGroup 40: Set ready true

5.3 使用方式

正常情况设置为准备状态调用1个参数的 BLASTSyncEngine::setReady 方法即可,传入对应 SyncGroup 的 id 。 如果想设置为 false ,则调用2个参数的 BLASTSyncEngine::setReady 方法,传入对应 SyncGroup 的 id ,第二个参数传 false 。

6. 第五步:检查是否同步结束

6.1 概念介绍

这一步时同步引擎的最后一步,就是在每一次系统 layout 的时候也同时检查同步组是否完成了绘制。

如果完成了,则表示当前同步组已经可以结束了,同步引擎回通过 TransactionReadyListener::onTransactionReady 方法将参与同步操作的容器的所有同步事务,回调给调用者。

流程图-第五步-试图结束同步.png

6.2 代码介绍

每次 layout 都会执行 RootWindowContainer::performSurfacePlacementNoTrace 方法。 在这里也会调用同步引擎的 BLASTSyncEngine::onSurfacePlacement 方法来检查是否已经同步完成。

# RootWindowContainer

    void performSurfacePlacementNoTrace() {
        ......
        // Trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
        // 开启Surface事务
        mWmService.openSurfaceTransaction();
        try {
            //  处理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);
        }
        ......
        // 重点: 检查是否同步组可以结束
        mWmService.mSyncEngine.onSurfacePlacement();
        // 处理App事务
        checkAppTransitionReady(surfacePlacer);
        ......
    }

applySurfaceChangesTransaction 流程会做很多事,包括处理窗口的 Surface ,在处理完后,看的调用了 BLASTSyncEngine::onSurfacePlacement 方法,这个和同步引擎有关系了。

# BLASTSyncEngine

    // 保存正在执行的 SyncGroup
    private final ArrayList<SyncGroup> mActiveSyncs = new ArrayList<>();
    
    // 临时要finish的集合
    private final ArrayList<SyncGroup> mTmpFinishQueue = new ArrayList<>();
    void onSurfacePlacement() {
        // 没有同步组,则退出
        if (mActiveSyncs.isEmpty()) return;
        // 加入临时要处理的集合
        mTmpFinishQueue.addAll(mActiveSyncs);
        ......
        // 遍历
        while (!mTmpFinishQueue.isEmpty()) {
            ......
            // 取出处理
            final SyncGroup group = mTmpFinishQueue.remove(0);
            final int grpIdx = mActiveSyncs.indexOf(group);
            // Skip if it's already finished:
            if (grpIdx < 0) continue;
            // 试图结束
            if (!group.tryFinish()) continue;
            ......
        }
    }

在每一次 layout 的时候同步引擎也会遍历自己下面的所有同步组,看看是不是完成同步了。

具体每个同步组是如何完成的,在 SyncGroup::tryFinish 方法。

# BLASTSyncEngine&SyncGroup

        // 保存参与事务的 root 成员容器
        final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();

        private boolean tryFinish() {
            // 没有执行第4步,则返回
            if (!mReady) return false;
            // 输出当前同步组的Id 和成员
            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
                    mSyncId, mRootMembers);
            ......
            // 这里遍历的是之前收集的容器
            for (int i = mRootMembers.size() - 1; i >= 0; --i) {
                final WindowContainer wc = mRootMembers.valueAt(i);
                // 有任意1个容器没完成同步,则这次 layout 当前调用返回 false
                if (!wc.isSyncFinished(this)) {
                    // 输出返回 fail 的原因
                    ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
                            mSyncId, wc);
                    return false;
                }
            }
            // 全部完成同步后逻辑
            finishNow();
            // 返回true,表示该组已完成同步
            return true;
        }

这里的日志会输出当前同步组的 Id 和成员:

打印日志: WindowManager: SyncGroup 11: onSurfacePlacement checking {ActivityRecord{296d886 u0 com.android.dialer/.main.impl.MainActivity t16}, ActivityRecord{49be865 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t14}, Task{9910f99 #16 type=standard A=10162:com.android.dialer}}

这里个方法会遍历同步组所有成员,只有全部容器都完成绘制时,这个同步组就可以结束了,就会执行 SyncGroup::finishNow 。 有任何一个容器没有完成绘制,则返回 false ,等下一次 layout 再判断,这里也会输出是因为哪个容器没有绘制完成导致的。

打印日志: WindowManager: SyncGroup 11: Unfinished container: Task{9910f99 #16 type=standard A=10162:com.android.dialer} 08-08 17:45:01.183 28071 28071 D View : frame={173, 1035, 346, 1242} scroll={0, 0} mText="相册" mLayout width=147 height=34 ......

可以看到这一次是因为 Task #16 没有绘制完所有 ID 为 11 的同步组就不能够完成同步。

6.2.1 容器完成同步的条件

这边可以看到容器是步时完成同步的逻辑在 WindowContainer::isSyncFinished 方法,从框架设计来说,同步引擎只需要一个返回值来知道容器是不是完成了同步,具体完成条件是什么算容器自己的业务逻辑了,这个代码是很容易被 google 修改的。不过目前还是可以简单了解一下当前的逻辑。

# WindowContainer

    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
        // 1. 不可见则不需要等待绘制,直接视为同步完成
        if (!isVisibleRequested()) {
            return true;
        }
        if (mSyncState == SYNC_STATE_NONE) {
            // 2. 设置容步状态
            prepareSync();
        }
        // 3. 等待绘制的状态肯定返回 false 
        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
            return false;
        }
        // READY
        // Loop from top-down.
        // 4. 从上往下遍历孩子
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer child = mChildren.get(i);
            final boolean childFinished = group.isIgnoring(child) || child.isSyncFinished(group);
            // 有任意1个孩子满足同时满足下面3个条件,则视为当前容器已经完成同步
            // 同步完成 
            // 请求可见
            // 不透明且填满父容器
            if (childFinished && child.isVisibleRequested() && child.fillsParent()) {
                // Any lower children will be covered-up, so we can consider this finished.
                return true;
            }
            // 有任意一个孩子没同步完成,则直接false
            if (!childFinished) {
                return false;
            }
        }
        // 5. 能走到这,就是没有子容器了。
        自己的状态不为 SYNC_STATE_WAITING_FOR_DRAW 那就可以视为同步完成了
        return true;
    }

从上面的代码可以发现一个容器是不是同步完成的条件是这个容器及其子容器是不是完成绘制了。 集合代码的注视,

    1. 如果一个容器都不需要显示,则可以直接视为同步完成
    1. 如果当前容器的状态是 SYNC_STATE_NONE 则说明还没有参与同步。 但是既然执行到这个容器的这个方法,那这种情况就说明当前容器的父容器被添加进同步组的时候,当前容器还没有创建挂载到父容器下。 不过父容器既然已经参与同步,那么当然容器肯定也需要参与,所以这个时候需要执行 WindowContainer::prepareSync 方法来为它和它的孩子们设置同步状态
    1. 如果当前容器同步状态是 SYNC_STATE_WAITING_FOR_DRAW ,那说明当前容器是一个还为完成绘制的 WindowState ,所以需要返回 false
    1. 这里是一个遍历也是递归,会遍历当前容器的孩子执行 WindowContainer::isSyncFinished 来判断是是不是完成了绘制。如果有任意一个孩子没有完成绘制,则整个容器也视为没有完成绘制。 这里的需要满足下面3个条件才算绘制完成:
    • 4.1 子容器是否绘制完成
    • 4.2 子容器是否请求可见
    • 4.3 子容器是否不透明且填满父容器
    1. 如果逻辑走在这,说明没有子容器,则且自身同步状态是 SYNC_STATE_READY ,那就可以返回 true 了。

上面第四点比较有有意思的是第三个条件 WindowContainer::fillsParent 方法,这个方法会被孩子重写该方法,需要关注的是 WindowState 的重写是判断自己如果是 StartWindow 就返回 true 否则返回 false 。

# WindowState
    @Override
    boolean fillsParent() {
        return mAttrs.type == TYPE_APPLICATION_STARTING;
    }

6.2.2 容器同步完成

如果同步组的容器都完成了同步,也就是都绘制完了,则会来到同步引擎的最后一个操作。

# BLASTSyncEngine&SyncGroup

        private SurfaceControl.Transaction mOrphanTransaction = null;

        private void finishNow() {
            if (mTraceName != null) {
                Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId);
            }
            // 日志
            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
            // 1. 拿到事务
            SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
            if (mOrphanTransaction != null) {
                // mOrphanTransaction合入到新建的 merged中
                merged.merge(mOrphanTransaction);
            }
            // 2. 遍历参与同步的容器,拿到它们的 Transaction
            for (WindowContainer wc : mRootMembers) {
                wc.finishSync(merged, this, false /* cancel */);
            }

            // 3. 遍历参与同步的容器,将其添加到wcAwaitingCommit集合
            final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
            for (WindowContainer wc : mRootMembers) {
                wc.waitForSyncTransactionCommit(wcAwaitingCommit);
            }
            // 4. 定义回调。当merged这个Transaction对象被apply后,执行其 onCommitted 方法
            class CommitCallback implements Runnable {
                // Can run a second time if the action completes after the timeout.
                boolean ran = false;
                public void onCommitted(SurfaceControl.Transaction t) {
                    synchronized (mWm.mGlobalLock) {
                        if (ran) {
                            return;
                        }
                        // 移除超时
                        mHandler.removeCallbacks(this);
                        ran = true;
                        // 参数进来是个空的Transaction
                        // 8. 收集 wcAwaitingCommit 下的事务
                        for (WindowContainer wc : wcAwaitingCommit) {
                            wc.onSyncTransactionCommitted(t);
                        }
                        // 提交事务
                        t.apply();
                        // 清空集合
                        wcAwaitingCommit.clear();
                    }
                }
                // 超时才执行
                // Called in timeout
                @Override
                public void run() {
                    ......
                    // 超时处理,执行 CommitCallback::onCommitted
                    synchronized (mWm.mGlobalLock) {
                        onCommitted(merged.mNativeObject != 0
                                ? merged : mWm.mTransactionFactory.get());
                    }
                }
            };
            // 创建提交回调
            CommitCallback callback = new CommitCallback();
            // 5. 提交监听
            merged.addTransactionCommittedListener(Runnable::run,
                    () -> callback.onCommitted(new SurfaceControl.Transaction()));
            ......
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
            // 6. 重点 * 同步引擎最后一步:回调总的 Transition 给调用者
            mListener.onTransactionReady(mSyncId, merged);
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            // 7. 从集合里移除
            mActiveSyncs.remove(this);
            ......
        }
    1. 获取了一个事务,则个事务也是同步组最终合并容器事务后,回调出去的总事务
    1. 遍历参与同步的容器,执行它们的 WindowContainer::finishSync 拿到它们的 Transaction 。具体实现稍后详看。
    1. 再次遍历参与同步的容器,将所有参与的容器都添加斤 wcAwaitingCommit 集合中了,和 mRootMembers 不同的是,wcAwaitingCommit 包含了所有容器,子容器也单独添加进去了
    1. 定义一个 CommitCallback ,其方法 onCommitted 将会在总事务提交后执行,做相应处理,对应第 8 点
    1. 添加一个事务提交监听,提交后触发 CommitCallback::onCommitted ,同时也有个超时处理
    1. 这里是同步引擎的最后一步,回调总事务出去给发起者
    1. 将当前同步组从集合中移除
    1. 事务提交后的监听,做一些处理

打印日志: WindowManager: SyncGroup 11: Finished!

流程图-finishNow.png

6.2.3 旧版本代码阅读

对于 SyncGroup::finishNow 最后一步其实最重要的是执行了 “mListener.onTransactionReady(mSyncId, merged);” 方法,将参与同步容器的事务 merge 到一个总的事务回调出去了。 其他的代码是后续 google 代码维护处理一些场景和 bug 新增的,早起的这个方法代码逻辑清晰很多,如下:

# BLASTSyncEngine&SyncGroup
    private void finishNow() {
        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
        SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
        if (mOrphanTransaction != null) {
            merged.merge(mOrphanTransaction);
        }
        for (WindowContainer wc : mRootMembers) {
            wc.finishSync(merged, false /* cancel */);
        }
        mListener.onTransactionReady(mSyncId, merged);
        mActiveSyncs.remove(mSyncId);
    }

可以看到,主要就是将 事务回调出去,并把同步组移除,也就意味着这次的同步操作完成。

6.3 使用方式

框架层每次 layout 会触发 BLASTSyncEngine::onSurfacePlacement 方法来检查是否已经同步完成,无需开发者手动操作。