忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
-- 服装学院的IT男
本文介绍桌面启动应用场景出现的第二部分的动画:“starting_reveal” 它是展示动画,这个动画的出现说明应用 Window 已经绘制好,需要展示给用户了。出现这个动画也表示需要移除 StartWindow 了,移除 StartWindowh 会有个 “window_transition”动画,也就是整个启动应用的第三个动画。这个在StartWindow-SplashScreen介绍介绍。
本篇分析的的是应用 Window 要显示的动画 “starting_reveal”, 这个动画结束后才是 StartWindow 的移除。
在SurfaceAnimator::createAnimationLeash打印的当前动画创建的堆栈信息如下:
10-21 22:46:29.378 27972 27993 E biubiubiu: SurfaceAnimator createAnimationLeash: starting_reveal
10-21 22:46:29.378 27972 27993 E biubiubiu: java.lang.Exception
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2770)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2777)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2783)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.TaskOrganizerController.applyStartingWindowAnimation(TaskOrganizerController.java:490)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.TaskOrganizerController.removeStartingWindow(TaskOrganizerController.java:546)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.StartingSurfaceController$StartingSurface.remove(StartingSurfaceController.java:267)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.ActivityRecord.lambda$removeStartingWindowAnimation$6(ActivityRecord.java:2774)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.ActivityRecord$$ExternalSyntheticLambda9.run(Unknown Source:6)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.ActivityRecord.removeStartingWindowAnimation(ActivityRecord.java:2780)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.ActivityRecord.removeStartingWindow(ActivityRecord.java:2715)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.ActivityRecord.onFirstWindowDrawn(ActivityRecord.java:6438)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowState.performShowLocked(WindowState.java:4708)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked(WindowStateAnimator.java:282)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.DisplayContent.lambda$new$8$com-android-server-wm-DisplayContent(DisplayContent.java:995)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.DisplayContent$$ExternalSyntheticLambda14.accept(Unknown Source:4)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2642)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2632)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4976)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4820)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1646)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.DisplayContent.applySurfaceChangesTransaction(DisplayContent.java:4700)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:1027)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:828)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
10-21 22:46:29.378 27972 27993 E biubiubiu: at android.os.Handler.handleCallback(Handler.java:942)
10-21 22:46:29.378 27972 27993 E biubiubiu: at android.os.Handler.dispatchMessage(Handler.java:99)
10-21 22:46:29.378 27972 27993 E biubiubiu: at android.os.Looper.loopOnce(Looper.java:210)
10-21 22:46:29.378 27972 27993 E biubiubiu: at android.os.Looper.loop(Looper.java:297)
10-21 22:46:29.378 27972 27993 E biubiubiu: at android.os.HandlerThread.run(HandlerThread.java:67)
10-21 22:46:29.378 27972 27993 E biubiubiu: at com.android.server.ServiceThread.run(ServiceThread.java:44)
整理后调用链如下:
RootWindowContainer::performSurfacePlacementNoTrace
RootWindowContainer::applySurfaceChangesTransaction
DisplayContent::applySurfaceChangesTransaction
DisplayContent::mApplySurfaceChangesTransaction
WindowStateAnimator::commitFinishDrawingLocked -- READY_TO_SHOW
WindowState::performShowLocked -- HAS_DRAWN
ActivityRecord::onFirstWindowDrawn -- 移除StartWindow,开始starting_reveal动画流程
ActivityRecord::removeStartingWindow
ActivityRecord::removeStartingWindowAnimation
StartingSurfaceController.StartingSurface::remove
TaskOrganizerController::removeStartingWindow
TaskOrganizerController::applyStartingWindowAnimation -- leash图层相关
WindowContainer::startAnimation
WindowContainer::startAnimation
WindowContainer::startAnimation
SurfaceAnimator::startAnimation --创建leash图层
SurfaceAnimator.createAnimationLeash
StartingWindowAnimationAdaptor::startAnimation
ITaskOrganizer.removeStartingWindow -- 跨进程通知SystemUI处理( ShellTaskOrganizer 远端动画)
这个调用链看前面部分还是很熟悉的,就 finishDrawingWindow 流程。 在执行 WindowStateAnimator.commitFinishDrawingLocked 和 WindowState::performShowLocked 一次把应用窗口的状态设置成 READY_TO_SHOW 和 HAS_DRAWN ,也就意味着应用窗口绘制完成了,那这个时候就可以把 StartWindow 移除了。于是会触发 ActivityRecord::onFirstWindowDrawn 方法,之后的逻辑就是 StartWindow 的移除流程。
1. system_server 阶段
StartWindow 的移除动画是在 SystemUI 执行的,所以 system_server 阶段做的事是创建相应的动画图层,而不会触发动画的执行。
1.1 应用窗户绘制完成状态设置
从WindowStateAnimator::commitFinishDrawingLocked 开始看。
# WindowStateAnimator
boolean commitFinishDrawingLocked() {
......
// mWin的打印是自己加的
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s mWin= %s",
mSurfaceController,mWin);
// 重点* finishDrawing设置状态为READY_TO_SHOW
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
// 重点* 这里是条件canShowWindows满足,才会进入
if (activity == null || activity.canShowWindows()
|| mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = mWin.performShowLocked();
}
return result;
}
# ActivityRecord
boolean canShowWindows() {
// allDrawn 是关键,在DisplayContent::applySurfaceChangesTransaction执行满足条件会置位true
return allDrawn && !(isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)
&& hasNonDefaultColorWindow());
}
这里需要注意的是当前的 windowState 的应用的,这里进入后续的条件是“activity.canShowWindows()”返回true,那么继续看 WindowState 的处理
# WindowState
boolean performShowLocked() {
......
final int drawState = mWinAnimator.mDrawState;
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
// remonve startWindow 流程
mActivityRecord.onFirstWindowDrawn(this);
} else {
mActivityRecord.onStartingWindowDrawn();
}
}
// 判断当前状态是否满足
if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
return false;
}
......
ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
mWinAnimator.mDrawState = HAS_DRAWN;
......
}
这个方法如果条件满足还会执行将窗口状态设置为 HAS_DRAWN ,不过之前分析过了当前主要看 remove startWindow 的流程,走的是 ActivityRecord::onFirstWindowDrawn 这里可能会有人会有误解:以为当前是 TYPE_APPLICATION_STARTING 类型应该走下面,这里要注意的是,执行当前调用链的windowState是 应用的 WindowState, 并不是 startWindow的WindowSate。
1.2 开始触发移除StartWindow
下面就是开始触发移除 StartWindow 的逻辑了。
# ActivityRecord
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
......
final Task associatedTask =
mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
// 根据之前对add流程分析,这里的associatedTask就是null
if (associatedTask == null) {
// 触发removeStartingWindow
removeStartingWindow();
} else ......
}
void removeStartingWindow() {
......
// 移除动画,参数为true
removeStartingWindowAnimation(true /* prepareAnimation */);
......
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
......
// 定义局部变量
final StartingSurfaceController.StartingSurface surface;
// mStartingData在add流程的时候创建的
final StartingData startingData = mStartingData;
if (mStartingData != null) {
// 赋值
surface = mStartingSurface;
// 然后mStartingData就设置为null
mStartingData = null;
......
} else ......
// 打印日志
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
+ " startingView=%s Callers=%s", this, mStartingWindow, mStartingSurface,
Debug.getCallers(5));
// 这里主要看内部的执行就好
final Runnable removeSurface = () -> {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);
try {
// 移除 startWindow
surface.remove(prepareAnimation && startingData.needRevealAnimation());
} catch (Exception e) {
Slog.w(TAG_WM, "Exception when removing starting window", e);
}
};
removeSurface.run();
}
这里调用 StartingSurfaceController.StartingSurface::remove ,参数是个 bool 值, prepareAnimation 根据调用过来的知道是为 true ,另外一个变量,根据之前的分析知道 startingData 实际上是 SplashScreenStartingData ,而在 SplashScreenStartingData 中 needRevealAnimation 返回的是 true 。 这块值的代码如下:
# needRevealAnimation
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
activity, mTheme);
}
@Override
boolean needRevealAnimation() {
// 注意为true
return true;
}
那么知道参数为 true 后继续看 remove 方法。
# StartingSurfaceController.StartingSurface
public void remove(boolean animate) {
synchronized (mService.mGlobalLock) {
mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
}
}
这里的 Task 自然就是应用的 Task ,然后调用到 TaskOrganizerController::removeStartingWindow
# TaskOrganizerController
void removeStartingWindow(Task task, boolean prepareAnimation) {
......
// 稍后跨进程通信
final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
if (lastOrganizer == null) {
return;
}
// 构建一个StartingWindowRemovalInfo的对象,然后填充数据,携带leash图层跨进程执行动画
final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
removalInfo.taskId = task.mTaskId;
removalInfo.playRevealAnimation = prepareAnimation
&& task.getDisplayInfo().state == Display.STATE_ON;
final boolean playShiftUpAnimation = !task.inMultiWindowMode();
// 拿到topActivity
final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
if (topActivity != null) {
removalInfo.deferRemoveForIme = topActivity.mDisplayContent
.mayImeShowOnLaunchingActivity(topActivity);
if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
final WindowState mainWindow =
topActivity.findMainWindow(false/* includeStartingApp */);
if (mainWindow != null) {
// 重点* 1. 创建starting_reveal动画
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
}
}
try {
// 重点* 2. 跨进程调用到systemUI 进程执行后续remove流程
lastOrganizer.removeStartingWindow(removalInfo);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
}
}
这个方法就比较重要了,做了2件事:
-
- 在当前进程(system_server)创建动画的leash图层,并且把这个leash 保存到 removalInfo 变量下,后续传递到了 SystemUI 进程做动画
-
- 跨进程通知 SystemUI 进程开始执行移除动画
1.3 创建leash图层
# TaskOrganizerController
static SurfaceControl applyStartingWindowAnimation(WindowState window) {
final SurfaceControl.Transaction t = window.getPendingTransaction();
final Rect mainFrame = window.getRelativeFrame();
// 注意adapter
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
// 是activity的WindowState,注意这个 ANIMATION_TYPE_STARTING_REVEAL 创建的是start_reveal动画
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
return adaptor.mAnimationLeash;
}
创建 StartingWindowAnimationAdaptor 的时候,是没传参数的,但是后面看的调用了 adaptor.mAnimationLeash,那么这个 mAnimationLeash 的赋值肯定是在中间这句WindowState::startAnimation 的流程,根据之前的流程知道实现是在 WindowContainer 中
# WindowContainer
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
// 注意最后一个参数为null
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
// 后面2个个参数为null
startAnimation(t, anim, hidden, type, animationFinishedCallback,
null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
}
// 调用到这,后面3个参数都为null
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim) {
// 打印log
ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
this, type, anim);
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
// 倒数第二个参数传过来是null
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
这一段 WindowContainer::startAnimation 的调用链也是动画里很常见的。
通过几次重载,最后执行的是 7个函数的方法,其中后面3个参数传递是null。其实动画结束和取消的回调都是null,由此可以猜测在 system_server 进程对 StartWindow 动画结束的回调并不关系。
后面就是和之前 app_transition 动画一样调用到 SurfaceAnimator 了,这个方法和应用启动动画也是一个套路,创建动画图层,然后执行 adapter 的 startAnimation 。
# SurfaceAnimator
private AnimationAdapter mAnimation;
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
......
if (mLeash == null) {
mLeash = createAnimationLeash(mAnimatable, surface, t, type,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
0 /* y */, hidden, mService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
......
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
......
}
这里创建 leash 图层以前说过,但是需要注意2点:
-
- 这个时候的type是上面代码传递过来的 ANIMATION_TYPE_STARTING_REVEAL ,所以返回的动画类型是 “starting_reveal“
-
- 这里并没有执行提升动画层级的方法,所以执行 leash 图层的下级是 应用的 WindowState(根据 winscope 的信息也是如此)
根据前面的信息,这里的 mAnimation 是 StartingWindowAnimationAdaptor ,然后创建
# StartingWindowAnimationAdaptor
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
mAnimationLeash = animationLeash;
}
只做了一件事,将创建的 leash 赋值到 mAnimationLeash ,不过根据前面的调用也确实够了,要的就是这句,后面的流程就要进入 SystemUI 进程执行了。
2. SystemUI进程执行远端动画
在 system_server 进程执行 TaskOrganizerController::removeStartingWindow 方法时,会先触发leash图层相关的对象创建,然后一切都准备好了后,会执行 ITaskOrganizer::removeStartingWindow 来通知 SystemUI 进程执行远端动画。 在 SystemUI 进程的实现类是 ShellTaskOrganizer 。
这部分的调用链如下:
TaskOrganizer.mInterface::removeStartingWindow
ShellTaskOrganizer::removeStartingWindow
StartingSurfaceDrawer::removeStartingWindow
StartingSurfaceDrawer::removeWindowSynced
SplashscreenContentDrawer::applyExitAnimation
SplashScreenExitAnimation::startAnimations
SplashScreenExitAnimation::createAnimator --创建动画
SplashScreenExitAnimation::onAnimationProgress --动画更新
SplashScreenExitAnimation::onAnimationEnd --动画结束
SplashScreenExitAnimation::reset --触发动画结束回调,执行remove StartWindow流程
StartingSurfaceDrawer::removeWindowInner
ValueAnimator::start --开始动画
# TaskOrganizer
private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
@Override
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(removalInfo));
}
}
实现在 ShellTaskOrganizer
# ShellTaskOrganizer
private StartingWindowController mStartingWindow;
@Override
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
if (mStartingWindow != null) {
mStartingWindow.removeStartingWindow(removalInfo);
}
}
# StartingWindowController
private final StartingSurfaceDrawer mStartingSurfaceDrawer;
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
removalInfo));
......
}
后续流程在 StartingSurfaceDrawer::removeStartingWindow 下,这里也会打印 StartWindow 的一个日志。
# StartingSurfaceDrawer
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
// 重点* 日志
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Task start finish, remove starting surface for task: %d",
removalInfo.taskId);
// 第二个参数为false
removeWindowSynced(removalInfo, false /* immediately */);
}
protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
final int taskId = removalInfo.taskId;
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
if (record != null) {
if (record.mDecorView != null) {
// 重点 * 关键日志
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing splash screen window for task: %d", taskId);
if (record.mContentView != null) {
record.clearSystemBarColor();
if (immediately
|| record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
removeWindowInner(record.mDecorView, false);
} else {
// 注意,immediately为false,进入的是这个逻辑,playRevealAnimation前面看到了是为true
if (removalInfo.playRevealAnimation) {
// 重点 * 执行退出动画,注意这几个参数
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
record.mCreateTime);
} else {
removeWindowInner(record.mDecorView, true);
}
}
} else {
// shouldn't happen
Slog.e(TAG, "Found empty splash screen, remove!");
removeWindowInner(record.mDecorView, false);
}
}
......
}
}
注意这里有2个 StartWindow 的关键日志打印,然后有4个地方进入 removeWindowInner 方法,但是当前逻辑走的是第二个,playRevealAnimation 为 true 的这个。
看 applyExitAnimation 方法之前需要注意这几个参数:
- record.mContentView :这个就是 Splashscreen 的 ContentView
- removalInfo.windowAnimationLeash : 是跨进程传递的对象,这个是 leash 图层
- removeWindowInner(record.mDecorView, true) : 动画结束回调,内部移除 StartWindow ,也就是说在执行完 StartWindow 移除动画后才会移除 StartWindow 的窗口。
# SplashscreenContentDrawer
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
Rect frame, Runnable finishCallback, long createTime) {
// 构建了一个Runnable
final Runnable playAnimation = () -> {
// 注意,动画结束的回调传到了SplashScreenExitAnimation中
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
animation.startAnimations();
};
// 这个肯定是有值的。根据流程或者只根据会打印下面的log都可以确定
if (view.getIconView() == null) {
playAnimation.run();
return;
}
final long appReadyDuration = SystemClock.uptimeMillis() - createTime;
final long animDuration = view.getIconAnimationDuration() != null
? view.getIconAnimationDuration().toMillis() : 0;
final long minimumShowingDuration = getShowingDuration(animDuration, appReadyDuration);
final long delayed = minimumShowingDuration - appReadyDuration;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"applyExitAnimation delayed: %s", delayed);
// 根据log 值为0,没有延迟,所以直接执行
if (delayed > 0) {
view.postDelayed(playAnimation, delayed);
} else {
playAnimation.run();
}
}
这里看到后面主要的逻辑都在 SplashScreenExitAnimation 中了, 这个类的名字也很明显。
2.2 动画核心类 - SplashScreenExitAnimation
先看构造方法
# SplashScreenExitAnimation
// SplashScreenExitAnimation是一个动画监听
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final SplashScreenView mSplashScreenView;
// 动画的leash图层
private final SurfaceControl mFirstWindowSurface;
// 动画结束回调
private Runnable mFinishCallback;
// 构造方法
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
// 拿到图标
View iconView = view.getIconView();
// 判断是否执行执行图标动画,以及一些参数的处理
// If the icon and the background are invisible, don't animate it
if (iconView == null || iconView.getLayoutParams().width == 0
|| iconView.getLayoutParams().height == 0) {
mIconFadeOutDuration = 0;
mIconStartAlpha = 0;
mBrandingStartAlpha = 0;
mAppRevealDelay = 0;
} else {
iconView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// The branding view could only exists when the icon is present.
final View brandingView = view.getBrandingView();
if (brandingView != null) {
mBrandingStartAlpha = brandingView.getAlpha();
} else {
mBrandingStartAlpha = 0;
}
mIconFadeOutDuration = context.getResources().getInteger(
R.integer.starting_window_app_reveal_icon_fade_out_duration);
mAppRevealDelay = context.getResources().getInteger(
R.integer.starting_window_app_reveal_anim_delay);
mIconStartAlpha = iconView.getAlpha();
}
mAppRevealDuration = context.getResources().getInteger(
R.integer.starting_window_app_reveal_anim_duration);
mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
mMainWindowShiftLength = mainWindowShiftLength;
// 动画结束回调
mFinishCallback = handleFinish;
mTransactionPool = pool;
}
}
构造方法里主要是对一些参数的获取初始化,为了后续的动画。有个印象即可,主要知道将动画结束的回调设置给了mFinishCallback。然后后开始动画SplashScreenExitAnimation::startAnimations
# SplashScreenExitAnimation
private ValueAnimator mMainAnimator;
void startAnimations() {
// 创建并执行
mMainAnimator = createAnimator();
mMainAnimator.start();
}
createAnimator会返回一个 ValueAnimator ,然后就 start 了,那么重点就是看具体的动画是怎么创建的了
# SplashScreenExitAnimation
// 根据官方注释,这里的动画是:淡出图标,显示应用程序,上移主窗口
// fade out icon, reveal app, shift up main window
private ValueAnimator createAnimator() {
// reveal app
final float transparentRatio = 0.8f;
final int globalHeight = mSplashScreenView.getHeight();
final int verticalCircleCenter = 0;
final int finalVerticalLength = globalHeight - verticalCircleCenter;
final int halfWidth = mSplashScreenView.getWidth() / 2;
final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
final float[] stops = {0f, transparentRatio, 1f};
mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
mRadialVanishAnimation.setRadialPaintParam(colors, stops);
if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
// shift up main window
View occludeHoleView = new View(mSplashScreenView.getContext());
if (DEBUG_EXIT_ANIMATION_BLEND) {
occludeHoleView.setBackgroundColor(Color.BLUE);
} else {
occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
}
final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
mSplashScreenView.addView(occludeHoleView, params);
mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
}
// 前面都是一些参数的准备,这里开始构建东相关
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(mAnimationDuration);
animator.setInterpolator(Interpolators.LINEAR);
// 添加监听
animator.addListener(this);
// 添加动画更新监听,传递了当前的进度
animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
return animator;
}
这里主要关注动画的 update ,执行的是当前类的 onAnimationProgress 方法
# SplashScreenExitAnimation
private void onAnimationProgress(float linearProgress) {
// 这里处理提吧的淡入淡出动画
onFadeOutProgress(linearProgress);
final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
mAppRevealDuration);
// 应用的进入
if (mRadialVanishAnimation != null) {
mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
}
// 主窗口的上移
if (mShiftUpAnimation != null) {
mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
}
}
到这里动画就算结束了。至于这3个动画具体的执行,一般来说都是不需要修改的,所以就不细看了,如果需要修改可以找到对应地方看。
2.2.1 图标淡入淡出
2.2.2 应用进入RadialVanishAnimation
2.2.3 主窗口的上移ShiftUpAnimation
3. 动画结束
之前知道动画结束的回调被保存在 SplashScreenExitAnimation 下的 mFinishCallback 变量中,SplashScreenExitAnimation 本身是个动画监听,所以看他的动画结束回调即可。
# SplashScreenExitAnimation
@Override
public void onAnimationEnd(Animator animation) {
reset();
InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_EXIT_ANIM);
}
这里主要看的是reset()方法,另外在当前类搜索 mFinishCallback 使用的地方就是在 reset() 方法中,reset() 又在 onAnimationEnd 和 onAnimationCancel 中调用,也就是动画结束或者取消都会执行。
# SplashScreenExitAnimation
private void reset() {
if (DEBUG_EXIT_ANIMATION) {
Slog.v(TAG, "vanish animation finished");
}
// 检查 SplashScreenView 是否附加到窗口上
if (mSplashScreenView.isAttachedToWindow()) {
// 影藏
mSplashScreenView.setVisibility(GONE);
if (mFinishCallback != null) {
// 执行回调
mFinishCallback.run();
mFinishCallback = null;
}
}
if (mShiftUpAnimation != null) {
// 结束位移动画
mShiftUpAnimation.finish();
}
}
这里主要关注动画结束回调的执行,也就是触发了 StartingSurfaceDrawer::removeWindowInner 方法的执行
# StartingSurfaceDrawer
private void removeWindowInner(View decorView, boolean hideView) {
if (mSysuiProxy != null) {
mSysuiProxy.requestTopUi(false, TAG);
}
if (hideView) {
// 设置GONE,这里的decorView就是startWindow的decorView
decorView.setVisibility(View.GONE);
}
// 触发remove
mWindowManagerGlobal.removeView(decorView, false /* immediate */);
}
这里主要是starting_reveal动画结束后会触发 startWindow 的移除。这个就和 【removeWindow】一样了。
4. 总结
本篇主要内容就是 starting_reveal 动画, 这个动画结束后,才会触发移除 StartWindow 的流程和动画。
-
- 触发时机是在应用窗口绘制完整,设置窗口状态为 HAS_DRAWN 时。
-
- 移除动画是远端动画,真正的执行在 SystemUI进程
-
- 动画结束后会才触发 startWindow 的移除