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);
    }
}
相关推荐
云深不知处㊣3 小时前
【社交+陪玩服务】全场景陪玩系统源码 小程序+H5双端 社群互动+即时点单+搭建教程
android·小程序·社交源码·找搭子系统源码·陪玩系统源码
casual_clover3 小时前
Kotlin 中实现静态方法的几种方式
android·kotlin
yzpyzp3 小时前
kotlin的?: 操作符(Elvis操作符)
android·kotlin
buleideli4 小时前
Android项目优化同步速度
android·gradle
tangweiguo030519875 小时前
Android 蓝牙工具类封装:支持经典蓝牙与 BLE,兼容高版本权限
android·gitee
cheese-liang6 小时前
Excel中使用VBA自动生成排班表
android·excel
程序员正茂6 小时前
Unity安卓Android从StreamingAssets加载AssetBundle
android·unity·assetbundle·streamingassets
tmacfrank8 小时前
Compose 实践与探索一 —— 关键知识与概念详解
android·ui·kotlin·android jetpack
尼古拉斯大锅盖9 小时前
Android代码最新快速扫描获取手机内图片、视频、音频、文档等文件
android·kotlin
_祝你今天愉快9 小时前
Android12 系统源码编译及踩坑全攻略
android·源码