本文来分析,从创建 Transition,到 Transition 就绪的完整流程。
Transition 收集 WC
第一阶段启动,窗口层级构建时,Transition 收集了 Task 的存在性改变,如下
// ActivityStarter.java
private void setNewTask(Task taskToAffiliate) {
final boolean toTop = !mLaunchTaskBehind && !avoidMoveToFront();
// 创建 Task
final Task task = mTargetRootTask.reuseOrCreateTask(
mStartActivity.info, mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
// Transition 收集存在性改变的 Task
task.mTransitionController.collectExistenceChange(task);
// Task 保存 ActivityRecord
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");
// ...
}
第一阶段启动结束时,对要启动的 ActivityRecord,收集了它的存在性改变,如下
// ActivityStarter.java
private @Nullable Task handleStartResult(@NonNull ActivityRecord started,
ActivityOptions options, int result, Transition newTransition,
RemoteTransition remoteTransition) {
// ...
// 成功启动
final boolean isStarted = result == START_SUCCESS || result == START_TASK_TO_FRONT;
// ...
if (isStarted) {
// 收集存在性改变的 ActivityRecord
// started 就是要启动的 activity
transitionController.collectExistenceChange(started);
}
// ...
if (newTransition != null) {
// 向 WMShell 请求启动 transition
transitionController.requestStartTransition(newTransition,
mTargetTask == null ? started.getTask() : mTargetTask,
remoteTransition, null /* displayChange */);
}
// ...
return startedActivityRootTask;
}
第二阶段启动,更新所有 Activity 可见性时,待启动的 ActivityRecord 以及 launcher ActivityRecord 的可见性,发生了改变,Transition 收集了它们,如下
// ActivityRecord.java
private void setVisibility(boolean visible, boolean deferHidingClient) {
// ...
boolean isCollecting = false;
boolean inFinishingTransition = false;
if (mTransitionController.isShellTransitionsEnabled()) {
isCollecting = mTransitionController.isCollecting();
if (isCollecting) {
// 更新可见性之前,先收集
mTransitionController.collect(this);
} else {
}
}
// ..
// 更新可见性
setVisibleRequested(visible);
// ...
}
以上就是 Transition 收集的所有 WC,现在来看下代码实现,如下
// Transition.java
void collect(@NonNull WindowContainer wc) {
// ...
// Transient-hide may be hidden later, so no need to request redraw.
if (!isInTransientHide(wc)) {
// 1. SyncGroup 保存需要同步的 WC
mSyncEngine.addToSyncSet(mSyncId, wc);
}
// ...
// 2. 为 WC 建立 ChangeInfo,并保存到 mChanges 中
// ChangeInfo 保存了 WC 当前的状态
ChangeInfo info = mChanges.get(wc);
if (info == null) {
info = new ChangeInfo(wc);
updateTransientFlags(info);
mChanges.put(wc, info);
}
// 3. mParticipants 保存所有 Transition 的参与者
mParticipants.add(wc);
// ...
// 4. 如果 WC 需要显示壁纸,那么还需要收集壁纸 WindowToken
if (info.mShowWallpaper) {
wc.mDisplayContent.mWallpaperController.collectTopWallpapers(this);
}
}
由于 launcher 需要显示壁纸,因此还需额外收集了壁纸的 WindowToken,如下
// WallpaperController.java
void collectTopWallpapers(Transition transition) {
if (mFindResults.hasTopShowWhenLockedWallpaper()) { // 有锁屏壁纸
if (Flags.ensureWallpaperInTransitions()) {
// 收集壁纸 WallpaperWindowToken
transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper.mToken);
} else {
}
}
if (mFindResults.hasTopHideWhenLockedWallpaper()) { // 无锁屏壁纸,但有桌面壁纸
if (Flags.ensureWallpaperInTransitions()) {
// 收集壁纸 WallpaperWindowToken
transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper.mToken);
} else {
}
}
}
Transition 收集的 4 个 WC,还会被被 SyncGroup 收集,如下
SyncGroup 会保证所有被收集的 WC ,都重绘完成。
// BLASTSyncEngine.java
void addToSyncSet(int id, WindowContainer wc) {
getSyncGroup(id).addToSync(wc);
}
class SyncGroup {
private void addToSync(WindowContainer wc) {
if (mRootMembers.contains(wc)) {
return;
}
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
final SyncGroup dependency = wc.getSyncGroup();
if (dependency != null && dependency != this && !dependency.isIgnoring(wc)) {
// SyncGroup 相互依赖的情况 ...
} else {
// SyncGroup 使用 mRootMembers 保存需要 redraw 的 WC
mRootMembers.add(wc);
// WC 保存 SyncGroup
wc.setSyncGroup(this);
}
// prepare sync
wc.prepareSync();
// 此时还没有 ready
if (mReady) {
}
}
}
SyncGroup 与 WC 相互保存后,让 WC 执行 prepare sync,主要是为了切换同步状态。而同步状态用于检测 WC 是否处于同步中,同时也代表 WC 是否需要重绘。
基类 WindowContainer 的 prepare sync 逻辑,是递归地让所有 children 都 prepare sync,然后把同步状态都切换到 SYNC_STATE_READY,如下
// WindowContainer.java
boolean prepareSync() {
// 已经处于同步状态
if (mSyncState != SYNC_STATE_NONE) {
return false;
}
// children 也进行 prepare sync
for (int i = getChildCount() - 1; i >= 0; --i) {
final WindowContainer child = getChildAt(i);
child.prepareSync();
}
// 同步状态,切换到 SYNC_STATE_READY
mSyncState = SYNC_STATE_READY;
return true;
}
有两个子类对 prepare sync 有自己的想法,他们就是 WindowToken 和 WindowState,如下
// WindowToken.java
boolean prepareSync() {
// 如果发生旋转,并且 WindowToken 可以做异步旋转,那么它是不需要 prepare sync 的
if (mDisplayContent != null && mDisplayContent.isRotationChanging()
&& AsyncRotationController.canBeAsync(this)) {
return false;
}
return super.prepareSync();
}
// WindowState.java
boolean prepareSync() {
// ...
// 先通过基类方法 prepare sync
// 同步状态,会切换到 SYNC_STATE_READY
if (!super.prepareSync()) {
return false;
}
// 壁纸 WindowState 只会执行到这一步
// 也就是说,同步状态保持为 SYNC_STATE_READY
if (mIsWallpaper) {
return true;
}
if (mActivityRecord != null && mViewVisibility != View.VISIBLE
&& mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
&& mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
// Skip sync for invisible app windows which are not managed by activity lifecycle.
return false;
}
// 同步状态切换到 SYNC_STATE_WAITING_FOR_DRAW
// 表示需要等待重绘
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
if (mPrepareSyncSeqId > 0) {
}
mSyncSeqId++;
if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
} else if (mHasSurface && mWinAnimator.mDrawState != DRAW_PENDING) {
// Only need to request redraw if the window has reported draw.
// 重置 mRedrawForSyncReported 为 false
// 表示需要通知 app ,同步它绘制的 buffer
requestRedrawForSync();
}
return true;
}
归纳基类 WindowContainer,以及子类 WindowToken 和 WindowState 的 prepare sync 逻辑
- 一般来说,WindowState 的同步状态切换到 SYNC_STATE_WAITING_FOR_DRAW 。但是,壁纸的 WindowState,同步状态只切换到 SYNC_STATE_READY。
- 一般来说,WindowState 以外的 WC,同步状态都是切换到 SYNC_STATE_READY。但是,异步旋转的 WindowToken 不需要 prepare sync,即同步状态保持 SYNC_STATE_NONE。因为它们不参与 Transition 动画,而是做异步旋转。
总结下,WC 的 prepare sync 就是标记其下的 WindowState 需要重绘,即绘制状态切换到 SYNC_STATE_WAITING_FOR_DRAW。而壁纸窗口是一个特例,由于它依赖壁纸 target,它不依赖 prepare sync 来标记重绘,因此同步状态切换到 SYNC_STATE_READY。
request start transition
第一阶段启动结束时,会向 WMShell request start transition,如下
// ActivityStarter.java
private @Nullable Task handleStartResult(@NonNull ActivityRecord started,
ActivityOptions options, int result, Transition newTransition,
RemoteTransition remoteTransition) {
// ...
// 成功启动
final boolean isStarted = result == START_SUCCESS || result == START_TASK_TO_FRONT;
// ...
if (isStarted) {
// 收集存在性改变的 ActivityRecord
// started 就是要启动的 activity
transitionController.collectExistenceChange(started);
}
// ...
if (newTransition != null) {
// 向 WMShell 请求启动 transition
transitionController.requestStartTransition(newTransition,
mTargetTask == null ? started.getTask() : mTargetTask,
remoteTransition, null /* displayChange */);
}
// ...
return startedActivityRootTask;
}
// TransitionController.java
// startTask 是要启动的 Activity 的 Task
// remoteTransition 是从启动 activity 传入的 Bundle 中解析出来的,它代表一个远程动画
// displayChange 为 null
Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange) {
// ...
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
ActivityManager.RunningTaskInfo startTaskInfo = null;
ActivityManager.RunningTaskInfo pipTaskInfo = null;
// startTask 是要启动的 Activity 的 Task
if (startTask != null) {
// 获取 task info
startTaskInfo = startTask.getTaskInfo();
}
// ...
// 把 transition request 数据化
final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
transition.getFlags(), transition.getSyncId());
// ...
// 向 WMShell 发起请求
mTransitionPlayers.getLast().mPlayer.requestStartTransition(
transition.getToken(), request);
// ...
}
// ...
return transition;
}
来看下 WMShell 处理 request start transition
// Transitions.java
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
request.getDebugId(), transitionToken, request);
if (mKnownTransitions.containsKey(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
// 在 WMShell 中,ActiveTransition 代表一个 transition 动画
final ActiveTransition active = new ActiveTransition(transitionToken);
mKnownTransitions.put(transitionToken, active);
WindowContainerTransaction wct = null;
if (request.getType() == TRANSIT_SLEEP) {
} else {
// 1. 先让 TransitionHandler 处理请求,
for (int i = mHandlers.size() - 1; i >= 0; --i) {
wct = mHandlers.get(i).handleRequest(transitionToken, request);
if (wct != null) {
// ActiveTransition#mHandler 保存了对 transition request 感兴趣的 TransitionHandler
// 当开始执行动画时,它将优先尝试执行动画
active.mHandler = mHandlers.get(i);
}
}
if (request.getDisplayChange() != null) {
}
}
// ...
// 2.通知 WMCore start transition
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
// 3. mPendingTransitions 保存待执行的 transition 动画
mPendingTransitions.add(0, active);
}
transition request 会依次交给所有 TransitionHandler 处理,谁对这个 request 有兴趣,就会返回一个非空的 WindowContainerTransaction ( 简称 WCT ) 对象。然后通知 WMCore 去 start transition,并传入 WCT 。
WCT 包含了对 WindowContainer 的操作,会被传入到 WMCore,并在在 WMCore 侧执行。
从桌面启动 app,是由桌面执行动画的,这种动画叫做远程动画。因此,在 WMShell 侧,RemoteTransitionHandler 对这个 transition request 感兴趣,如下
// RemoteTransitionHandler.java
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@Nullable TransitionRequestInfo request) {
RemoteTransition remote = request.getRemoteTransition();
if (remote == null) return null;
// transition token -> RemoteTransition
mRequestedRemotes.put(transition, remote);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
+ " for (#%d) %s: %s", request.getDebugId(), transition, remote);
// 返回一个空数据的 WCT,仅仅代表当前 TransitionHandler 对这个 transition request 感兴趣
return new WindowContainerTransaction();
}
RemoteTransitionHandler 以 transition token 为 KEY,保存了 RemoteTransition 。当需要 Launcher 执行远程动画时,会通过 RemoteTransitionHandler#mRemoteTransition 这个 binder 回调,通知 Launcher 执行动画。
虽然是 RemoteTransitionHandler 对这个 transition request 直接感兴趣,但是返回的 TransitionHandler 却是 DefaultMixedHandler。DefaultMixedHandler 是为了处理多种 Transition 动画而设计的,例如,在分屏的子窗口中启动 app。本文为了简化分析,跳过 DefaultMixedHandler。
start transition
// WindowOrganizerController.java
private IBinder startTransition(@WindowManager.TransitionType int type,
@Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
enforceTaskPermission("startTransition()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
Transition transition = Transition.fromBinder(transitionToken);
// ...
final WindowContainerTransaction wct =
t != null ? t : new WindowContainerTransaction();
// ...
// ReadyContdition 用来检测是 transition ready 的条件
final Transition.ReadyCondition wctApplied;
if (t != null) {
wctApplied = new Transition.ReadyCondition("start WCT applied");
transition.mReadyTracker.add(wctApplied);
} else {
}
// ...
if (transition.shouldApplyOnDisplayThread()) { // 没有 display change
// ...
} else {
// 1. start transition
transition.start();
// 2. apply WCT
applyTransaction(wct, -1 /* syncId */, transition, caller);
// 3. 用 Transition.ReadyCondition 标记 transition ready 条件已经满足
if (wctApplied != null) {
wctApplied.meet();
}
}
// transition token 返回给 WMShell
return transition.getToken();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
由于传入的 WCT 是空数据,因此 WMCore 就是让 Transition 执行 start 流程,主要就是标记 Transition 状态为 STATE_STARTED。
// Transition.java
void start() {
// ...
// 标记 Transition 状态为 STATE_STARTED
mState = STATE_STARTED;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
mSyncId);
// 此时还没有 transition ready
applyReady();
mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos();
mController.updateAnimatingState();
}
transition ready
Transition start 后,就需要等待一个契机,进入 transition ready。transition ready 后,就可以开始检测 SyncGroup 收集的 WC 的其下窗口,是否都重绘完成。如果都重绘完成,那么就可以开始执行动画。
对于本文分析的案例,启动窗口绘制完成,就是进入 transition ready 的契机之一,但不一定能成功进入 transition ready。
当启动窗口绘制完成后,ViewRootImpl 会通知 WMS,如下
// WindowManagerService.java
// postDrawTransaction 携带了 view 绘制的 buffer
void finishDrawingWindow(Session session, IWindow client,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
// ...
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
// 获取对应的 WindowState
WindowState win = windowForClientLocked(session, client, false);
ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
// 1.有 WindowState 处理窗口绘制完成
if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
if (win.hasWallpaper()) {
}
// 2.标记 DisplayContent 需要 layout,并请求窗口刷新
win.setDisplayLayoutNeeded();
mWindowPlacerLocked.requestTraversal();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
在 ViewRootImpl 中,窗口的第一次绘制,是需要向 WMS 报告绘制完成的。
如果使用硬件渲染来绘制,再加上需要上报绘制完成,那么 ViewRootImpl 会向 ThreadedRender 注册一个监听绘制完成的回调。当回调执行时,ViewRootImpl 会得到一个 Transaction ,Transaction 包含了绘制的 Buffer。之后,ViewRootImpl 会通知 WMS 绘制完成,并传入 Transaction 参数。
也就是说,渲染线程完成绘制后,并没有立即 apply transaction。因此,绘制的内容并没有立即显示。
在 WMS 端,由代表窗口的 WindowState 处理窗口绘制完成
// WindowState.java
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {
// ...
boolean skipLayout = false;
boolean layoutNeeded = false;
// Control the timing to switch the appearance of window with different rotations.
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
if (asyncRotationController != null
&& asyncRotationController.handleFinishDrawing(this, postDrawTransaction)) {
// 处理异步旋转的窗口 ...
} else if (syncActive) {
// Currently in a Sync that is using BLAST.
} else if (syncNextBuffer()) {
// Sync that is not using BLAST
// 1. 同步状态 mSyncState 切换到 SYNC_STATE_READY
layoutNeeded = onSyncFinishedDrawing();
}
// 2. 绘制状态 mDrawState 切换到 COMMIT_DRAW_PENDING
layoutNeeded |= mWinAnimator.finishDrawingLocked(postDrawTransaction);
// 返回 true
// 表示需要发起一次窗口刷新
return !skipLayout && (hasSyncHandlers || layoutNeeded);
}
// WindowState.java
boolean onSyncFinishedDrawing() {
if (mSyncState == SYNC_STATE_NONE) return false;
// 同步状态,切换到 SYNC_STATE_READY
mSyncState = SYNC_STATE_READY;
mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this);
return true;
}
// WindowStateAnimator.java
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
final boolean startingWindow =
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finishing drawing window %s: mDrawState=%s",
mWin, drawStateToString());
}
boolean layoutNeeded = false;
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
mSurfaceController);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
// 绘制状态切换到 COMMIT_DRAW_PENDING
mDrawState = COMMIT_DRAW_PENDING;
layoutNeeded = true;
}
// postDrawTransaction 携带了 app 绘制的 buffer
// WindowState 的 sync transaction 合并 postDrawTransaction
if (postDrawTransaction != null) {
mWin.getSyncTransaction().merge(postDrawTransaction);
layoutNeeded = true;
}
// 返回 true
return layoutNeeded;
}
WindowState 处理 finish drawing
- 把同步状态切换到 SYNC_STATE_READY。
- 把绘制状态切换到 COMMIT_DRAW_PENDING。
- 把携带 View 绘制 Buffer 的 Transaction,合并到 WindowState 的 sync transaction 中。
WindowState 处理 finish drawing 后,标记 DisplayContent 需要 layout,并请求一次窗口刷新。
窗口刷新
此次窗口刷新的目的,就是为了处理窗口绘制完成,看看是否能 show 窗口 surface,如下
// RootWindowContainer.java
void performSurfacePlacementNoTrace() {
// ...
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
try {
applySurfaceChangesTransaction();
}
// ...
}
private void applySurfaceChangesTransaction() {
final int count = mChildren.size();
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
// DisplayContent 处理 surface change
dc.applySurfaceChangesTransaction();
// ...
}
// ...
}
// DisplayContent.java
void applySurfaceChangesTransaction() {
// ...
// 1. layout
performLayout(true /* initial */, false /* updateInputWindows */);
// ...
try {
// 2. apply surface change
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (!com.android.window.flags.Flags.removePrepareSurfaceInPlacement()) {
// 3. prepare surface
prepareSurfaces();
}
// ...
}
apply surface change 会处理窗口绘制完成,prepare surface 会根据情况,决定是否 show 窗口 surface。
apply surface change
// DisplayContent.java
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
// ...
if (w.mHasSurface) { // relayout 后,是有 surface 的
// commit finish drawing
final boolean committed = w.mWinAnimator.commitFinishDrawingLocked();
// ...
}
// ...
};
// WindowStateAnimator.java
boolean commitFinishDrawingLocked() {
if (DEBUG_STARTING_WINDOW_VERBOSE &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+ drawStateToString());
}
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
mSurfaceController);
// 绘制状态切换到 READY_TO_SHOW
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
// 启动窗口类型,执行下一步的 perform show
if (activity == null || activity.canShowWindows()
|| mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = mWin.performShowLocked();
}
return result;
}
// WindowState.java
boolean performShowLocked() {
// ...
// 此时绘制状态是 READY_TO_SHOW
final int drawState = mWinAnimator.mDrawState;
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
// ...
} else {
// 通知 ActivityRecord 启动窗口绘制完成
mActivityRecord.onStartingWindowDrawn();
}
}
// 在 isReadyForDisplay() 中,检测了 ActivityRecord#mVisible ,由于它为还是 false
// 因此,isReadyForDisplay() 返回 false
if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
// 注意,到此结束了
return false;
}
// ...
// 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();
// ...
return true;
}
apply surface change 处理启动窗口绘制完成,把绘制完成的启动窗口的绘制状态,从 COMMIT_DRAWING_PENDING 切换到 READY_TO_SHOW,但是并没有切换到 HAS_DRAWN。
apply surfce change 另外还通知了 ActivityRecord 启动窗口绘制完成,其中
// ActivityRecord.java
void onStartingWindowDrawn() {
boolean wasTaskVisible = false;
if (task != null) {
mSplashScreenStyleSolidColor = true;
// Task#mHasBeenVisible 在 Task 构造函数中初始为 false
// 这里把 Task#mHasBeenVisible 设置为 true,并返回旧值,也就是 false
wasTaskVisible = !setTaskHasBeenVisible();
}
// 注意,这里检测了 ActivityRecord 的 mVisibleRequested 必须为 true
if (!wasTaskVisible && mStartingData != null && !finishing && !mLaunchedFromBubble
&& mVisibleRequested && !mDisplayContent.mAppTransition.isReady()
&& !mDisplayContent.mAppTransition.isRunning()
// 只针对 TRANSIT_OPEN 和 TRANSIT_TO_FRONT 动画
&& mDisplayContent.isNextTransitionForward()) {
mStartingData.mIsTransitionForward = true;
// 当前 ActivityRecord 就是要启动的 activity,它就是 orientation source
if (this != mDisplayContent.getLastOrientationSource()) {
}
// 虽然,app transition 已经废弃,但是这个方法中,会标记 transition ready
mDisplayContent.executeAppTransition();
}
}
// DisplayContent.java
void executeAppTransition() {
// set transition ready
mTransitionController.setReady(this);
if (mAppTransition.isTransitionSet()) {
// ...
}
}
ActivityRecord 得知启动窗口绘制完成后,会标记 transition ready。但是,有很多条件,其中非常关键的一个条件是 ActivityRecord#mVisibleRequested 必须为 true。
当 app 端完成 pause activity,会触发第二阶段启动,在其更新 Activity 可见性中,会把待启动的 ActivityRecord 的 mVisibleRequested 更新为 true。
触发 transition ready 的条件,其实只有一个,那就是 WMCore 侧,不再有事务需要处理,这些事务包含 Activity 生命周期调度、配置更新,等等。
启动窗口绘制完成时,如果第二阶段启动完成,就可以标记 transiton ready,而不用等待第三阶段启动。在我个人看来,是了减少用户等待 app 启动的延迟,让用户尽快看到启动窗口。而动画是在 Launcher 执行的,它与第三阶段启动是异步的,它们可以同时进行。当第三阶段启动完成,并且 app 端完成 Activity 真窗的绘制,就可以立即 show 真窗 surface。
那么,有人可能有疑问,如果启动窗口绘制完成时,第二阶段启动还没有完成,那岂不是永远不能进入 transiton ready 状态?其实不然,在第三阶段启动中,完成 Activity resume 的调度后,此时 WMCore 没有需要处理的事务了,就可以名正言顺地标记 transition ready,如下
// ActivityTaskSupervisor.java
boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
boolean andResume, boolean checkConfig) throws RemoteException {
// ...
try {
// ...
try {
// ...
// 调度 app 端启动 activity
mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
proc.getThread(), launchActivityItem, lifecycleItem,
// Immediately dispatch the transaction, so that if it fails, the server can
// restart the process and retry now.
true /* shouldDispatchImmediately */);
// ...
}
}
// ...
if (andResume && readyToResume()) {
// ActivityRecord 状态更新到 RESUMED
r.setState(RESUMED, "realStartActivityLocked");
// 不需要等待 app 端反馈 activity resumed 完成,服务端直接 complete resumed
r.completeResumeLocked();
}
// ...
return true;
}
// ActivityRecord.java
/**
* Once we know that we have asked an application to put an activity in the resumed state
* (either by launching it or explicitly telling it), this function updates the rest of our
* state to match that fact.
*/
void completeResumeLocked() {
// ..
mTaskSupervisor.reportResumedActivityLocked(this);
// ...
}
// ActivityTaskSupervisor.java
boolean reportResumedActivityLocked(ActivityRecord r) {
mStoppingActivities.remove(r);
// 要启动 activity 的 root task
final Task rootTask = r.getRootTask();
// 检测 root task 所属的 DisplayArea 下 top resumed activity 的状态是否已经切换到 RESUMED
// 在通知 app 端启动 activity 后,接着把就 ActivityRecord 状态切换到 RESUMED
if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
mRootWindowContainer.ensureActivitiesVisible();
// 虽然 app transition 功能已经废弃,但是夹杂了 shell transition 的代码
mRootWindowContainer.executeAppTransitionForAllDisplay();
return true;
}
return false;
}
// RootWindowContainer.java
void executeAppTransitionForAllDisplay() {
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
display.mDisplayContent.executeAppTransition();
}
}
// DisplayContent.java
void executeAppTransition() {
// 标记 transition ready
mTransitionController.setReady(this);
if (mAppTransition.isTransitionSet()) {
}
}
prepare surface
由于 apply surface change 只把启动窗口的绘制状态切换到 READY_TO_SHOW,因此在 prepare surface 中无法 show 启动窗口 surface,如下
// DisplayContent.java
void prepareSurfaces() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
try {
// DisplayContent 通过基类 WindowContainer 的方法执行 prepare surface
super.prepareSurfaces();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
// WindowContainer.java
void prepareSurfaces() {
mCommittedReparentToAnimationLeash = mSurfaceAnimator.hasLeash();
// 递归地让所有 chidlren 都执行 prepare surface
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).prepareSurfaces();
}
}
直接来看 WindowState 的 prepare surface
// WindowState.java
void prepareSurfaces() {
mIsDimming = false;
if (mHasSurface) {
// ...
// 由 WindowStateAnimator 来处理 prepare surface
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
// ...
}
super.prepareSurfaces();
}
// WindowStateAnimator.java
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
// ...
if (!w.isOnScreen()) {
} else if (mLastAlpha != mShownAlpha
|| mLastHidden) { // mLastHidden 在 relayout 创建 surface 时,设置为 true
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
boolean prepared =
mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
// 注意,只有绘制状态为 HAS_DRAWN,才能 show app 绘制的 surface
if (prepared && mDrawState == HAS_DRAWN) {
if (mLastHidden) {
// WindowSurfaceController 是用来控制 app 绘制 surface 的
// 这里用它来 show 窗口 surface
mSurfaceController.showRobustly(t);
mLastHidden = false;
// ...
}
}
}
// ...
}
很可惜,由于启动窗口的绘制状态,目前并不是 HAS_DRAWN,因此 prepare surface 不能 show 启动窗口 surface。
那么,什么时候才 show 启动窗口 surface?在 Transition 动画开始执行前,后面的分析会讲到。
检测窗口重绘
transition ready 会触发一次窗口刷新,如下
// TransitionController.java
void setReady(WindowContainer wc, boolean ready) {
if (mCollectingTransition == null) return;
mCollectingTransition.setReady(wc, ready);
}
// Transition.java
void setReady(WindowContainer wc, boolean ready) {
if (!isCollecting() || mSyncId < 0) return;
// 保存 transition ready 状态
mReadyTrackerOld.setReadyFrom(wc, ready);
applyReady();
}
class ReaderTrackerOld {
void setReadyFrom(WindowContainer wc, boolean ready) {
mUsed = true;
WindowContainer current = wc;
while (current != null) {
// ready-group 就是 DisplayContent
if (isReadyGroup(current)) {
// mReadyGroups 保存 ready-group 状态
mReadyGroups.put(current, ready);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
+ " %b. group=%s from %s", ready, current, wc);
break;
}
current = current.getParent();
}
}
}
private void applyReady() {
if (mState < STATE_STARTED) return;
final boolean ready;
// 获取 transition ready 状态,无论走那一条路,都是 true
if (mController.useFullReadyTracking()) {
ready = mReadyTracker.isReady();
} else {
ready = mReadyTrackerOld.allReady();
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
// 通过 BLASTSyncEngine 设置给 SyncGroup
boolean changed = mSyncEngine.setReady(mSyncId, ready);
// ...
}
// BLASTSyncEngine.java
class SyncGroup {
private boolean setReady(boolean ready) {
if (mReady == ready) {
return false;
}
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
// 保存 transition ready 状态
mReady = ready;
if (ready) {
// 请求窗口刷新
mWm.mWindowPlacerLocked.requestTraversal();
}
return true;
}
}
transition ready 触发的窗口刷新,是为了检测 SyncGroup 中 WC 是否都重绘完成,如果都重绘完成,就可以真正的开始执行 Transition 动画了。
// RootWindowContainer.java
void performSurfacePlacementNoTrace() {
// ...
try {
// apply surface change 处理了启动窗口绘制完成
applySurfaceChangesTransaction();
}
// ...
// 检测动画是否可以执行了
mWmService.mSyncEngine.onSurfacePlacement();
}
// BLASTSyncEngine.java
void onSurfacePlacement() {
if (mActiveSyncs.isEmpty()) return;
// queue in-order since we want interdependent syncs to become ready in the same order they
// started in.
mTmpFinishQueue.addAll(mActiveSyncs);
// 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;
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) {
// ...
}
}
}
class SyncGroup {
private boolean tryFinish() {
// 恰好已经标记过 transition ready
// 它说明了,如果没有 transition ready,是无法执行动画的
if (!mReady) return false;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
mSyncId, mRootMembers);
if (!mDependencies.isEmpty()) {
}
// 1. 检测 WC 是否都同步完成
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);
return false;
}
}
// 2. finish SyncGroup
finishNow();
return true;
}
根据前面的分析,SyncGroup 保存了 4 个 WC,分别是要启动的 ActivityRecord 及其 Task、launcher ActivityRecord、WallpaperWindowToken。
但是,并不是所有 WC 下的所有窗口都需要重绘。Launcher ActivityRecord 不可见,因为不需要检测其下窗口是否重绘完成,如下
// ActivityRecord.java
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
// ...
if (!super.isSyncFinished(group)) return false;
// ...
// activity 不可见,代表同步完成,即不需要它下面的窗口重绘
if (!isVisibleRequested()) return true;
}
WallpaperWindowToken 有自己独特的检测同步完成方法,如下
// WallpaperWindowToken.java
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
// 壁纸不可见,代表同步完成,即壁纸窗口不需要重绘
// 壁纸可见,但是有没有绘制完成的壁纸窗口,代表同步没有完成,即还需要等待壁纸窗口重绘
return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
}
boolean hasVisibleNotDrawnWallpaper() {
if (!isVisible()) return false;
for (int j = mChildren.size() - 1; j >= 0; --j) {
final WindowState wallpaper = mChildren.get(j);
// 如果有壁纸窗口可见,但是没有绘制完成,返回 true
if (!wallpaper.isDrawn() && wallpaper.isVisible()) {
return true;
}
}
return false;
}
WallpaperWindowToken 此时还是可见的。因为 WallpaperWindowToken 的可见性更新,依赖 wallpaper target 的可见性更新,对于本文分析的案例,wallpaper target 就是 Launcher ActivityRecord。在 Transition 结束时,提交了 Launcher ActivityRecord 不可见后,才正式提交 WallpaperWindowToken 的不可见。
另外,窗口创新也并没有触发壁纸窗口重绘,那么壁纸窗口此时还是处于绘制完成的状态。因此,WallpaperWindowToken 此时处于同步完成的状态。
现在就剩下待启动的 ActivityRecord 及其 Task,由于启动窗口绘制完成,因此它们都是处于同步完成的状态。
现在 SyncGroup 保存的 WC 都同步完成,直接 finish SyncGroup
// BLASTSyncEngine.java
class SyncGroup
private void finishNow() {
if (mTraceName != null) {
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId);
}
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
// 创建一个 Transaction
SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
if (mOrphanTransaction != null) {
}
// 1. WC finish sync
// 收集处于同步状态的 WC 及其 children 的 sync transaction
for (WindowContainer wc : mRootMembers) {
wc.finishSync(merged, this, false /* cancel */);
}
// 2. 收集等待 sync tranasction 提交的 WC
// WC 及其 children,都会被收集到参数 wcAwaitingCommit
final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
for (WindowContainer wc : mRootMembers) {
wc.waitForSyncTransactionCommit(wcAwaitingCommit);
}
final int syncId = mSyncId;
final long mergedTxId = merged.getId();
final String syncName = mSyncName;
class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
public void onCommitted(SurfaceControl.Transaction t) {
// Don't wait to hold the global lock to remove the timeout runnable
mHandler.removeCallbacks(this);
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
}
ran = true;
// 对于那些等待 sync transaction 提交的 WC
// 再次收集它们的 sync transaction
for (WindowContainer wc : wcAwaitingCommit) {
wc.onSyncTransactionCommitted(t);
}
// apply transaction
t.apply();
wcAwaitingCommit.clear();
}
}
// Called in timeout
@Override
public void run() {
// ...
}
};
CommitCallback callback = new CommitCallback();
// 3.监听 sync transaction 提交
merged.addTransactionCommittedListener(Runnable::run,
() -> callback.onCommitted(new SurfaceControl.Transaction()));
mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
// 4.通知 Transition,transaction ready
mListener.onTransactionReady(mSyncId, merged);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
mActiveSyncs.remove(this);
mHandler.removeCallbacks(mOnTimeout);
// ...
}
}
finish SyncGroup,主要是对其收集的 WC 进行操作
- WC finish sync,其实就是收集处于同步状态的 WC 及其 children 的 sync transaction。
- 收集 WC 及其 chidlren,它们都需要等待 sync transaction 提交。
- 收集的 sync transaction 会被发送到 WMShell,因此必须保证,当 sync transaction apply 时,必须同步 apply WMCore 侧 WC 的 sync transaction。这就需要 WMCore 监听 sync transaction 的提交。
- 通知 Transition,transaction ready,同时传入收集的 sync transaction。
解答疑惑
现在可以解答 Android V app 冷启动(2) 窗口层级的构建 提出的一个问题:待启动 ActivityRecord 和其 Task 的 surface,是何时 show 出来?
// RootWindowContainer.java
void performSurfacePlacementNoTrace() {
// ...
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
try {
// 1. apply surface change
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
// ...
// 2. 检测 Transition 就绪
mWmService.mSyncEngine.onSurfacePlacement();
// ...
// 3. 调度动画线程
mWmService.scheduleAnimationLocked();
}
apply surface change 时,处理启动窗口绘制完成,只把启动窗口的绘制状态,切换到 READY_TO_SHOW。并且在 prepare surface 时,由于 ActivityRecord#mVisible 还是 false,导致无法 show ActivityRecord 和 Task surface。
Transition 就绪时,提交了待启动 ActivityRecord 的可见,把 ActivityRecord#mVisible 更新为 true。但是待启动的 ActivityRecord 及其 Task surface 仍然处于 hidden 状态,那么执行 Transition 动画时,启动窗口是不可见的。
调度动画线程,执行一些与动画相关的操作,例如,prepare surface,如下
// WindowAnimator.java
private void animate(long frameTimeNs) {
// ...
try {
// ...
final int numDisplays = root.getChildCount();
for (int i = 0; i < numDisplays; i++) {
final DisplayContent dc = root.getChildAt(i);
dc.updateWindowsForAnimator();
// 执行 DisplayContent prepare surface
dc.prepareSurfaces();
}
// ...
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
}
// ...
}
根据前面的分析,prepare surface,会尝试 show ActivityRecord 和 Task surface,而此时待启动的 ActivityRecord#mVisible 为 true,因此它们的 surface 都会 show 出来。