【Android 14源码分析】ShellTransitions-2-requestStartTransition处理

807 阅读13分钟

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

                        -- 服装学院的IT男

建议阅读顺序:

BLASTSyncEngine设计剖析

ShellTransitions总体流程介绍

ShellTransitions-1-同步组初始化

ShellTransitions-2-requestStartTransition处理

ShellTransitions-3-动画前准备

ShellTransitions-4-播放动画与结束处理

1. 2次 WMCore -> WMShell 简介

ITransitionPlayer 的实现在 SystemUI 下的 Transitions 下的内部类 TransitionPlayerImpl 。

# com.android.wm.shell.transition.Transitions

    private final TransitionPlayerImpl mPlayerImpl;

    // Player 服务端
    @BinderThread
    private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
        @Override
        public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
                SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
                throws RemoteException {
            // WMCore -> WMShell onTransitionReady
            mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
                    iBinder, transitionInfo, t, finishT));
        }

        @Override
        public void requestStartTransition(IBinder iBinder,
                TransitionRequestInfo request) throws RemoteException {
            // WMCore -> WMShell requestStartTransition
            // 切线程,调用到外部类 Transitions 的方法
            mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
        }
    }

SystemUI 会创建 TransitionPlayerImpl 对象,并通过 WindowOrganizer::registerTransitionPlayer 传递给 system_server 端,之前在 TransitionController 看到的 mTransitionPlayer 就是这里传过去的对象,用于进程通信。

这边看到 TransitionPlayerImpl 只提供了2个方法,并且内部实现也只是简单的切换线程执行了外部类 Transitions 的方法。

这2个方法也是整个流程中 WMCore 调用 WMShell 的方法。

    1. TransitionPlayerImpl::requestStartTransition 的第一次 WMCore -> WMShell 通信,目的是为了让 SystemUI 端做三件事
    • 1.1 在 SystemUI 端创建一个和 Transition 对应的 ActiveTransition (通过 transitionToken 联系)
    • 1.2 为这个 ActiveTransition 匹配一个真正播放动画的处理类(也可能匹配不到)
    • 1.3 与 system_server 端通信,触发下一个流程 -- startTransition
    1. TransitionPlayerImpl::onTransitionReady 这是第二次 WMCore -> WMShell 通信,目的是要开始在 SystemUI 触发动画播放了。

当前先分析第一次 WMCore -> WMShell ,也就是 TransitionPlayerImpl::requestStartTransition 的逻辑。

2. requestStartTransition 阶段(第一次 WMCore -> WMShell)

# com.android.wm.shell.transition.Transitions

    // 应该是处理具体动画的集合,里面放了很多子类的对象
    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();

    // 稍后要执行动画的Transition
    private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();


    void requestStartTransition(@NonNull IBinder transitionToken,
            @Nullable TransitionRequestInfo request) {
        
        // 日志
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
                transitionToken, request);
        ......
        // 1. 创建对应的 ActiveTransition
        final ActiveTransition active = new ActiveTransition();
        WindowContainerTransaction wct = null;

        // 2. 从 mHandlers 集合中找到能播放动画的对象
        if (request.getType() == TRANSIT_SLEEP) {
            mSleepHandler.handleRequest(transitionToken, request);
            active.mHandler = mSleepHandler;
        } else {
            for (int i = mHandlers.size() - 1; i >= 0; --i) {
                // 通过 TransitionHandler::handleRequest 确定
                wct = mHandlers.get(i).handleRequest(transitionToken, request);
                if (wct != null) {
                    // 如果这个 TransitionHandler 能处理这个请求? 就设置给 ActiveTransition
                    active.mHandler = mHandlers.get(i);
                    break;
                }
            }
            ......
        }
        // 3. WMCShell -> WMCore 开始事务
        mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
        // 通过token 与 WMCore 的 Transitions联系
        active.mToken = transitionToken;
        // 放入集合
        mPendingTransitions.add(0, active);
    }

第一次 WMCShell -> WMCore 的逻辑不复杂,都在这了,先看一下这个方法打印的日志

打印日志: WindowManagerShell: Transition requested: android.os.BinderProxy@8a54fca TransitionRequestInfo { type = OPEN, triggerTask = TaskInfo{userId=0 taskId=17 displayId=0 isRunning=true baseIntent=Intent ......}, remoteTransition = null, displayChange = null }

这段日志的输出还是很长的,将 TaskInfo 部分做了省略,目前是以桌面冷启动场景分析的,可以看到 type 是 OPEN。

这里将方法分为3个点,和第一小节的描述对应上再详细解释下。

    1. 首先就创建了一个 ActiveTransition 对象 active ,并且在方法底下看到将 system_server 端 Transition 的 token 设置给了 ActiveTransition ,那这2个对象就有映射关系了。而且在代码中看到如果匹配上了播放动画的对象,则会赋值给 active 的 mHandler 对象,也就是说这个 mHandler 是一个过渡事务播放动画的对象
    1. 这一块逻辑的目的是为了给 ActiveTransition 匹配到一个播放动画的对象,当前 type = OPEN ,所以会通过 TransitionHandler::handleRequest 确定当前事务类型能用哪个 TransitionHandler 子类能播放这个事务的动画。看代码的实现也是有可能匹配不到的,也就是说当前这个方法执行完,可能这个 ActiveTransition 可能还是没有播放动画对象的(active.mHandler = null)
    1. 这一阶段 SystemUI 的处理就完成了,继续主流程走回 system_server 端

SystemUI 端的 requestStartTransition 阶段处理就完成了,逻辑相对简单。比较有 是 mHandlers 集合下存放的 TransitionHandler 对象到底是什么,简单理解它就是一个接口,定义了实现过渡事务的几个操作,当前场景主要关心的是过渡动画的播放,具体场景会匹配具体的子类来处理过渡动画的具体行为。

下面对这个类的简单介绍一下。

3. TransitionHandler 介绍

# com.android.wm.shell.transition.Transitions

    /**
     * Interface for something which can handle a subset of transitions.
     */
    public interface TransitionHandler {

        // 播放过渡动画,能成功播放返回 true
        boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction startTransaction,
                @NonNull SurfaceControl.Transaction finishTransaction,
                @NonNull TransitionFinishCallback finishCallback);

        // 合并其他动画到当前
        default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
                @NonNull TransitionFinishCallback finishCallback) { }

        // 处理事务请求,如果能处理则返回一个 WindowContainerTransaction 
        WindowContainerTransaction handleRequest(@NonNull IBinder transition,
                @NonNull TransitionRequestInfo request);

        // 事务消耗回调()
        default void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
                @Nullable SurfaceControl.Transaction finishTransaction) { }

        // 动画缩放设置,比如播放时长延长
        default void setAnimScaleSetting(float scale) {}
    }

当前主要关注 handleRequest 和 startAnimation 方法即可。 startAnimation 方法就是具体播放动画的处理里,后续流程会详细分析。 handleRequest 方法在 Transitions::requestStartTransition 中已经看到了。 如果这个事务能够被当前 TransitionHandler 的实现处理,则其 handleRequest 方法会返回一个 WindowContainerTransaction ,如果不能则返回 null 。 当前分析的冷启动场景,mHandlers 里的元素都返回 null ,也就是说 ActiveTransition 下的 mHandler 变量还是 null 。

我手上源码 TransitionHandler 的实现类有下面几个,当然还有没列出来的,看类名也能大概猜到是处理什么场景的过渡事务。

TransitionHandler实现类.png

后续的逻辑回到了 system_server 端执行 startTransition 阶段。

4. startTransition WMShell -> WMCore

WindowOrganizerController::startTransition
	Transition::start
		Transition::applyReady
			BLASTSyncEngine::setReady
				SyncGroup::setReady
	WindowOrganizerController::applyTransaction         -- 也可能是  onStartingWindowDrawn  -- > executeAppTransition 流程
		WindowOrganizerController::applyTransaction
			Transition::applyDisplayChangeIfNeeded
				Transition::setReady
					ReadyTracker::setReadyFrom    -- 设置准备好
					Transition::applyReady
						BLASTSyncEngine::setReady
							SyncGroup::setReady

当前场景在 Transitions::requestStartTransition 方法由于没有对应的 TransitionHandler 匹配,所以传递的 wct 是 null 。

# WindowOrganizerController
    @Override
    public void startTransition(@NonNull IBinder transitionToken,
            @Nullable WindowContainerTransaction t) {
        startTransition(-1 /* unused type */, transitionToken, t);
    }

    private IBinder startTransition(@WindowManager.TransitionType int type,
            @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
            ......
            // 根据 token 获取到 WMCore 对应的 Transition
            Transition transition = Transition.fromBinder(transitionToken);
            ......
            // 获取 wct ,参数为 null 就新建一个
            final WindowContainerTransaction wct =
                t != null ? t : new WindowContainerTransaction();
            // * 1
            transition.start();
            transition.mLogger.mStartWCT = wct; 
            // * 2 
            applyTransaction(wct, -1 /*syncId*/, transition, caller);
            ......
    }

注意传递的 syncId 是 -1 ,wct 现在也是有值的,如果参数为 null 就新建。

这里有2个逻辑:

    1. Transition::start 开始过渡事务
    1. WindowOrganizerController::applyTransaction 不过由于当前场景这里的 wct 是新建的没啥内容,所以这里的调用可以忽略

分析 Transition::start 逻辑,看方法名是要开始这个事务,那对应到同步引擎其实就是执行 SyncGroup::setReady 方法,设置这个同步组准备就绪

4.1 Transition::start

先看这条线

# Transition

    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();

    void start() {
        ......//异常状态处理

        // 设置状态
        mState = STATE_STARTED;
        // 日志
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
                mSyncId);
        applyReady();
        ......
        SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
    }

这里有2个点:

    1. 把 Transition 的状态设置为 STATE_STARTED
    1. 执行 applyReady 方法,目的是把同步组设置为就绪状态,但是内部逻辑也可能设置失败

打印日志: WindowManager: Starting Transition 6

# Transition

    private void applyReady() {
        // 1. 判断状态是否满足设置条件
        if (mState < STATE_STARTED) return;
        // 判断参与容器是否都准备好了
        // allReady query
        final boolean ready = mReadyTracker.allReady();
        // 日志
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                "Set transition ready=%b %d", ready, mSyncId);
        // 2. 设置同步组是否准备好了
        boolean changed = mSyncEngine.setReady(mSyncId, ready);
        ......
    }
    1. 从当前分析的调用链,状态肯定是 STATE_STARTED ,不过既然加了这个代码,说明肯定还有其他地方调用
    1. 这个方法在分析同步引擎的时候提过,既然这里调用了2个参数的方法,那说明 “ready” 可能就是 false

打印日志: WindowManager: Set transition ready=false 6

通过日志,也确实看到这一次的执行 “ready=false” 那么同步组就不能背设置为就绪状态,根据同步引擎的设计,只有当同步处于“ready=true”才能在每一次刷新的时候去检查是否同步完成。

而这里的 ready 值受 ReadyTracker::allReady 影响,先来看看这个方法的返回值受什么影响

4.2 了解 ReadyTracker

4.2.1 ReadyTracker介绍

首先看一下 ReadyTracker 类的定义,代码如下:

# Transition
    /**
     * The transition sync mechanism has 2 parts:
     *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
     *      launch or stop or get a new configuration?).
     *   2. Whether all the windows involved have finished drawing their final-state content.
     *
     * A transition animation can play once both parts are complete. This ready-tracker keeps track
     * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
     * even if the WM operations in one group are ready, the whole transition itself may not be
     * ready if there are WM operations still pending in another group. This class helps keep track
     * of readiness across the multiple groups. Currently, we assume that each display is a group
     * since that is how it has been until now.
     */
    // 静态内部类
    private static class ReadyTracker {
        // 跟踪的容器(DisplayContent级别)的准备状态
        private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();

        private boolean mUsed = false;
        private boolean mReadyOverride = false;
        private int mDeferReadyDepth = 0;

        void addGroup(WindowContainer wc) {
            if (mReadyGroups.containsKey(wc)) {
                Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
                return;
            }
            // 添加进跟踪组,默认是 false
            mReadyGroups.put(wc, false);
        }

        void setReadyFrom(WindowContainer wc, boolean ready) {
            mUsed = true;
            WindowContainer current = wc;
            while (current != null) {
                // 只有 DisplayContent 类型才能被添加进集合
                if (isReadyGroup(current)) {
                    // 设置 value
                    mReadyGroups.put(current, ready);
                    // 打印相关信息
                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
                            + " %b. group=%s from %s", ready, current, wc);
                    break;
                }
                current = current.getParent();
            }
        }

        // 设置全部准备完毕(目前不知道调用场景,可暂时忽略)
        void setAllReady() {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
            mUsed = true;
            mReadyOverride = true;
        }

        boolean allReady() {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
                    + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
                    groupsToString());
            
            if (!mUsed) return false;
            if (mDeferReadyDepth > 0) return false;
            if (mReadyOverride) return true;

            for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
                final WindowContainer wc = mReadyGroups.keyAt(i);
                if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
                // mReadyGroups 对应的 value 为 false 就返回 false
                if (!mReadyGroups.valueAt(i)) return false;
            }
            return true;
        }

        // 只有 DisplayContent 类型才能被 ReadyTracker 跟踪
        private static boolean isReadyGroup(WindowContainer wc) {
            return wc instanceof DisplayContent;
        }
    }

处理第一次 ReadyTracker::addGroup 被跟踪对象外,另外的方法会输出如下日志:

日志-ReadyTracker.png

注释都在代码上了,另外再提几点:

    1. ReadyTracker 是 Transition 内部类,可以理解为是 Transition 工作流程的一个帮助类
    1. 过渡动画播放有2个前提,也是 ReadyTracker 定义上的 google 注释:
    • 2.1 确定所有与特定过渡相关的窗口管理(WM)操作是否已经准备就绪,目前是以一个屏幕作为一组
    • 2.2 所有参与设计的窗口都需要绘制完完成

第二点好理解,第一点有点抽象,但是也是 ReadyTracker 这个类的功能,就是跟着是否满足第一个条件,第一个条件满足了 ReadyTracker::allReady 才返回 true ,才能将同步组设置为准备就绪,才会有 2.2 后续的逻辑。

  • 3. mReadyGroups 下的元素只能是 DisplayContent ,那以一个屏幕的手机为例,这个集合最少就只有1个元素
    1. 如果执行了 ReadyTracker::setAllReady 就表示全部准备就绪(没有设置延迟的话),不过目前没发现这个方法的调用场景,暂时忽略
    1. 根据日志,正常走到 ReadyTracker::setAllReady 能不能返回 true 和 mReadyGroups 这个 map 对应的 value 有关,而赋值的地方处理刚添加,其他时候就只能通过 ReadyTracker::setReadyFrom 赋值,这个方法还有会打印是哪个容器触发的设置值
    1. 调用 ReadyTracker::setReadyFrom 设置为 true 的场景和 Android 14 以前版本的 AppTransition 三大步的 executeAppTransition 是一样的,这个其实也就是 ReadyTracker 所谓跟踪的目的

4.2.2 跟踪者的添加

分析 ReadyTracker 的第一步先看看 ReadyTracker::addGroup 方法的执行。 执行的地方是在 Transition::collect 方法收集容器时,这个方法之前看过,不过这次看主要关注 ReadyTracker::addGroup 逻辑。

# Transition

    private final ReadyTracker mReadyTracker = new ReadyTracker();
    
    void collect(@NonNull WindowContainer wc) {
        ......
        // 日志,收集了某个容器
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
                mSyncId, wc);
        // 想要收集对应的屏幕到 ReadyTracker 中
        // getAnimatableParent方法会一直获取这个容器的父容器,直到是 DisplayContent 
        for (WindowContainer<?> curr = getAnimatableParent(wc);
                curr != null && !mChanges.containsKey(curr);
                curr = getAnimatableParent(curr)) {
            final ChangeInfo info = new ChangeInfo(curr);
            updateTransientFlags(info);
            mChanges.put(curr, info);
            // 如果是 DisplayContent
            if (isReadyGroup(curr)) {
                // 放入跟踪集合
                mReadyTracker.addGroup(curr);
                // 打印收集日志
                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
                                + " Transition %d with root=%s", mSyncId, curr);
            }
        }
        ......
        // 下面为收集容器事务,省略部分代码
        // 1. 加入到同步组
            mSyncEngine.addToSyncSet(mSyncId, wc); 
        // 2. 获取容器对应的 ChangeInfo ,记录这这个容器哪些属性发生了改变
        ChangeInfo info = mChanges.get(wc);
        // 3. 保存到集合
        mParticipants.add(wc);
        ......
    }

每当 Transition 收集一个容器时,就会执行 Transition::collect 方法,然后会通过 getAnimatableParent 方法不断获取容器的父容器,直到是 DisplayContent (Transition::isReadyGroup才返回true)。

当找到这个容器所在的 DisplayContent 时如果它还没有被添加进 ReadyTracker 跟踪,就执行 ReadyTracker::addGroup 方法添加,如果已经添加过就不处理了。 这里刚好有收集容器和 ReadyTracker 收集 DisplayContent 的日志,可以根据日志的输出分析执行逻辑。

这里看一下日志的输出:

日志-ReadyTracker-添加进集合.png

根据日志可以确认是在 Transition 收集 Task 容器的的时候 ReadyTracker 才收集了对应的 DisplayContent ,并不是在最初 Transition 收集 ActivityRecord 的时候。 这是因为 Transition 收集 ActivityRecord 的时候 ActivityRecord 才刚创建,还没有挂载到窗口树上(Task 都还没创建),所有无法找到其对应的屏幕也就无法收集。

4.2.3 ReadyTracker准备状态逻辑

现在已经知道 ReadyTracker 下有当前屏幕了,就可以去看什么时候设置为准备就绪的逻辑了。

根据日志 ReadyTracker::setReadyFrom 执行了3次,第一次是启动 Actvity 前期,上一个 Activity 要 pause 的时候,也就是执行了 TaskDisplayArea::pauseBackTasks . 后面2个其实都是 executeAppTransition 流程。

新的 Activity 要创建时相关日志:

过渡事务设置准备-启动Activity.png

StartingWindow绘制完成相关日志:

过渡事务设置准备-StartingWindow绘制完成.png

2次设置为 true 的调用链如下:

ActivityTaskSupervisor::realStartActivityLocked
    LaunchActivityItem::init -- 触发应用端处理
    ResumeActivityItem::init
    Task::minimalResumeActivityLocked  -- 设置状态为 RESUMED
        ActivityRecord::setState
        ActivityRecord::completeResumeLocked
            ActivityTaskSupervisor::reportResumedActivityLocked
                RootWindowContainer::executeAppTransitionForAllDisplay
                    DisplayContent::executeAppTransition -- executeAppTransition 逻辑
                        TransitionController::setReady
                            TransitionController::setReady
                                Transition::setReady
                                    ReadyTracker::setReadyFrom
                                    Transition::applyReady
                                        BLASTSyncEngine::setReady

RootWindowContainer::performSurfacePlacementNoTrace
    RootWindowContainer::applySurfaceChangesTransaction
        DisplayContent::applySurfaceChangesTransaction
            DisplayContent::mApplySurfaceChangesTransaction  
                WindowStateAnimator::commitFinishDrawingLocked
                    WindowState::performShowLocked
                        ActivityRecord::onStartingWindowDrawn
                            DisplayContent::executeAppTransition -- executeAppTransition 逻辑
                                TransitionController::setReady
                                    TransitionController::setReady
                                        Transition::setReady
                                            ReadyTracker::setReadyFrom
                                            Transition::applyReady
                                                BLASTSyncEngine::setReady

公共流程是 DisplayContent::executeAppTransition 之后,以这个方法看一下代码执行逻辑。

# DisplayContent
    void executeAppTransition() {
        // * 本次重点
        mTransitionController.setReady(this);
        // 进入条件
        if (mAppTransition.isTransitionSet()) {
            // log
            ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
                    "Execute app transition: %s, displayId: %d Callers=%s",
                    mAppTransition, mDisplayId, Debug.getCallers(5));
            // 以前 AppTransition 的逻辑
            mAppTransition.setReady();
            mWmService.mWindowPlacerLocked.requestTraversal();
        }
    }

这个方法很重要,常说的 executeAppTransition 流程其实指的也就是这个方法。 以前分析的重点是 AppTransition::isTransitionSet 满足后的条件,现在 Android 14 已经不走下面的逻辑了, 目前只看 TransitionController::setReady 的调用,传递的参数是 this ,也就是当前屏幕(DisplayContent)

# TransitionController
    void setReady(WindowContainer wc) {
        // 第二个参数就是给 ReadyTracker 的
        // 这里直接传递true
        setReady(wc, true);
    }
    void setReady(WindowContainer wc, boolean ready) {
        if (mCollectingTransition == null) return;
        mCollectingTransition.setReady(wc, ready);
    }

mCollectingTransition 就是当前收集的主事务

# Transition
    void setReady(WindowContainer wc, boolean ready) {
        if (!isCollecting() || mSyncId < 0) return;
        // 设置屏幕准备完成
        mReadyTracker.setReadyFrom(wc, ready);
        // 设置同步组
        applyReady();
    }

ReadyTracker::setReadyFrom 设置 true 后又执行了 ReadyTracker::applyReady ,这个方法之前已经分析过来,只不过执行内部因为 ReadyTracker::allReady 返回 false ,导致同步组也设置 false ,不过现在就可以把同步组设置为 true 了。

这里设置完 true 后,即表示 ReadyTracker 的跟踪,这个屏幕已经准备就绪,当然根据注释,不代表目标收集的窗口都绘制完了。 后续 ReadyTracker::allReady 也会返回 true 。

现在同步引擎就处于就绪状态了,后续逻辑就是在每一次layout都会检查同步组容器是否都绘制完