BLASTSyncEngine#onSurfacePlacement
根据上一篇文章的分析,当 MainActivity 的窗口绘制完成后,执行了窗口大刷新,如下
// RootWindowContainer.java
void performSurfacePlacementNoTrace() {
// ...
mWmService.openSurfaceTransaction();
try {
// 1. 处于所有 DisplayContent 的 surface change
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
// ...
}
// ...
// 2. SyncGroup 同步完成检测
mWmService.mSyncEngine.onSurfacePlacement();
// ...
}
第1步中,通过 WindowState 的 sync transcation, 显示了 MainActivity 的窗口 surface。
第2步中,由于现在窗口已经绘制完成,并且状态也已经完成切换,因此,是时候检测 SyncGroup 的根成员,是否完成同步,如下
// BLASTSyncEngine.java
void onSurfacePlacement() {
if (mActiveSyncs.isEmpty()) return;
mTmpFinishQueue.addAll(mActiveSyncs);
// 为了防止 SyncGroup 依赖循环,这里计算了最大的循环次数,看下面注释
// There shouldn't be any dependency cycles or duplicates, but add an upper-bound just
// in case. Assuming absolute worst case, each visit will try and revisit everything
// before it, so n + (n-1) + (n-2) ... = (n+1)*n/2
int visitBounds = ((mActiveSyncs.size() + 1) * mActiveSyncs.size()) / 2;
while (!mTmpFinishQueue.isEmpty()) {
if (visitBounds <= 0) {
Slog.e(TAG, "Trying to finish more syncs than theoretically possible. This "
+ "should never happen. Most likely a dependency cycle wasn't detected.");
}
--visitBounds;
// 获取 SyncGroup
final SyncGroup group = mTmpFinishQueue.remove(0);
final int grpIdx = mActiveSyncs.indexOf(group);
// Skip if it's already finished:
if (grpIdx < 0) continue;
// 检测 SyncGroup 同步是否完成
if (!group.tryFinish()) continue;
// 这里是处理依赖方 SyncGroup
int insertAt = 0;
for (int i = 0; i < mActiveSyncs.size(); ++i) {
// ...
}
}
}
对于当前分析的屏幕旋转案例来讲,不存在 SyncGroup 相互依赖的情况。因此,这里就是检测单个 SyncGroup 是否同步完成,如下
// BLASTSyncEngine.java
// SyncGroup
private boolean tryFinish() {
// 1. SyncGroup 一定要处于 ready 状态
if (!mReady) return false;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
mSyncId, mRootMembers);
// 如果当前 SyncGroup 依赖于其他 SyncGroup,那么必须等待依赖的 SyncGroup 先完成同步
if (!mDependencies.isEmpty()) {
}
// 2. 检测每一个根成员是否完成同步
for (int i = mRootMembers.size() - 1; i >= 0; --i) {
final WindowContainer wc = mRootMembers.valueAt(i);
if (!wc.isSyncFinished(this)) {
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Unfinished container: %s",
mSyncId, wc);
// 如果任何一个跟成员没有完成同步,代表 SyncGroup 没有完成同步
return false;
}
}
// 3.根成员都完成同步,那么执行 finish 流程
finishNow();
return true;
}
检测 SyncGroup 是否完成同步
- SyncGroup 必须处于 ready 状态。
- 检测 SyncGroup#mRootMembers 保存的每一个跟成员,是否完成同步。如果有任何一个跟成员没有完成同步,那么 SyncGroup 就没有同步完成。
- 如果每一个根成员都同步完成,那么表示 SyncGroup 同步完成,执行 finishNow()。
根据 Android U WMS : 屏幕旋转动画(1)#更新 transition ready 的分析,现在 SyncGroup 已经处于 ready 状态。
根据 Android U WMS : 屏幕旋转动画(1)#请求 change transition 和 Android U WMS : 屏幕旋转动画(1)#DisplayContent 更新配置 的分析,此时的所有根成员有 DisplayContent 和 HelloWorld Task。
检测同步完成
SyncGroup 检测根成员是否同步完成,其实就是递归地检测根成员及其 children 是否同步完成,而最终的决定性因素是可见窗口 WindowState 是否同步完成。对于当前分析的屏幕旋转动画来说,其实就是检测 MainActivity 的窗口 WindowState 是否同步完成,如下
// WindowState.java
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
// 请求不可见,或者完全透明,那么代表同步完成
if (!isVisibleRequested() || isFullyTransparent()) {
// Don't wait for invisible windows. However, we don't alter the state in case the
// window becomes visible while the sync group is still active.
return true;
}
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mWinAnimator.mDrawState == HAS_DRAWN
&& !mRedrawForSyncReported && !mWmService.mResizingWindows.contains(this)) {
// Complete the sync state immediately for a drawn window that doesn't need to redraw.
onSyncFinishedDrawing();
}
// 通过基类函数检测同步完成
return super.isSyncFinished(group);
}
窗口就是可见的,并且也不是透明的,因此使用基类函数决来检测是否同步完成
// WindowContainer.java
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
// 1.必须请求可见
if (!isVisibleRequested()) {
return true;
}
// 2. 同步状态必须为 SYNC_STATE_READY
if (mSyncState == SYNC_STATE_NONE) {
prepareSync();
}
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
return false;
}
// 3. 从上到下遍历 children,检测 child 是否同步完成
// 对于 WindowState 来说,就是子窗口
for (int i = mChildren.size() - 1; i >= 0; --i) {
// ...
}
// 默认同步完成
return true;
}
首先,窗口是可见的。其次,根据 Android U WMS : 屏幕旋转动画(3)#窗口绘制完成 的分析,MainActivity 的窗口 WindowState 的同步状态已经切换到 SYNC_STATE_READY。最后,WindowState 没有子窗口。因此,MainActivity 的窗口 WindowState 已经完成同步。
因此,SyncGroup 此时是已经同步完成。 其实,从更广义的角度来讲,SyncGroup 检测同步完成,其实就是检测可见窗口是否重绘完成。
同步完成
SyncGroup 的根成员都同步完成以后,就会执行 SyncGroup#finishNow()
private void finishNow() {
// ...
// 1. 创建一个 Transaction,收集 sync transaction
SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
// WC 被移除时,如果 parent 不处于同步状态,那么 WC 的 sync transcation 被合并到 mOrphanTransaction
if (mOrphanTransaction != null) {
merged.merge(mOrphanTransaction);
}
for (WindowContainer wc : mRootMembers) {
wc.finishSync(merged, this, false /* cancel */);
}
// 2. 收集需要等待 sync transaction 提交的 WC
final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
for (WindowContainer wc : mRootMembers) {
wc.waitForSyncTransactionCommit(wcAwaitingCommit);
}
class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
// 3.1 处理 merged sync transaction 的提交
public void onCommitted(SurfaceControl.Transaction t) {
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
}
mHandler.removeCallbacks(this);
ran = true;
// 通过一个 Transaction,收集那些等待 merged sync transaction
// 提交的 WC 的 sync transcation ,然后 apply
for (WindowContainer wc : wcAwaitingCommit) {
wc.onSyncTransactionCommitted(t);
}
t.apply();
wcAwaitingCommit.clear();
}
}
// 3.2 处理 merged sync transaction 提交超时
public void run() {
// ...
}
};
CommitCallback callback = new CommitCallback();
// 3. 通过一个回调,监听 merged sync transaction 的提交
merged.addTransactionCommittedListener(Runnable::run,
() -> callback.onCommitted(new SurfaceControl.Transaction()));
// merged sync transaction 的提交有超时限制
mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
// 4. 通知监听者 transaction ready
// 这个监听者就是 Transition
mListener.onTransactionReady(mSyncId, merged);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
// 既然 SyncGroup 已经 finish 了,从 mActiveSyncs 移除 SyncGroup
mActiveSyncs.remove(this);
// 移除 SyncGroup 收集 WC 的超时回调
mHandler.removeCallbacks(mOnTimeout);
if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
// ...
}
for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) {
// ...
}
}
SyncGroup 同步完成,主要做了以下几件事
- 创建一个 Transaction,主要是收集根成员及其处于同步状态的 children 的 sync transcation。参考【收集 sync transaction】
- 把根成员及其所有 children 收集起来,它们需要等待 sync transaction 的提交。参考【收集等待 merged sync transcation 提交的 WC】
- 通过一个回调,监听 merged sync transcation 的提交。其实 merged sync transcation 是在 WMShell 侧 apply 的,WMCore 收到提交回调后,会把那些等待 merged sync transaction 提交的 WC 的 sync transcation 收集起来,然后 apply。其实就是同步提交 WMShell 和 WMCore 两侧的 sync transaction。
- 通知 Transition transaction ready。这个 transaction 就是 merged sync transaction。
收集 sync transcation
SyncGroup 收集根成员的 sync transcation,是在根成员的完成同步的函数 finishSync() 中进行的。对于屏幕旋转动画来说,根成员只有 DisplayContent 和 HelloWorld Task,但是它们都没有复写 finishSync(),使用的是基类 WindowContainer 函数,如下
// WindowContainer.java
void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
boolean cancel) {
// 1.WC 不处于同步状态,不需要完成同步
if (mSyncState == SYNC_STATE_NONE) return;
// 2. SyncGroup 不相同,也不能完成同步
final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
if (syncGroup != null && group != syncGroup) return;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this);
// 3.合并 WC 的 sync transaction
outMergedTransaction.merge(mSyncTransaction);
// 4.递归地合并 WC children 的 sync transaction
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).finishSync(outMergedTransaction, group, cancel);
}
// 此时 cancel 为 false
if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
// 5.重置同步的数据
mSyncState = SYNC_STATE_NONE;
mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
mSyncGroup = null;
}
基类的同步完成的逻辑,就是递归收集 当前WC 及其 children 的 sync transaction,但是有个前提,被收集 sync transaction 的 WC 必须处于同步状态。
目前,DisplayContent 下,只有做异步旋转的 WC ,不处于同步状态,因此,除了异步旋转的 WC,其他的 WC 的 sync transaction 都会被收集。
收集等待 merged sync transcation 提交的 WC
SyncGroup 调用根成员的 waitForSyncTransactionCommit() 来收集等待 merged sync transaction 提交的 WC。根成员 DisplayContent 和 Task 都没有复写这个函数,使用基类 WindowContainer 的函数,如下
// WindowContainer.java
void waitForSyncTransactionCommit(ArraySet<WindowContainer> wcAwaitingCommit) {
if (wcAwaitingCommit.contains(this)) {
return;
}
// 这个变量不为0,代表 WC 正在等待 sync transaction 提交
mSyncTransactionCommitCallbackDepth++;
// 递归地收集 WC 及其 children
wcAwaitingCommit.add(this);
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).waitForSyncTransactionCommit(wcAwaitingCommit);
}
}
非常简单,就是递归收集 WC 及其 children,没有任何限制。因此,做异步旋转的 WC 也会被收集。
transaction ready
SyncGroup 收集完 sync transcation 后,通知 Transition transaction ready,并把这个 merged sync transcation 传递给 Transition,如下
// Transition.java
// 参数 transaction 是 SyncGroup 收集的 sync tranasction
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
// ...
// Transition 状态切换到 STATE_PLAYING
mState = STATE_PLAYING;
// mStartTransaction 保存的是 merged sync transaction
// 现在它的名字叫 start transaction
mStartTransaction = transaction;
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
// ...
// 1. 计算所有做动画的目标
mTargets = calculateTargets(mParticipants, mChanges);
// ...
// 2. TransitionInfo 收集动画信息
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
// 给 Transition 和 TransitionInfo 设置 track id
mController.assignTrack(this, info);
// 3.把正在收集的 Transition 保存到 mPlayingTransitions,并且重置 mCollectingTransition
mController.moveToPlaying(this);
// 根据 transition root leash 所在的 DisplayContent,重新填充 mTargetDisplays
// Repopulate the displays based on the resolved targets.
mTargetDisplays.clear();
for (int i = 0; i < info.getRootCount(); ++i) {
final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
info.getRoot(i).getDisplayId());
mTargetDisplays.add(dc);
}
// ...
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AsyncRotationController controller = dc.getAsyncRotationController();
// 4.如果 DisplayContent 发生了改变,异步旋转在 start transaction 做一些初始化操作
if (controller != null && containsChangeFor(dc, mTargets)) {
controller.setupStartTransaction(transaction);
}
}
// 4.构建 finish transaction
buildFinishTransaction(mFinishTransaction, info);
// 5.构建 cleanup transaction
mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildCleanupTransaction(mCleanupTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
mLogger.mInfo = info;
// 6. 通知 WMShell 开始执行动画
mController.getTransitionPlayer().onTransitionReady(
mToken, info, transaction, mFinishTransaction);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
}
} catch (RemoteException e) {}
// ...
} else {}
// ...
}
Transition 处理 transaction ready
- 计算所有要做动画的目标。并不是所有动画的参与者都需要做动画,因此需要计算最终需要做动画的目标。
- 计算动画的信息,保存到 TransitionInfo。由于动画是在 WMShell 中执行,因此需要把动画信息转化成跨进程传输的数据,然后发送给 WMShell。参考【计算动画目标】
- Transition 马上要执行了,因此重置了 Transition#mCollectingTransition,然后把 Transition 保存到 Transition#mPlayingTransition 集合中。
- 构建 finish transaction ,用于在动画执行完成后,执行复位操作。参考【构建 finish transaction】
- 构建 cleanup transaction,用于清理 transition root leash 以及截图层 的 surface。其实,这些工作在 finish transcation 执行过,只不过,为了以防万一,再执行了一遍。
- 通知 WMShell 开始执行动画。传递了 TransitionInfo, start transaction(即 merged sync transcation),finish transcation。
计算动画目标
在分析动画目标的计算之前,对于当前分析的屏幕旋转动画,首先得知道,数据结构到底保存了哪些数据。根据 Android U WMS : 屏幕旋转动画(1) 的分析可知
- Transition#mParticipants 只保存了 DisplayContent 和 HelloWorld Task。
- Transition#mChanges 保存了 DisplayContent, TaskDisplayArea,HelloWorld Task 在改变前 的信息。
知道这些数据之后,再来分析屏幕旋转动画的动画目标的计算,如下
// Transition.java
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 保存有效的参与者作为动画目标
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;
}
// WindowState 不能参与动画,最低要求是 WindowToken
// The level of transition target should be at least window token.
if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
// 没有改变的 WC 也不能参与动画
if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as no-op: %s", wc);
continue;
}
// 保存有效参与者的 ChangeInfo
targets.add(changeInfo);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
targets.mArray);
// 2. 尝试提升动画目标的层级
tryPromote(targets, changes);
// 3. 建立动画目标之间的关联
// Establish the relationship between the targets and their top changes.
populateParentChanges(targets, changes);
// 4.返回动画目标的 ChangeInfo 列表,以 z-order 降序排列
final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
return targetList;
}
计算动画目标
- 首先从 Transition 的参与者中,过滤掉一些无效的参与者,例如,参与者没有改变。剩下的都是有效的参与者,它们的 ChangeInfo 会被保存到 Targets 中。对于屏幕旋转动画来说,它的几个参与者都是有效的。
- 尝试提升动画目标的层级。何谓提升层级?举个例子,如果 Activity 是一个动画目标,其实可以把这个动画目标提升到 parent,也就是 Task。那么不用对 ActivityRecord 动画,而是对 Task 做动画。
- 建立动画目标之间的关联。其实就是利用 ChangeInfo#mEndParent 指向父动画目标,并且还会收集一些 TaskFragment, DiaplayArea 作为动画目标。 对于屏幕旋转动画来说,这一步没有实质性作用,本文就不分析了。
- 从 Targets 中返回动画目标的 ChangeInfo 列表。
OK,来看下如何提升动画目标的层级
// Transition.java
private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
WindowContainer<?> lastNonPromotableParent = null;
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();
// 这里是一个优化的代码,如果当前 parent 与上一次不能提升的 parent 一样
// 那么,当前动画目标肯定就不能提升
if (parent == lastNonPromotableParent) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: its sibling was rejected");
continue;
}
// 1.检测动画目标是否可以提升到 parent
if (!canPromote(targetChange, targets, changes)) {
lastNonPromotableParent = parent;
continue;
}
// 走到这里,表示能提升动画层级
// 2. 既然能提升,但是还需要根据条件移除动画目标
// 这个条件是动画目标的 WC 是否被 organized
if (reportIfNotTop(target)) {
// 如果 WC 被 organized,那么动画目标不需要从 Targets 中移除
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);
// 如果 WC 不被 organized,那么动画目标需要从 Targets 中移除
targets.remove(i);
}
// 3. 既然能提升了,那么把 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);
}
// 4. 既然能提升,把动画目标的,关于是否做动画标志位转移到 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;
}
}
}
尝试提升动画目标的层级
- 检测动画目标是否能提升层级到 parent。
- 既然能提升,那么需要移除当前动画目标,保存 parent 这个动画目标。但是,如果这个动画目标的 WC 被 organized,是不需要移除的,因为它被 WMShell 管理,所以也可能是动画的一部分。一般来说,只有 DisplayContent,TaskDisplayArea,root Task 是被 organized。
- 既然能提升,那么把 parent 加入到动画目标。
- 既然能提升,把动画目标的,关于是否做动画标志位转移到 parent 中。也就是说,如果动画目标的标志位决定其不做动画,那么提升的 parent 也不用做动画。
好,一切的关键在于如何判断动画目标是否能提升层级到 parent,来看下
// Transition.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 不能创建动画目标,或者 parent 没有改变,那么动画目标不能提升层级
if (!parent.canCreateRemoteAnimationTarget()
|| parentChange == null || !parentChange.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
"parent can't be target " + parent);
return false;
}
// 壁纸 WindowToken 不能提升动画层级
if (isWallpaper(target)) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper");
return false;
}
// parent 改变了,也不能提升动画层级
if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
// When a window is reparented, the state change won't fit into any of the parents.
// Don't promote such change so that we can animate the reparent if needed.
return false;
}
// 获取动画目标的 transition mode
final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
// 从 parent 遍历动画目标的兄弟节点
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);
// 兄弟节点没有 ChangeInfo 或者 不是动画目标,不参与比较
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;
}
// 走到这里表示兄弟有改变,并且还是动画目标
// 获取兄弟节点的 transition mode
final int siblingMode = siblingChange.getTransitMode(sibling);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" sibling is a participant with mode %s",
TransitionInfo.modeToString(siblingMode));
// 动画目标和兄弟的 reduce transition mode 不相等,那么也不能提升层级
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;
}
// 计算 reduce transition mode
/** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
@TransitionInfo.TransitionMode
private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
switch (mode) {
case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
default: return mode;
}
}
检测一个动画目标是否能提升动画层级,是采用排除法。这里列举下不能提升的情况
- parent 不能创建远程动画目标。目前,只有 ActivityRecord,TaskFragment, TaskDisplayArea 能创建远程动画目标。
- parent 没有改变。
- 动画目标和兄弟的 reduce transition mode 不相同。什么是 reduce transition mode ?它是在可见性方面,把 transition mode 缩小,例如 TRANSIT_TO_BACK -> TRANSIT_CLOSE。对于旋转动画来说,transition mode 是 TRANSIT_CHANGE,reduce transition mode 也是 TRANSITION_CHANGE。
对于屏幕旋转动画来说,目前 Targets 保存的是 DisplayContent, HelloWorld Task。 Transition#mChanges 保存了 DisplayContent, TaskDisplayArea,HelloWorld Task 在改变前的信息。
那么,Task 肯定是能提升动画的层级的,原因如下
- Task 的 parent TaskDisplayArea 有改变(例如,配置中的旋转方向),并且也能创建远程动画目标。
- Task 的兄弟,不在 Targets 中,即不是动画目标。
既然 Task 能提升动画层级到 parent, 那么 Task 是否需要从 Targets 中移除呢? 不需要!因为 HelloWorld Task 是 root task,它是被 organized 的。
计算 transition info
calculateTargets() 计算出了所有的动画目标,并返回了一个 ArrayList<ChangeInfo>,现在 calculateTransitionInfo() 把这个动画目标集合,转化成可以跨进程传输的数据 TransitionInfo,如下
// type 是 TRANSIT_CHANGE
// flags 是 0
// sortedTargets 是所有动画目标的数据
// startT 是合并后的 sync transaction,现在称之为 start transaction
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
ArrayList<ChangeInfo> sortedTargets,
@NonNull SurfaceControl.Transaction startT) {
// 创建 TransitionInfo 用于动画目标的信息
final TransitionInfo out = new TransitionInfo(type, flags);
// 1. 为每一个 DisplayContent 只创建一个 transition root leash
// 并保存到 Transition#mRoots 集合中
calculateTransitionRoots(out, sortedTargets, startT);
if (out.getRootCount() == 0) {
return out;
}
// 2. 把动画目标的 ChangeInfo 转换成 TransactionInfo.Change,然后保存到 TransitionInfo
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
final ChangeInfo info = sortedTargets.get(i);
final WindowContainer target = info.mContainer;
// 创建 Change
final TransitionInfo.Change change = new TransitionInfo.Change(
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
// mEndParent 是在 populateParentChanges() 中设置的
// 这里把它保存到了 Change#mParent 中
if (info.mEndParent != null) {
change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
}
if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
&& target.getParent() != info.mStartParent) {
// ...
}
// 保存 transition mode
change.setMode(info.getTransitMode(target));
info.mReadyMode = change.getMode();
change.setStartAbsBounds(info.mAbsoluteBounds);
// 保存 flags
change.setFlags(info.getChangeFlags(target));
info.mReadyFlags = change.getFlags();
change.setDisplayId(info.mDisplayId, getDisplayId(target));
final Task task = target.asTask();
final TaskFragment taskFragment = target.asTaskFragment();
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()) {
change.setAllowEnterPip(
topRunningActivity.checkEnterPictureInPictureAppOpsState());
}
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 的偏移量
change.setEndRelOffset(bounds.left - parentBounds.left,
bounds.top - parentBounds.top);
// 获取旋转后的方向
int endRotation = target.getWindowConfiguration().getRotation();
if (activityRecord != null) {
// ...
} else {
change.setEndAbsBounds(bounds);
}
if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
// ...
}
// 保存旋转前后的方向
change.setRotation(info.mRotation, endRotation);
// 保存截图 surface
if (info.mSnapshot != null) {
change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
}
// TransitionInfo#Changes 集合保存 Change
out.addChange(change);
}
// 3. TransitionInfo#mOptions 保存 activity 动画数据
TransitionInfo.AnimationOptions animOptions = null;
// 省略一段 activity to activity 的动画参数的解析...
if (animOptions != null) {
out.setAnimationOptions(animOptions);
}
return out;
}
TransitionInfo 总共保存了三类数据
- TransitionInfo 使用 ArrayList<Root> mRoots 保存 transition root leash,它其实就是一个 SurfaceControl。动画目标的 surface 都要直接/间接地 reparent 到 root leash。
- 为每一个动画目标创建一个 Change 对象,并填充数据,然后保存到 TransitionInfo 的 ArrayList<Change> mChanges 。
- 解析 Activity to activity 的动画,保存到 TransitionInfo 的 mOptions 。
屏幕旋转动画,不涉及第3步的 activity to activity 动画。
对于第2步,填充数据到 Change。这里要重点提一下 Change 构造函数的第二个参数,也就是动画目标的 leash,获取方式如下
// Transition.java
private static SurfaceControl getLeashSurface(WindowContainer wc,
@Nullable SurfaceControl.Transaction t) {
final DisplayContent asDC = wc.asDisplayContent();
if (asDC != null) {
// DisplayContent leash 是 mWindowingLayer,而不是它自己的 surface
// DisplayContent is the "root", so we use the windowing layer instead to avoid
// hardware-screen-level surfaces.
return asDC.getWindowingLayer();
}
if (!wc.mTransitionController.useShellTransitionsRotation()) {
// 屏幕旋转动画不涉及 WindowToken
final WindowToken asToken = wc.asWindowToken();
if (asToken != null) {
// ...
}
}
// 默认返回动画目标的 surface
return wc.getSurfaceControl();
}
可以看到只有 DisplayContent 比较特殊,它的 leash 并不是自己的 surface,而是 mWindowingLayer,它的值其实是 WindowedMagnification:0:31 的 child。这是 WMS 窗口层级的基本知识,这里就不过多分析了。
最后看下如何计算 transition root leash
// 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 int endDisplayId = getDisplayId(wc);
if (endDisplayId < 0) continue;
// 注意,一个 DisplayContent 只能有一个 transition root leash
if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
// 1.找到同一个 DisplayContent 下,所有动画目标的公共祖先
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
// 2. 找到一个创建 leash 的参考点
// Make leash based on highest (z-order) direct child of ancestor with a participant.
// Check whether the ancestor is belonged to last parent, shouldn't happen.
final boolean hasReparent = !wc.isDescendantOf(ancestor);
WindowContainer leashReference = wc;
if (hasReparent) {
// 根据上面注释所说,这是一个异常情况,此时 reparent 是不应该发生的
Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
+ " target= " + wc);
} else {
// 从 wc 到共同祖先这条线上,最高的那个 parent,就是创建 leash 参考点
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
}
// 3.创建 transition root leash
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
// 注意,root leash 的 layer 是 0
startT.setLayer(rootLeash, leashReference.getLastLayer());
// 4.保存到 TransitionInfo#mRoots 中
outInfo.addRootLeash(endDisplayId, rootLeash,
ancestor.getBounds().left, ancestor.getBounds().top);
}
}
创建 transition root leash,其实就是创建一个 SurfaceControl,所有动画都是在 root leash 执行的,并且每一个 DisplayContent 只有一个 root leash。 它的创建过程如下
- 首先找到同一个 DisplayContent 下的所有动画目标的共同祖先。
- 找到一个创建 root leash 的参考点,这个参考点,是所有动画目标中,最接近共同祖先的那一个。
- 利用 leash 参考点,创建 root leash surface,并把 layer 设置为 0。
- TransitionInfo 根据root leash 信息,创建 Root,并保存到 mRoots 集合中。
我曾经质疑过,root leash 的 layer 设置为 0,到底显示在哪里?可以通过 winscope 查看 root leash 前后的层级情况。
在 root leash 显示之前,WindowdMagnification:0:31 挂在 DisplayContent surface 之下,并且 layer 是 0,如下
在 root leash 显示之后,root leash 的 layer 是 0,它显示在 WindowdMagnification:0:31 之上,如下
继续来看第2步,创建 leash 参考点是共同祖先的 child,那么它的 makeAnimationLeash() 创建的 leash surface 到底挂在哪个 surface 之下呢? 首先看下基类 WindowContainer 的实现
// WindowContainer.java
public Builder makeAnimationLeash() {
return makeSurface().setContainerLayer();
}
Builder makeSurface() {
final WindowContainer p = getParent();
return p.makeChildSurface(this);
}
Builder makeChildSurface(WindowContainer child) {
final WindowContainer p = getParent();
return p.makeChildSurface(child)
// 挂在 parent surface 之下
.setParent(mSurfaceControl);
}
leash 参考点的 parent 是 公共祖先,那么 leash 参考点创建的 surface 默认是挂在公共祖先的 parent surface 之下。
但是,对于屏幕旋转动画来说,动画目标有 DisplayContent, TaskDisplayArea,Task,因此公共祖先就是 RootWindowContainer,而 leash 参考点就是 DisplayContent。那么 DisplayContent 创建的 root leash surface 是不是挂在 RWC surface 之下? 不是这样的,DisplayContent 复写了 makeAnimationLeash()
// DisplayContent.java
public SurfaceControl.Builder makeAnimationLeash() {
return mWmService.makeSurfaceBuilder(mSession).setParent(mSurfaceControl)
.setContainerLayer();
}
原来,DisplayContent 创建的 leash surface 挂在自己的 surface 之下。
构建 finish transaction
// 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) {
final Point tmpPos = new Point();
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
// 1.复位所有的动画目标
for (int i = mTargets.size() - 1; i >= 0; --i) {
final WindowContainer target = mTargets.get(i).mContainer;
if (target.getParent() != null) {
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());
target.getRelativePosition(tmpPos);
t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
// No need to clip the display in case seeing the clipped content when during the
// display rotation. No need to clip activities because they rely on clipping on
// task layers.
if (target.asTaskFragment() == null) {
t.setCrop(targetLeash, null /* crop */);
} else {
// Crop to the resolved override bounds.
final Rect clipRect = target.getResolvedOverrideBounds();
t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
}
t.setCornerRadius(targetLeash, 0);
t.setShadowRadius(targetLeash, 0);
t.setMatrix(targetLeash, 1, 0, 0, 1);
t.setAlpha(targetLeash, 1);
// The bounds sent to the transition is always a real bounds. This means we lose
// information about "null" bounds (inheriting from parent). Core will fix-up
// non-organized window surface bounds; however, since Core can't touch organized
// surfaces, add the "inherit from parent" restoration here.
if (target.isOrganized() && target.matchParentBounds()) {
t.setWindowCrop(targetLeash, -1, -1);
}
displays.add(target.getDisplayContent());
}
}
// 2.移除截图层
// Remove screenshot layers if necessary
if (mContainerFreezer != null) {
mContainerFreezer.cleanUp(t);
}
//3. 更新 WMS 窗口层级,以及 surface 层级
mController.mBuildingFinishLayers = true;
try {
for (int i = displays.size() - 1; i >= 0; --i) {
if (displays.valueAt(i) == null) continue;
displays.valueAt(i).assignChildLayers(t);
}
} finally {
mController.mBuildingFinishLayers = false;
}
// 4.移除 transition root leash
for (int i = 0; i < info.getRootCount(); ++i) {
t.reparent(info.getRoot(i).getLeash(), null);
}
}
根据注释就可以看出,finish transaction 是在 WMShell 中执行的,并且是在动画完成后执行,因此 finish transaction 执行的是复位操作
- 复位所有的动画目标。例如,WMShell 在执行动画前,会把动画目标的 leash,直接/间接地 reparent 到 transition root leash。那么,当动画执行完成后,就需要把动画目标的 leash 再 reparent 到本来的 parent 。
- 移除截图层。对于屏幕旋转动画来说,创建了截图层。在动画执行完成后,就需要移除它。
- WMShell 在执行动画前,会对动画目标的 leash 设置 layer。那么,当动画结束后,就需要重新根据 WMS 层级,设置动画目标 leash 的 layer,使它们回归本来的位置。
- 移除 transition root leash。
下一步
现在一切就绪了,下一篇文章就来分析 WMShell 如何执行旋转动画。