【Android 14源码分析】ShellTransitions-1-同步组初始化

511 阅读9分钟

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

                        -- 服装学院的IT男

BLASTSyncEngine设计剖析

ShellTransitions总体流程介绍

ShellTransitions-1-同步组初始化

ShellTransitions-2-requestStartTransition处理

ShellTransitions-3-动画前准备

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

分析代码前,先回顾一下 Activity 启动流程走到 system_server 进程后的调用链:

ActivityTaskManagerService::startActivity
    ActivityTaskManagerService::startActivityAsUser
        ActivityTaskManagerService::startActivityAsUser
            ActivityStartController::obtainStarter
                ActivityStarter::execute
                    ActivityStarter::executeRequest -- 构建 ActivityRecord -- 创建ActivityRecord
                        ActivityStarter::startActivityUnchecked
                            ActivityStarter::startActivityInner        -- 关键函数startActivityInner
                                ActivityStarter::getOrCreateRootTask   -- 创建或者拿到Task
                                ActivityStarter::setNewTask            -- 将task与activityRecord 绑定
                                RootWindowContainer::resumeFocusedTasksTopActivities    -- 处理需要显示的Activity

本次的分析从 ActivityStarter::startActivityUnchecked 方法开始。

整体流程如下:

流程图-ShellTrnsition-第一步.png

# ActivityStarter

    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, ActivityOptions options, Task inTask,
            TaskFragment inTaskFragment, @BalCode int balCode,
            NeededUriGrants intentGrants, int realCallingUid) {
        int result = START_CANCELED;
        ......
        // 获取 Transition 的管理类
        final TransitionController transitionController = r.mTransitionController;
        // 1. 创建一个 Transition
        Transition newTransition = transitionController.isShellTransitionsEnabled()
                ? transitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
        // 获取一个远程过渡动画的参数
        RemoteTransition remoteTransition = r.takeRemoteTransition();
        try {
            // 延迟layout
            mService.deferWindowLayout();
            // 2.1 收集 ActivityRecord
            transitionController.collect(r);
            try {
                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
                // 2.2 启动主流程 (收集 Task)
                result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                        startFlags, options, inTask, inTaskFragment, balCode,
                        intentGrants, realCallingUid);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                // 3. 启动后处理
                startedActivityRootTask = handleStartResult(r, options, result, newTransition,
                        remoteTransition);
            }
        } finally {
            // 恢复layout
            mService.continueWindowLayout();
        }
        postStartActivityProcessing(r, result, startedActivityRootTask);

        return result;
    }

这里获取的 transitionController 其实是 WMS 的一个成员变量,全局也只有1个对象,并不存在多个,用于控制 Transition 逻辑。

这段代码有以下3个点需要注意:

    1. 为当前操作创建一个 Transition ,这里的逻辑也会触发 SyncGroup 的创建,也就是 BLASTSyncEngine 的第一步
    1. 收集容器
    • 2.1 收集 ActivityRecord 到同步组
    • 2.2 收集 Task 到同步组
    1. 触发 requestStartTransition 流程

1. 创建过渡事务和同步组

TransitionController::createAndStartCollecting 会创建一个过渡事务并且收集,看参数传递是的 “TRANSIT_OPEN”

# TransitionController

    BLASTSyncEngine mSyncEngine;

    Transition createAndStartCollecting(int type) {
        ......
        // 同步引擎中已经在处理同步事务
        if (mSyncEngine.hasActiveSync()) {
            // 如果正在收集的处理
            ......
            return null;
        }

        // 新建个事务
        Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
        // 开始收集
        moveToCollecting(transit);
        return transit;
    }

这里有2种情况:

    1. 同步引擎中已经有任务在处理,这个场景当前暂时不分析
    1. 同步引擎中没有任务在处理,则创建出一个 Transition ,然后执行 moveToCollecting

1.1 创建Transition

# Transition

    // 过渡事务类型
    final @TransitionType int mType;

    // 唯一标识
    private final Token mToken;

    Transition(@TransitionType int type, @TransitionFlags int flags,
            TransitionController controller, BLASTSyncEngine syncEngine) {
        mType = type;
        ......
        // 内部类,binder 类型
        mToken = new Token(this);
        ......
    }

mType : 和之前的 AppTransition 一样,表示当前过渡的类型,几个场景的类型在 【应用启动动画-app_transition-2】有介绍 Token : 内部类,是个binder, 也就是当前 Transition 的唯一标识。看来是要跨进程使用

1.2 创建同步组

这个方法的目的是要把参数的 Transition 作为这次过渡事务收集的主事务,并且创建一个同步组。

如果执行这个方法的时候,已经有主事务了,则会报错 。

# TransitionController

    // 当前系统中正在收集过渡的Transition
    private Transition mCollectingTransition = null;

    private ITransitionPlayer mTransitionPlayer;
    // 默认超时时间,0.5秒, 和同步事务一样
    private static final int DEFAULT_TIMEOUT_MS = 5000;

    void moveToCollecting(@NonNull Transition transition) {
        // 如果已经有正在收集的主事务了,则报错
        if (mCollectingTransition != null) {
            throw new IllegalStateException("Simultaneous transition collection not supported.");
        }
        // 异常处理,SystemUI 被杀了才会为null
        if (mTransitionPlayer == null) {
            // If sysui has been killed (by a test) or crashed, we can temporarily have no player
            // In this case, abort the transition.
            transition.abort();
            return;
        }
        // * 赋值给主事务
        mCollectingTransition = transition;
        // Distinguish change type because the response time is usually expected to be not too long.
        final long timeoutMs =
                transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
        // * 开始收集
        mCollectingTransition.startCollecting(timeoutMs);
        // 打印日志
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                mCollectingTransition);
        dispatchLegacyAppTransitionPending();
    }

打印日志: WindowManager: Start collecting in Transition: TransitionRecord{6e037e id=40 type=OPEN flags=0x0}

这里的主流程是 Transition::startCollecting 方法,开始收集过渡事务。

# Transition

// 实现接口
Transition implements BLASTSyncEngine.TransactionReadyListener{

    // 当前事务的状态
    private @TransitionState int mState = STATE_PENDING;
    
    void startCollecting(long timeoutMs) {

        // 如果不是默认状态,则说明之前已经开始过收集,再执行这个方法就是bug了
        if (mState != STATE_PENDING) {
            throw new IllegalStateException("Attempting to re-use a transition");
        }
        // 设置状态,表示开始收集
        mState = STATE_COLLECTING;
        // 开始一个同步组
        // 注意第一个参数。当前实现了 TransactionReadyListener 所以会执行  onTransactionReady
        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
                mParallelCollectType != PARALLEL_TYPE_NONE);
        mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
        ......
    }
}
    1. 设置过渡事务的状态为 STATE_COLLECTING
    1. 通过同步引擎开始一个同步组,内部逻辑不需要再解释了,这边留意穿进去的 TransactionReadyListener 是 “this” ,也就是说等同步完成后,会执行 Transition 下的 onTransactionReady 回调方法。

1.3 小结

    1. 创建了事务,并且设置为主事务
    1. 创建了同步组,传递的回调为 Transition

2. 收集容器

这里会收集2个容器,新启动应用的 ActivityRecord 和 Task 。

2.1 收集 ActivityRecord

这一步的触发在 ActivityStarter::startActivityUnchecked 方法中 “transitionController.collect(r);” 的调用,参数 r 为启动 Activity 对应的 ActivityRecord 。

# TransitionController

    private Transition mCollectingTransition = null;


    void collect(@NonNull WindowContainer wc) {
        if (mCollectingTransition == null) return;
        mCollectingTransition.collect(wc);
    }

调用的是这次的主事务 Transition 对应的收集。

# Transition

    private final BLASTSyncEngine mSyncEngine;

    // 已经收集过的容器
    final ArraySet<WindowContainer> mParticipants = new ArraySet<>();

    // 保存这次事务中,参与者(容器)的哪些属性发生了改变
    final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();

    void collect(@NonNull WindowContainer wc) {
        // 没有执行 Transition::startCollecting 则报错
        if (mState < STATE_COLLECTING) {
            throw new IllegalStateException("Transition hasn't started collecting.");
        }
        // 不处于收集状态则不处理 状态为 STATE_COLLECTING 或者 STATE_STARTED 都满足收集条件
        if (!isCollecting()) {
            // Too late, transition already started playing, so don't collect.
            return;
        }
        // 日志,收集了某个容器
        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);
            }
        }
        ......
        // 非壁纸窗口这里 needSync 一般都为 true
        // 判断条件如下:
        // 不是壁纸或者容器所在的这个屏幕被收集
        // 并且容器没有被暂时隐藏
        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
                // Transient-hide may be hidden later, so no need to request redraw.
                && !isInTransientHide(wc);
            
        if (needSync) {
            // * 1. 加入到同步组
            mSyncEngine.addToSyncSet(mSyncId, wc);
        }
        // * 2. 获取容器对应的 ChangeInfo ,记录这个容器哪些属性发生了改变
        ChangeInfo info = mChanges.get(wc);
        if (info == null) {
            // 如果之前没有保存则新建
            info = new ChangeInfo(wc);
            updateTransientFlags(info);
            // 保存到集合
            mChanges.put(wc, info);
        }
        // * 3. 保存到集合
        mParticipants.add(wc);
        ......
    }
    1. 通过 BLASTSyncEngine 添加容器到同步组
    1. 创建对应的 ChangeInfo 并保存
    1. 添加进 mParticipants 集合

打印日志: WindowManager: Collecting in transition 40: ActivityRecord{55c10df u0 com.android.dialer/.main.impl.MainActivity

这里还有个点,在 “mReadyTracker.addGroup(curr); ”这部分。 目前分析进来的场景是收集 ActivityRecord 不过由于 ActivityRecord 还没挂载到层级树,所以这部分代码现在相当于无效逻辑,当前有个印象即可,后面会详细解释。

2.2 收集 Task

在 ActivityStarter::startActivityUnchecked 方法中 “result = startActivityInner(r,..);” 的调用,这里方法内部会创建 Task ,同时也会将其收集到同步组。

# ActivityStarter

    int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord...) {

            ......
            if (mTargetRootTask == null) {
                // 创建Task
                mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
                        mOptions);
            }
            if (newTask) {
                // taskToAffiliate 为null
                final Task taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                        ? mSourceRecord.getTask() : null;
                // * 将需要启动的ActivityRecord与 新创建的Task 进行绑定
                setNewTask(taskToAffiliate);
            } ......
            ......
        }

setNewTask 方法会将新建的 Task 和 ActivityRecord 绑定,当前要分析的流程也在这。

# ActivityStarer

    private void setNewTask(Task taskToAffiliate) {
        // 为true
        final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
        // 就是mTargetRootTask,也就是刚刚创建的Task
        final Task task = mTargetRootTask.reuseOrCreateTask(
                mStartActivity.info, mIntent, mVoiceSession,
                mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
        // * 收集 Task 容器过渡事务
        task.mTransitionController.collectExistenceChange(task);
        // ActivityRecord的挂载
        addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");
        ......
    }

后续的逻辑来到 TransitionController::collectExistenceChange 方法。 这个方法表示收集的是“存在性”变化,也就是“从无到有,或者从有到无”。当前这个 Task 是冷启动新建的,所以是“从无到有”。

# TransitionController

    void collectExistenceChange(@NonNull WindowContainer wc) {
        if (mCollectingTransition == null) return;
        mCollectingTransition.collectExistenceChange(wc);
    }

这里的 mCollectingTransition 就是当前的主事务。

# Transition

    final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();

    void collectExistenceChange(@NonNull WindowContainer wc) {
        // 如果已经开始播放动画了,则不能再收集了
        if (mState >= STATE_PLAYING) {
            // Too late to collect. Don't check too-early here since `collect` will check that.
            return;
        }
        // 日志
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
                + " %s", mSyncId, wc);
        // 1. 收集这个容器,当前是 Task
        collect(wc);
        // 2. 存在性改变
        mChanges.get(wc).mExistenceChanged = true;
    }

Transition::collect 的调用和前面收集 ActivityRecord 到同步组是一样的,只不过这里的参数是 Task 。 另外还把这个容器对应的 ChangeInfo 的 mExistenceChanged 变量设置为 true 标记了事务的改变类型。

打印日志: WindowManager: Existence Changed in transition 11: Task{9910f99 #16 type=standard A=10162:com.android.dialer}

3. 触发 requestStartTransition 流程

这个逻辑的调用在 ActivityStarter::startActivityUnchecked 方法调用 ActivityStarter::handleStartResult 。

# ActivityStarter
    private @Nullable Task handleStartResult(@NonNull ActivityRecord started,
            ActivityOptions options, int result, Transition newTransition,
            RemoteTransition remoteTransition) {
                ......
                if (isStarted) {
                    // The activity is started new rather than just brought forward, so record it as an
                    // existence change.
                    // 记录 ActivityRecord 从无到有
                    transitionController.collectExistenceChange(started);
                } ......
                ......
                if (newTransition != null) {
                    // * 请求 SystemUI 开始事务 
                    transitionController.requestStartTransition(newTransition,
                            mTargetTask == null ? started.getTask() : mTargetTask,
                            remoteTransition, null /* displayChange */);
                } 
                ......
            }
    1. 参数 newTransition 是在 ActivityStarter::startActivityUnchecked 方法新建的过渡事务
    1. 这里会和 Task 一样记录 ActivityRecord 的“存在性”变化。区别是由于之前 ActivityRecord 已经添加进同步组了,所以这次不会再次添加
    1. 后续逻辑会触发第一次 WMCore-> WMShell 调用

对应新启动应用 Task 和 ActivityRecord 收集和存在性记录的顺序为:

  • 收集的顺序:ActivityRecord -> Task
  • 存在性记录顺序:Task -> ActivityRecord

收集容器到同步组顺序日志:

WindowManager: SyncGroup 6: Adding to group: ActivityRecord{8a1f45f u0 com.android.dialer/.main.impl.MainActivity t-1}
WindowManager: SyncGroup 6: Adding to group: Task{5e42eac #13 type=standard A=10162:com.android.dialer}
WindowManager: SyncGroup 6: Adding to group: ActivityRecord{14a87b7 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t12}

存在性变化顺序日志:

WindowManager: Existence Changed in transition 6: Task{5e42eac #13 type=standard A=10162:com.android.dialer}
WindowManager: Existence Changed in transition 6: ActivityRecord{8a1f45f u0 com.android.dialer/.main.impl.MainActivity t13}

继续看主线。

# TransitionController

    // 具体执行在 SystemUI 端
    private ITransitionPlayer mTransitionPlayer;

    Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
            @Nullable RemoteTransition remoteTransition,
            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
                ......
                try {
                    // 日志
                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                            "Requesting StartTransition: %s", transition);

                    // RunningTaskInfo 对象会报错一个正在运行的 Task 的相关信息
                    ActivityManager.RunningTaskInfo info = null;
                    if (startTask != null) {
                        
                        info = new ActivityManager.RunningTaskInfo();
                        // 填充 info
                        startTask.fillTaskInfo(info);
                    }
                    // 创建一个请求的对象
                    final TransitionRequestInfo request = new TransitionRequestInfo(
                            transition.mType, info, remoteTransition, displayChange);
                    ......
                    // * WMCore  ---》WMShell
                    mTransitionPlayer.requestStartTransition(transition.getToken(), request);

                    if (remoteTransition != null) {
                        transition.setRemoteAnimationApp(remoteTransition.getAppThread());
                    }
                } catch (RemoteException e) {
                    ......
                }
                return transition;
            }

构建了一些相关对象,然厚触发了第一次 WMCore-> WMShell 调用。 request 对象包含了很多数据,不过稍后会在 SystemUI 端打印出来,所以可以不用可以去记。

打印日志: WindowManager: Requesting StartTransition: TransitionRecord{6e037e id=40 type=OPEN flags=0x0}

这个 mTransitionPlayer 是个binder 对象,服务端在 SystemUI 。构建与调用链如下:

Transitions::init
    TransitionPlayerImpl::init
    WindowOrganizer::requestStartTransition   -- 跨进程通信,像 WMCore 注册
        WindowOrganizerController::requestStartTransition