Android V app 冷启动(6) Transition 数据化

Transition 动画,它并不是在 WMCore 侧执行的。因此,WMCore 要把 Transition 动画数据化,发送给 WMShell 去执行。

本文承接 Android V app 冷启动(5) 窗口动画就绪,来分析这个数据化的过程。

java 复制代码
// Transition.java

// transaction 是 SyncGroup 收集的 sync transaction
public void  onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
    // 1. 
    commitVisibleActivities(transaction);
    
    // ...

    // Transition 状态切换到 STATE_PLAYING
    mState = STATE_PLAYING;
    
    // SyncGroup 收集的 sync transaction,它现在叫做 transition 的 start transaction
    mStartTransaction = transaction;
    
    // 创建一个 finish transaction
    mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();

    // ...
    
    // 2. 
    mTargets = calculateTargets(mParticipants, mChanges);

    // ...
    
    // 3. 
    final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
    info.setDebugId(mSyncId);
    // 给 Transition 设置一个 track id
    // 这个 track 是为多动画并行服务的,目前不存在这种多动画情况,track id 为 0
    mController.assignTrack(this, info);

    // 把 Transition 保存到 TransitionController#mPlayingTransitions 列表中
    // 并重置 TransitionController#mCollectingTransition 为 null
    // 表示此时只有 playing transition,没有 collecting transition
    mController.moveToPlaying(this);

    // ...
    
    // activity 的 overridePendingTransition() 自定义动画,保存到 TransitionInfo
    overrideAnimationOptionsToInfoIfNecessary(info);

    // ...

    // 收集在动画结束后还可见的 WindowToken(即 ActivityRecord/WallpaperWindowToken)
    // Record windowtokens (activity/wallpaper) that are expected to be visible after the
    // transition animation. This will be used in finishTransition to prevent prematurely
    // committing visibility. Skip transient launches since those are only temporarily visible.
    if (mTransientLaunches == null) {
        for (int i = mParticipants.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mParticipants.valueAt(i);
            if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
            mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
        }
    }

    // ...
    
    // 4.
    buildFinishTransaction(mFinishTransaction, info);
    
    // 5.
    mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
    buildCleanupTransaction(mCleanupTransaction, info);
    
    if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
        // ...
        try {
            // ...
            
            // 6. 通知 WMShell,即执行动画
            mController.getTransitionPlayer().onTransitionReady(
                    mToken, info, transaction, mFinishTransaction);
            
            // ...
        } 
        // ...
    } 

    // ...
}

简单介绍下这几步在做什么

  1. commit visible activities : 到目前为止,用于 app 绘制的 surface 还没有 show 出来。现在要做动画了,必须得 show 出来。
  2. calculate animating targets : Transition 收集的 WC ,并不能作为最终动画的目标。例如,有些 WC 其实并没有改变,不需要做动画。再例如,有些 WC 和其 parent 以同样的方式做动画,那么可以用 parent 代替 WC 做动画。
  3. calculate transition info : WMCore 需要把 Transition 数据化,这样才能发送给 WMShell 执行动画。
  4. build finish transaction : 所有做动画的 surface,都是在一个名为 transition root 的 surface 上执行的,并且还会伴随 layer 更新。因此,在动画执行完成后,需要把 surface 做复位操作,例如,reparent,重新更新 layer,等等。但是,要注意,这个 finish transaction 是在 WMShell 中执行的。
  5. build cleanup transaction : cleanup transaction 与 finish transaction 有一些重复的功能,例如,移除截图层,transition root。但是,它却是在 WMCore 侧执行的,按照 AOSP 的说法,just in case。
  6. 发送数据给 WMShell 执行动画。这些数据包括,transition token、transition info、start transaction( 即 SyncGroup 收集的 sync transaction )、finish transaction(需要在 WMShell 侧执行)。

1. commit visible activities

java 复制代码
// Transition.java

/** The transition is ready to play. Make the start transaction show the surfaces. */
private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
    for (int i = mParticipants.size() - 1; i >= 0; --i) {
        final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
        if (ar == null || ar.getTask() == null) {
            continue;
        }
        
        // 只针对可见的 activity
        if (ar.isVisibleRequested()) {
            // 1. commit visibility
            ar.commitVisibility(true /* visible */, false /* performLayout */,
                    true /* fromTransition */);
            // 2. commit finish drawing
            ar.commitFinishDrawing(transaction);
        }
        ar.getTask().setDeferTaskAppear(false);
    }
}

目前,只有待启动的 activity 才是可见的,而此时它的启动窗口的绘制状态是 READY_TO_SHOW,并且用于 app 绘制的 surface ,还是处于隐藏状态,即不可见。

1.1 commit visiblity

java 复制代码
// ActivityRecord.java

// visible 为 true
void commitVisibility(boolean visible, boolean performLayout, boolean fromTransition) {
    // ...
    
    if (visible == isVisible()) {
        return;
    }

    // ...
    
    // 更新 mVisible
    setVisible(visible);
    
    // 第二阶段启动,已经更新过 mVisibleRequested
    setVisibleRequested(visible);
    
    ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "commitVisibility: %s: visible=%b"
                    + " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
            this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
            Debug.getCallers(5));
    
    // ...


    postApplyAnimation(visible, fromTransition);
}

// visible 为 true
private void postApplyAnimation(boolean visible, boolean fromTransition) {
    // ...
    
    if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
            PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
        // 更新 mClientVisible
        // ActivityRecord 在创建之时,mClientVisible 就已经为 true
        setClientVisible(visible);
    }

    // ...
}

第二阶段启动,更新了所有 Activity 的可见性,其实更新的只是 ActivityRecord#mVisibleRequested 。而此时的提交可见性,还会更新 ActivityRecord#mVisibleActivityRecord#mClientVisible

根据这三个可见性在代码中的用途,我对这个三个可见性,做了如下总结

  1. ActivityRecord#mVisibleRequested 代表 app 端 activity 是否请求可见。例如,app 要启动一个 activity。
  2. ActivityRecord#mVisible 用于控制 ActivityRecord surface,以及其下用于 app 绘制 surface 的可见性。
  3. ActivityRecord#mClientVisible 用于服务端控制 app 端窗口的可见性。例如,如果服务端通知 app 端窗口不可见,那么 app 端就不需要绘制窗口了。

1.2 commit finish drawing

java 复制代码
// ActivityRecord.java

/** Updates draw state and shows drawn windows. */
void commitFinishDrawing(SurfaceControl.Transaction t) {
    boolean committed = false;
    
    // 对 ActivityRecord 其下的窗口 commit finish drawing
    for (int i = mChildren.size() - 1; i >= 0; i--) {
        committed |= mChildren.get(i).commitFinishDrawing(t);
    }
    
    if (committed) {
        
    }
}
java 复制代码
// WindowState.java

/** Makes the surface of drawn window (COMMIT_DRAW_PENDING) to be visible. */
boolean commitFinishDrawing(SurfaceControl.Transaction t) {
    // 1. 
    boolean committed = mWinAnimator.commitFinishDrawingLocked();
    
    if (committed) {
        // 2. 
        // Ensure that the visibility of buffer layer is set.
        mWinAnimator.prepareSurfaceLocked(t);
    }
    
    for (int i = mChildren.size() - 1; i >= 0; i--) {
        
    }
    return committed;
}

这两步是不是看起来有点面熟,恰好 Android V app 冷启动(5) 窗口动画就绪 分析过。commit finish drawing 会更新绘制状态,prepare surface 是为了 show app 绘制的 surface。

待启动的 Activity 的 ActivityRecord#mVisible 此时为 true,表示其下的窗口 surface 需要可见。那么,首先要把启动窗口的绘制状态,切换到 HAS_DRAWN。如下

java 复制代码
// WindowStateAnimator.java

boolean commitFinishDrawingLocked() {
    // 此时的绘制状态是 READY_TO_SHOW
    if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
        return false;
    }
    ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
            mSurfaceController);
    mDrawState = READY_TO_SHOW;
    boolean result = false;
    final ActivityRecord activity = mWin.mActivityRecord;
    if (activity == null || activity.canShowWindows()
            || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
        result = mWin.performShowLocked();
    }
    return result;
}
java 复制代码
// WindowState.java

boolean performShowLocked() {
    // ...

    // 绘制状态此时是 READY_TO_SHOW
    // ActivityRecord#mVisible 已经被更新为 true,因此 isReadyForDisplay() 返回 true
    if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
        return false;
    }

    // ...

    // 这里强制重置 mWinAnimator.mLastAlpha 为 -1,是为了能在 prepare surface 中 show 窗口 surface
    // 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();

    // mHidden 这个变量有歧义,AOSP 对它的注释是 Used to determine if to show child windows
    // 可见,它并不是代表窗口是否处于隐藏状态,而是表示是否可以显示子窗口
    // 从下面的逻辑,也证实了这个变量的意思
    if (mHidden) {
        mHidden = false;
        final DisplayContent displayContent = getDisplayContent();
        // 此时不存在子窗口
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            
        }
    }

    return true;
}

绘制状态切换到 HAS_DRAWN 后,接下来的 prepare surface 就可以 show 窗口 surface。如下

java 复制代码
// WindowStateAnimator.java

void prepareSurfaceLocked(SurfaceControl.Transaction t) {
    final WindowState w = mWin;
    if (!hasSurface()) {
        // ...
        return;
    }

    // 就是 mShownAlpha = mAlpha
    computeShownFrameLocked();

    if (!w.isOnScreen()) {
        // ...
    } 
    // 如下两个条件都满足
    // commit finish drawing,强制重置 mLastAlpha 为 -1
    // 窗口 surface 创建的时候处于 hidden 状态,mLastHidden 被设置为 true
    else if (mLastAlpha != mShownAlpha
            || mLastHidden) {
        // 更新 mLastAlpha
        mLastAlpha = mShownAlpha;
        
        ProtoLog.i(WM_SHOW_TRANSACTIONS,
                "SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
                mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);

        // 给 app 绘制的 surface ,设置 alpha
        boolean prepared =
            mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);

        if (prepared && mDrawState == HAS_DRAWN) {
            // 在 relayout 中,surface 创建时,mLastHidden 被设置为 true
            if (mLastHidden) {
                // 由 WindowSurfaceController 来 show app 绘制的 surface
                mSurfaceController.showRobustly(t);
                
                // 用于 app 绘制的 surface 已经可见
                mLastHidden = false;
                
                // ...
            }
        }
    }

    // ...
}
java 复制代码
// WindowSurfaceController.java

void showRobustly(SurfaceControl.Transaction t) {
    ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
    if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
            + " during relayout");

    if (mSurfaceShown) {
        return;
    }

    // 这里会更新 mSurfaceShown 为 true
    setShown(true);
    
    // show surface
    // 注意,t 是 start transaction
    t.show(mSurfaceControl);
    
    // ...
}

2. calculate animating targets

java 复制代码
// Transition.java

/**
 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
 * animation targets to higher level in the window hierarchy if possible.
 */
// participants 就是 Transition#mParticipants,即 Transition 参与者
// changes 就是 Transition#mChanges,保存了参与者,及其当前状态
static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
        ArrayMap<WindowContainer, ChangeInfo> changes) {
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
            "Start calculating TransitionInfo based on participants: %s", participants);
    
    // Targets 用来保存所有的动画目标
    final Targets targets = new Targets();
    
    // 1.Targets 保存有效的参与者
    // Add all valid participants to the target container.
    for (int i = participants.size() - 1; i >= 0; --i) {
        final WindowContainer<?> wc = participants.valueAt(i);
        
        if (!wc.isAttached()) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "  Rejecting as detached: %s", wc);
            continue;
        }
        
        // The level of transition target should be at least window token.
        if (wc.asWindowState() != null) continue;

        final ChangeInfo changeInfo = changes.get(wc);
        // Reject no-ops, unless wallpaper
        if (!changeInfo.hasChanged()
                && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "  Rejecting as no-op: %s", wc);
            continue;
        }
        
        // 注意,Targets 保存的是参与者的 ChangeInfo
        targets.add(changeInfo);
    }
    
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
            targets.mArray);
     
    // 2. 尝试提升动画目标的层级 
    // Combine the targets from bottom to top if possible.
    tryPromote(targets, changes);
    
    // 本案例不涉及这一步
    // Establish the relationship between the targets and their top changes.
    populateParentChanges(targets, changes);

    final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
    return targetList;
}

目前,Transition 有四个参与者,待启动的 ActivityRecord 及其 Task、Launcher 的 ActivityRecord、以及与 Launcher 相关的 WallpaperWindowToken。

待启动的 ActivityRecord 及其 Task,Launcher 的 ActivityRecord,它们的可见性都改变了。但是,WallpaperWindowToken 却什么也没改变,我在 Android V app 冷启动(5) 窗口动画就绪#检测动画是否可以执行 做过简单说明吗,读者可以参考下。

排除 WallpaperWindowToken,对剩下的三个 WC ,尝试对其进行动画层级提升,如下

java 复制代码
// Transition.java

/**
 * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
 *
 * @param targets all targets that will be sent to the player.
 */
private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
    WindowContainer<?> lastNonPromotableParent = null;
    // Go through from the deepest target.
    for (int i = targets.mArray.size() - 1; i >= 0; --i) {
        final ChangeInfo targetChange = targets.mArray.valueAt(i);
        final WindowContainer<?> target = targetChange.mContainer;
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
        final WindowContainer<?> parent = target.getParent();
        if (parent == lastNonPromotableParent) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "      SKIP: its sibling was rejected");
            continue;
        }
        
        // 1. 检测是否能提升到 parent
        if (!canPromote(targetChange, targets, changes)) {
            // 不能提升,就标记这个 parent
            lastNonPromotableParent = parent;
            continue;
        }
        
        // 走到这里,表示能把动画目标的层级提升至 parent
        
        // 2.既然能提升到 parent,决定是否保留当前动画目标
        // 标准就是 WC 是否可以被 WMShell 管理
        // 一般来说,只有 root task 和 DisplayArea 可以被 WMShell 管理
        if (reportIfNotTop(target)) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        keep as target %s", target);
        } else {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        remove from targets %s", target);
            targets.remove(i);
        }
        
        // 3. 既然能提升到 parent,那么 parent 也是动画目标,因此把 parent 加入到 targets 中
        // parent 加入到 targets 中,会继续参与下一轮遍历,它也尝试提升动画层级
        final ChangeInfo parentChange = changes.get(parent);
        if (targets.mArray.indexOfValue(parentChange) < 0) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "      CAN PROMOTE: promoting to parent %s", parent);
            // The parent has lower depth, so it will be checked in the later iteration.
            i++;
            targets.add(parentChange);
        }
        
        // child 的 flag 转移到 parent
        if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
            parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
        } else {
            parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
        }
        if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
            parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
        }
    }
}
java 复制代码
// Transiont.java

/**
 * Under some conditions (eg. all visible targets within a parent container are transitioning
 * the same way) the transition can be "promoted" to the parent container. This means an
 * animation can play just on the parent rather than all the individual children.
 *
 * @return {@code true} if transition in target can be promoted to its parent.
 */
private static boolean canPromote(ChangeInfo targetChange, Targets targets,
        ArrayMap<WindowContainer, ChangeInfo> changes) {
    final WindowContainer<?> target = targetChange.mContainer;
    final WindowContainer<?> parent = target.getParent();
    final ChangeInfo parentChange = changes.get(parent);
    
    // parent 不能创建远程动画目标,或者没有改变,不能提升
    // 目前,只有 TaskFragment, TaskDisplayArea, ActivityRecord 可以创建远程动画目标
    if (!parent.canCreateRemoteAnimationTarget()
            || parentChange == null || !parentChange.hasChanged()) {
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
                "parent can't be target " + parent);
        return false;
    }
    
    // ...
    
    final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
    for (int i = parent.getChildCount() - 1; i >= 0; --i) {
        final WindowContainer<?> sibling = parent.getChildAt(i);
        if (target == sibling) continue;
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
                sibling);
        final ChangeInfo siblingChange = changes.get(sibling);
        
        // 兄弟节点没有改变,或者不在动画目标集合中,不能提升
        if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
            if (sibling.isVisibleRequested()) {
                // Sibling is visible but not animating, so no promote.
                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                        "        SKIP: sibling is visible but not part of transition");
                return false;
            }
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        unrelated invisible sibling %s", sibling);
            continue;
        }

        // 兄弟节点,做动画的方式不一样,也不能提升
        final int siblingMode = siblingChange.getTransitMode(sibling);
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                "        sibling is a participant with mode %s",
                TransitionInfo.modeToString(siblingMode));
        if (reduceMode(mode) != reduceMode(siblingMode)) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "          SKIP: common mode mismatch. was %s",
                    TransitionInfo.modeToString(mode));
            return false;
        }
    }
    return true;
}

待启动的 ActivityRecord,它的动画层级能提升至 parent,原因如下

  1. parent(Task)可以创建远程动画目标。说简单点,就是可以被用来做动画。
  2. parent 有可见性改变。
  3. ActivityRecord 没有兄弟节点。

待启动的 ActivityRecord 的 Task,它的动画层级,不能提升至 parent,即 TaskDisplayArea。因为,parent 没有改变。

Launcher 的 ActivityRecord ,它的动画层级可以提升至 parent,原因如下

  1. parent(Task)可以创建远程动画目标。
  2. parent 有可见性改变。在更新 ActivityRecord#mVisibleReuqsted 为 false 时,也会递归更新 task 的。因此 Launcher 的 task 以及 root task,他们的可见性都是有改变的。
  3. Launcher 的 ActivityRecord,一般是有兄弟节点,但是他们没有改变,也没有参与到 Transition 中。

Launcher ActivityRecord 的 Task,也可以提升至 parent,即 root task,原因如下

  1. root task,它也是 task,可以创建远程动画目标。
  2. root task 有可见性改变。
  3. Launcher task 没有兄弟节点。

Launcher root task ,不能提升动画目标层级的,因为它的 parent,即 TaskDisplayArea 没有改变。

OK,现在的动画目标,只有两个,待启动的 Activity 的 Task,以及 Launcher root Task。

3. calculate transition info

java 复制代码
// Transition.java

/**
 * Construct a TransitionInfo object from a set of targets and changes. Also populates the
 * root surface.
 * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
 * @param startT The start transaction - used to set-up new leashes.
 */
@VisibleForTesting
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
        ArrayList<ChangeInfo> sortedTargets,
        @NonNull SurfaceControl.Transaction startT) {
    // 动画信息,都填充到 TransitionInfo
    final TransitionInfo out = new TransitionInfo(type, flags);
    
    // 创建 transition root
    // transition root 是动画的 root surface
    calculateTransitionRoots(out, sortedTargets, startT);

    // ...

    final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
    final int count = sortedTargets.size();
    for (int i = 0; i < count; ++i) {
        final ChangeInfo info = sortedTargets.get(i);
        final WindowContainer target = info.mContainer;
        
        // TransitionInfo.Change 用来保存参与动画的 WC 的数据
        // 构造函数,保存了 leash surface
        final TransitionInfo.Change change = new TransitionInfo.Change(
                target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
                        : null, getLeashSurface(target, startT));
        
        // ...
        
        // 保存 transition mode
        change.setMode(info.getTransitMode(target));
        info.mReadyMode = change.getMode();
        
        // 保存 start bounds
        change.setStartAbsBounds(info.mAbsoluteBounds);
        
        // 保存 flags
        change.setFlags(info.getChangeFlags(target));
        info.mReadyFlags = change.getFlags();
        
        // 保存 display id
        change.setDisplayId(info.mDisplayId, getDisplayId(target));

        // ...

        final Task task = target.asTask();
        final TaskFragment taskFragment = target.asTaskFragment();
        final boolean isEmbeddedTaskFragment = taskFragment != null
                && taskFragment.isEmbedded();
        final ActivityRecord activityRecord = target.asActivityRecord();

        if (task != null) {
            // 获取 task info
            final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
            task.fillTaskInfo(tinfo);
            
            // 保存 task info
            change.setTaskInfo(tinfo);
            
            change.setRotationAnimation(getTaskRotationAnimation(task));
            final ActivityRecord topRunningActivity = task.topRunningActivity();
            if (topRunningActivity != null) {
                if (topRunningActivity.info.supportsPictureInPicture()) {
                    
                }
                
                // 保存 fixed rotation
                setEndFixedRotationIfNeeded(change, task, topRunningActivity);
            }
        } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
            
        }

        final WindowContainer<?> parent = target.getParent();
        final Rect bounds = target.getBounds();
        final Rect parentBounds = parent.getBounds();
        // 保存相对 parent offset
        change.setEndRelOffset(bounds.left - parentBounds.left,
                bounds.top - parentBounds.top);
                
        int endRotation = target.getWindowConfiguration().getRotation();
        if (activityRecord != null) {
            
        } else if (isWallpaper(target) && Flags.ensureWallpaperInTransitions()
                && target.getRelativeDisplayRotation() != 0
                && !target.mTransitionController.useShellTransitionsRotation()) {
            
        } else {
            // 保存 end bounds
            change.setEndAbsBounds(bounds);
        }

        // ...

        // 保存前后旋转的方向
        change.setRotation(info.mRotation, endRotation);
        
        // ...
        
        out.addChange(change);
    }
    return out;
}

代码只展示了与本案例相关的数据解析,我就不一一分析了,这里展示下最终的解析数据

txt 复制代码
{id=13 t=OPEN f=0x0 trk=0 r=[0@Point(0, 0)] 
c=[
    {WCT{RemoteToken{21e666b Task{d7726f #39 type=standard A=10206:com.awesome.helloworld}}} 
    m=OPEN f=NONE leash=Surface(name=Task=39)/@0x173bb7c 
    sb=Rect(0, 0 - 1280, 1840) eb=Rect(0, 0 - 1280, 1840) 
    d=0 endFixedRotation=1},
    
    {WCT{RemoteToken{b77580e Task{c7b2d2 #1 type=home}}} 
    m=TO_BACK f=SHOW_WALLPAPER leash=Surface(name=Task=1)/@0xb50d72d 
    sb=Rect(0, 0 - 1280, 1840) eb=Rect(0, 0 - 1280, 1840) d=0}]
}

另外,我还想谈一下 transition root 的原理,代码如下

java 复制代码
// Transition.java

static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
        ArrayList<ChangeInfo> sortedTargets,
        @NonNull SurfaceControl.Transaction startT) {
    // There needs to be a root on each display.
    for (int i = 0; i < sortedTargets.size(); ++i) {
        final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
        // Don't include wallpapers since they are in a different DA.
        if (isWallpaper(wc)) continue;
        final DisplayContent dc = wc.getDisplayContent();
        if (dc == null) continue;
        final int endDisplayId = dc.getDisplayId();

        // 每一个 DisplayContent 只有一个 transition root
        // Check if Root was already created for this display with a higher-Z window
        if (outInfo.findRootIndex(endDisplayId) >= 0) continue;

        // 1.找到共同的祖先
        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);

        // Check whether the ancestor is belonged to last parent, shouldn't happen.
        final boolean hasReparent = !wc.isDescendantOf(ancestor);
        WindowContainer leashReference = wc;
        if (hasReparent) {
            Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
                    + " target= " + wc);
        } else {
            // 2.
            // Make leash based on highest (z-order) direct child of ancestor with a participant.
            while (leashReference.getParent() != ancestor) {
                leashReference = leashReference.getParent();
            }
        }
        
        // 3. 创建一个名为 transition root 的 surface,挂载到 leash reference 的 parent 之下
        final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                "Transition Root: " + leashReference.getName())
                .setCallsite("Transition.calculateTransitionRoots").build();
        rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
        
        // 因为在下一步中,transition root 要使用 leashReference 的 last layer
        // 因此,这里要提前更新 layer,以便 leashReference 得到正确的 last layer
        // Update layers to start transaction because we prevent assignment during collect, so
        // the layer of transition root can be correct.
        assignLayers(dc, startT);
        
        // 4.transition root 使用 leshReference 的 layer,
        // 是为了让 transition root 覆盖在 leshReference 之上
        startT.setLayer(rootLeash, leashReference.getLastLayer());
        
        // 5. trantion root 保存到 TransitionInfo 中
        outInfo.addRootLeash(endDisplayId, rootLeash,
                ancestor.getBounds().left, ancestor.getBounds().top);
    }
}

根据代码注释,我用图来接下了 transition root 原理

stateDiagram-v2 TaskDisplayArea --> launcher_task TaskDisplayArea --> app_task

两个 Task 是动画目标,由于 app task(要启动的 app 的 task) 处于 top 位置,遍历动画目标时,首先遍历到的是 app task,它们的共同祖先是 TaskDisplayArea。因此,leash reference 指的就是 app task,而 transition root surface 就是构建在其之上,如下

stateDiagram-v2 TaskDisplayArea_surface --> launcher_task_surface TaskDisplayArea_surface --> app_task_surface TaskDisplayArea_surface --> transition_root_surface

build finish transaction

动画是在 transition root 上执行的,并且每一个动画目标的 layer,parent 都可能改变。因此,在 finish transaction 中,要对动画目标进行重置,以便它们回归本来的层级结构。 如下

java 复制代码
// Transition.java

/**
 * Build a transaction that "resets" all the re-parenting and layer changes. This is
 * intended to be applied at the end of the transition but before the finish callback. This
 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
 * Additionally, this gives shell the ability to better deal with merged transitions.
 */
private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
    // usually only size 1
    final ArraySet<DisplayContent> displays = new ArraySet<>();
    
    // 遍历动画目标,并重置它们
    for (int i = mTargets.size() - 1; i >= 0; --i) {
        final WindowContainer<?> target = mTargets.get(i).mContainer;
        if (target.getParent() == null) continue;
        
        final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
        final SurfaceControl origParent = getOrigParentSurface(target);
        
        // Ensure surfaceControls are re-parented back into the hierarchy.
        t.reparent(targetLeash, origParent);
        t.setLayer(targetLeash, target.getLastLayer());
        t.setCornerRadius(targetLeash, 0);
        t.setShadowRadius(targetLeash, 0);
        t.setAlpha(targetLeash, 1);
        displays.add(target.getDisplayContent());
        // For config-at-end, the end-transform will be reset after the config is actually
        // applied in the client (since the transform depends on config). The other properties
        // remain here because shell might want to persistently override them.
        if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
            resetSurfaceTransform(t, target, targetLeash);
        }
    }
    
    // 移除截图层,当前动画没有截图层
    // Remove screenshot layers if necessary
    if (mContainerFreezer != null) {
        mContainerFreezer.cleanUp(t);
    }
    
    // 动画期间是不允许更新 layer,所以动画结束时,在 finish transaction 中更新一次 layer
    // Need to update layers on involved displays since they were all paused while
    // the animation played. This puts the layers back into the correct order.
    for (int i = displays.size() - 1; i >= 0; --i) {
        if (displays.valueAt(i) == null) continue;
        assignLayers(displays.valueAt(i), t);
    }
    
    // 移除 transition root
    for (int i = 0; i < info.getRootCount(); ++i) {
        t.reparent(info.getRoot(i).getLeash(), null);
    }
}

build cleanup transaction

在动画结束时,cleanup transaction 是在 WMCore 侧 apply 的,而 finish transaction 是在 WMShell 侧执行的。它们两个的工作都点重叠,按照 AOSP 的注释,是为了以防万一。

java 复制代码
// Transition.java

/**
 * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots).
 * This will ALWAYS be applied on transition finish just in-case
 */
private static void buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info) {

    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
        final TransitionInfo.Change c = info.getChanges().get(i);
        
        // 移除截图层,当前动画没有截图层
        if (c.getSnapshot() != null) {
            t.reparent(c.getSnapshot(), null);
        }
        
        // 当前动画没有发生屏幕旋转,不需要清理 fixed transform hint
        // The fixed transform hint was set in DisplayContent#applyRotation(). Make sure to
        // clear the hint in case the start transaction is not applied.
        if (c.hasFlags(FLAG_IS_DISPLAY) && c.getStartRotation() != c.getEndRotation()
                && c.getContainer() != null) {
            t.unsetFixedTransformHint(WindowContainer.fromBinder(c.getContainer().asBinder())
                    .asDisplayContent().mSurfaceControl);
        }
    }
    
    // 移除 transition root surface
    for (int i = info.getRootCount() - 1; i >= 0; --i) {
        final SurfaceControl leash = info.getRoot(i).getLeash();
        if (leash == null) continue;
        t.reparent(leash, null);
    }
}
相关推荐
雨白8 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹10 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空11 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭12 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日13 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安13 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑13 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟17 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡18 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0019 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体