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);
// ...
}
// ...
}
// ...
}
简单介绍下这几步在做什么
- commit visible activities : 到目前为止,用于 app 绘制的 surface 还没有 show 出来。现在要做动画了,必须得 show 出来。
- calculate animating targets : Transition 收集的 WC ,并不能作为最终动画的目标。例如,有些 WC 其实并没有改变,不需要做动画。再例如,有些 WC 和其 parent 以同样的方式做动画,那么可以用 parent 代替 WC 做动画。
- calculate transition info : WMCore 需要把 Transition 数据化,这样才能发送给 WMShell 执行动画。
- build finish transaction : 所有做动画的 surface,都是在一个名为 transition root 的 surface 上执行的,并且还会伴随 layer 更新。因此,在动画执行完成后,需要把 surface 做复位操作,例如,reparent,重新更新 layer,等等。但是,要注意,这个 finish transaction 是在 WMShell 中执行的。
- build cleanup transaction : cleanup transaction 与 finish transaction 有一些重复的功能,例如,移除截图层,transition root。但是,它却是在 WMCore 侧执行的,按照 AOSP 的说法,just in case。
- 发送数据给 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#mVisible 和 ActivityRecord#mClientVisible。
根据这三个可见性在代码中的用途,我对这个三个可见性,做了如下总结
- ActivityRecord#mVisibleRequested 代表 app 端 activity 是否请求可见。例如,app 要启动一个 activity。
- ActivityRecord#mVisible 用于控制 ActivityRecord surface,以及其下用于 app 绘制 surface 的可见性。
- 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,原因如下
- parent(Task)可以创建远程动画目标。说简单点,就是可以被用来做动画。
- parent 有可见性改变。
- ActivityRecord 没有兄弟节点。
待启动的 ActivityRecord 的 Task,它的动画层级,不能提升至 parent,即 TaskDisplayArea。因为,parent 没有改变。
Launcher 的 ActivityRecord ,它的动画层级可以提升至 parent,原因如下
- parent(Task)可以创建远程动画目标。
- parent 有可见性改变。在更新 ActivityRecord#mVisibleReuqsted 为 false 时,也会递归更新 task 的。因此 Launcher 的 task 以及 root task,他们的可见性都是有改变的。
- Launcher 的 ActivityRecord,一般是有兄弟节点,但是他们没有改变,也没有参与到 Transition 中。
Launcher ActivityRecord 的 Task,也可以提升至 parent,即 root task,原因如下
- root task,它也是 task,可以创建远程动画目标。
- root task 有可见性改变。
- 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 原理
两个 Task 是动画目标,由于 app task(要启动的 app 的 task) 处于 top 位置,遍历动画目标时,首先遍历到的是 app task,它们的共同祖先是 TaskDisplayArea。因此,leash reference 指的就是 app task,而 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);
}
}