忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。
-- 服装学院的IT男
回顾一下 AppTransition 工作模型,在上一篇已经看完了 AppTransition 的2个重要的流程: prepareAppTransition ,executeAppTransition 并且知道 AppTransition::isReady 方法现在已经是返回 true 了。
本篇要继续介绍 AppTransition 的第三流程 :goodToGo 。这边也会看到很多和动画相关的代码。
我们知道要做动画就需要创建 leash 图层,搜索"animation-leash of"关键字最终确定是在 SurfaceAnimator::createAnimationLeash 方法完成对 leash 图层的创建,这个方法我加上堆栈后得到以下调用链:
php
I WindowManager: Cancelling animation restarting=true for Task{fa3f494 #11 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
E biubiubiu: SurfaceAnimator createAnimationLeash: app_transition
E biubiubiu: java.lang.Exception
E biubiubiu: at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
E biubiubiu: at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
E biubiubiu: at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2770)
E biubiubiu: at com.android.server.wm.WindowContainer$AnimationRunnerBuilder.lambda$build$4$com-android-server-wm-WindowContainer$AnimationRunnerBuilder(WindowContainer.java:3912)
E biubiubiu: at com.android.server.wm.WindowContainer$AnimationRunnerBuilder$$ExternalSyntheticLambda4.startAnimation(Unknown Source:7)
E biubiubiu: at com.android.server.wm.WindowContainer.applyAnimationUnchecked(WindowContainer.java:3073)
E biubiubiu: at com.android.server.wm.Task.applyAnimationUnchecked(Task.java:3380)
E biubiubiu: at com.android.server.wm.WindowContainer.applyAnimation(WindowContainer.java:2912)
E biubiubiu: at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:851)
E biubiubiu: at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:1024)
E biubiubiu: at com.android.server.wm.AppTransitionController.handleAppTransitionReady(AppTransitionController.java:284)
E biubiubiu: at com.android.server.wm.RootWindowContainer.checkAppTransitionReady(RootWindowContainer.java:979)
E biubiubiu: at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:846)
E biubiubiu: at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
E biubiubiu: at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
E biubiubiu: at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
可以确定是在一次 layout 流程中,由 RootWindowContainer::checkAppTransitionReady 这个方法触发的。
1. RootWindowContainer::checkAppTransitionReady
scss
# RootWindowContainer
private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
// Trace all displays app transition by Z-order for pending layout change.
for (int i = mChildren.size() - 1; i >= 0; --i) {
// 遍历所有的屏幕
final DisplayContent curDisplay = mChildren.get(i);
// If we are ready to perform an app transition, check through all of the app tokens
// to be shown and see if they are ready to go.
// 重点*1. 判断是否满足执行条件
if (curDisplay.mAppTransition.isReady()) {
// handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
// 重点*2. 条件满足主要做的事
curDisplay.mAppTransitionController.handleAppTransitionReady();
......
}
......
}
}
layout 流程是很频繁的,但是能不能进入后面的逻辑还是要看 AppTransition::isReady 条件是否满足,这个已经在上篇分析过了。
经过 1.3 小结,流程已经走到了AppTransition::setReady,那么对应的,本篇最上面 RootWindowContainer::checkAppTransitionReady 方法里的"mAppTransition.isReady()"就会返回true,则可以进入 AppTransitionController::handleAppTransitionReady ,这个方法主要的如下:
scss
# AppTransitionController
void handleAppTransitionReady() {
mTempTransitionReasons.clear();
// 重点* 1. 判断是否满足条件执行后续逻辑
if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
|| !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
|| !transitionGoodToGoForTaskFragments()) {
return;
}
// 经典日志输出
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
......
// 根据debug当前案例这里返回的是12 表示 "TRANSIT_OLD_WALLPAPER_CLOSE",目前这个不影响动画的分析,暂不详细看这个方法
@TransitionOldType final int transit = getTransitCompatType(
mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
......
try {
// 重点* 2. 应用app transition动画
applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
voiceInteraction);
// 重点* 3. 处理closing activity可见性*/
handleClosingApps();
// 重点* 4. 处理opening actvity可见性*/
handleOpeningApps();
......
// 重点* 5. goodToGo
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
......
} finally {
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
......
}
这里有5个重点
-
- 想要继续执行后面的方法需要 transitionGoodToGo 返回true
-
- 执行动画相关的逻辑,主要是创建 leash 图层
-
- 处理需要 close 的 APP
-
- 处理需要 open 的 APP ,比如会设置窗口状态为 HAS_DRAWN
-
- goodToGo流程,也就是执行动画
这边主要关心 1,2,5 这3点和当前分析动画相关的逻辑。先看第一点
1.1 AppTransitionController::transitionGoodToGo
进入 AppTransitionController::handleAppTransitionReady 的条件是 AppTransition::isReady ,但是现在看来真正想执行这个方法的代码,还需要 transitionGoodToGo 这个方法返回true, 才行。
换句话说,只有在日志里看到下面这句打印的时候,才说明是真正执行了 AppTransitionController::handleAppTransitionReady 方法里面的内容
markdown
V WindowManager: **** GOOD TO GO
所以先看一下 AppTransitionController::transitionGoodToGo 方法。
上次看到执行了3次,又一个返回 false,都会被 return 。这边只看传入"mDisplayContent.mOpeningApps"的条件即可。
java
AppTransitionController
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
// 关键日志
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
// 这个一般不会出现
if (mDisplayContent.mAppTransition.isTimeout()) {
return true;
}
......// 省略屏幕旋转处理
// 对每个APP处理
for (int i = 0; i < apps.size(); i++) {
WindowContainer wc = apps.valueAt(i);
final ActivityRecord activity = getAppFromContainer(wc);
if (activity == null) {
continue;
}
// 关键日志,后面用来判断的几个值都在
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
activity, activity.allDrawn, activity.startingDisplayed,
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
return false;
}
......
}
......//省略其他情况
// 正常情况都在这里返回,如果壁纸不可见或壁纸过渡准备就绪,返回true,表示可以继续应用过渡
return !mWallpaperControllerLocked.isWallpaperVisible()
|| mWallpaperControllerLocked.wallpaperTransitionReady();
}
这里3个 return 的地方,2个日志的打印。相关的解释已经在代码上了,这里2个对应打印的日志如下:
ini
WindowManager: Checking 0 opening apps (frozen=false timeout=false)...
WindowManager: Check opening app=ActivityRecord{fea3a44 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t15}: allDrawn=false startingDisplayed=true startingMoved=false isRelaunching()=false startingWindow=Window{97dc59d u0 Splash Screen com.google.android.dialer}
这里既然已经打印了2个日志,那返回值肯定是在最后的那个 return 了。
根据目前的日志,这个方法肯定是返回 true 了的。以后这个返回 false 的可以根据这里的日志来判断是哪个变量导致的返回 false。一般来说需要关注的是 "activity.allDrawn" 这个值。
2 leash图层相关
前面包括上篇启动都是一些准备工作,现在要真正开始看动画相关的调用了。
java
# AppTransitionController
private void applyAnimations(ArraySet<ActivityRecord> openingApps,
ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
LayoutParams animLp, boolean voiceInteraction) {
......
// 重点* 1. 提升打开动画层级对象,比如对整个Task做动画
final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
openingApps, closingApps, true /* visible */);
// 提升关闭动画层级对象,比如对整个Task做动画
final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
openingApps, closingApps, false /* visible */);
// // 重点* 2. 可见的APP动画处理
applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
voiceInteraction);
// 不可见的APP动画处理
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
......
}
这里有4个方法的执行,但是是对打开和关闭的各自处理,其实也就是2对方法。 getAnimationTargets 和 applyAnimations 。
openingApps 就是 TargetActivity ,当前场景就是 "电话"这个应用的 Actyivity 。而 openingApps 就是 SourceActivity, 当前场景就是 launcher 。
2.1 提升动画目标图层
AppTransitionController::getAnimationTargets 这个方法是用来提升动画层级的。 怎么理解呢? 比如是应用的打开动画,按理说应该对窗口也就是 ActivityRecord 做动画。但是最终的 leash 图层是对整个 Task 做动画了。 这一步从 ActivityRecord --> Task 提升动画层级的,也就是找到合适的动画图层。 看一下这个方法的部分代码:
swift
# AppTransitionController
static ArraySet<WindowContainer> getAnimationTargets(
ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
boolean visible) {
.......
// 根据visible来判断源数据用哪个集合
final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
......
// The final animation targets which cannot promote to higher level anymore.
final ArraySet<WindowContainer> targets = new ArraySet<>();
......
// 打印经过处理的集合和源集合
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
apps, targets);
// 返回经过提级后的容器列表
return targets;
}
这个方法很长,这里并没有完整代码,根据加的注释,这个方法就是返回一个动画集合的容器,比如当前场景传递进来的是"电话"的 ActivityRecord ,返回的是其 Task 。 这段的日志只看openingApps场景输入如下:
arduino
10-25 20:04:39.900 1422 1492 V WindowManager: getAnimationTarget in={ActivityRecord{fea3a44 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t15}}, out={Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}}
具体传进来的是什么,提升后又是什么,感兴趣的可以自己去看这个方法,或者在不同场景看这个日志的打印。
2.2 创建leash图层
得到了提升后的目标动画图层,就会执行 AppTransitionController::applyAnimations 方法,开始创建 leash 图层了。
less
# AppTransitionController
// 经过提级后,这里的wcs里面只有应用的Task,而apps还是应用的ActivityRecord
private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
@TransitionOldType int transit, boolean visible, LayoutParams animLp,
boolean voiceInteraction) {
// 当前场景只有1个
final int wcsCount = wcs.size();
for (int i = 0; i < wcsCount; i++) {
final WindowContainer wc = wcs.valueAt(i);
......
// 遍历集合下的各个容器,触发动画逻辑
wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
}
}
# WindowContainer
boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
boolean enter, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
......
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
if (okToAnimate()) {
// 打印log
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transit=%s, enter=%b, wc=%s",
AppTransition.appTransitionOldToString(transit), enter, this);
// 主流程, 子类Task重写
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
} else {
cancelAnimation();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
......
}
这段的日志输出如下:
arduino
10-25 20:04:39.901 1422 1492 V WindowManager: applyAnimation: transit=TRANSIT_OLD_WALLPAPER_CLOSE, enter=true, wc=Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
这里的 transit 为什么是这个前面提过了,继续看主流程。
less
# WindowContainer
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
// 默认返回null,Task重写返回Task本身,所以这里就是应用的Task
final Task task = asTask();
......
// 重点* 1 获取动画adapter
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
transit, enter, isVoiceInteraction);
// 拿到动画的adapter
AnimationAdapter adapter = adapters.first;
// 缩略图的adapter,当前场景为null
AnimationAdapter thumbnailAdapter = adapters.second;
if (adapter != null) {
......
AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
......
// 重点* 2 主流程
animationRunnerBuilder.build()
.startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
......
}
}
又看到了动画 Adapter 相关的东西了,肯定是和之前分析的 RemoteAnimationAdapter 要联系起来的。
2.2.1 获取动画 Adapter
谷歌对于动画的设计用的是 适配器模式,把具体场景的动画执行交给了对于的动画 Adapter 类。而当前的这个方法就是获取对于场景是由哪个 Adapter 来继续后续逻辑的。 而现在分析的启动动画,前面已经知道在 launcher 创建了一个 RemoteAnimationAdapter 并且已经传递到 system_service 进程了。 那现在又执行到这个方法,盲猜获取的 Adapter 就是 RemoteAnimationAdapter 。
继续看代码:
java
# WindowContainer
Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
@TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
......// 忽略其他场景
// 1. 拿到controller
final RemoteAnimationController controller =
getDisplayContent().mAppTransition.getRemoteAnimationController();
......
if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
// 2. 通过controller创建RemoteAnimationRecord,isChanging为false,所以最后参数为null
final RemoteAnimationController.RemoteAnimationRecord adapters =
controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
......
// debug看到 mThumbnailAdapter为null,需要留意其mAdapter
resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
} else ...... // 后面还有创建LocalAnimationAdapter的
return resultAdapters;
}
这里有2步:
-
- 先拿到 AppTransition 下的 mRemoteAnimationController 这个是前面提过的,里面有 launcher 传过来的 RemoteAnimationAdapter
-
- 通过 RemoteAnimationController 构建出一个 RemoteAnimationRecord 对象,然后将里面的对象放在 resultAdapters 中返回
那么问题的关键还是 RemoteAnimationController::createRemoteAnimationRecord 方法
csharp
# RemoteAnimationController
// 注意这个数组保存的是RemoteAnimationRecord
private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
Point position, Rect localBounds, Rect endBounds, Rect startBounds) {
// 日志,只是打印windowContainer,那肯定就是电话应用的 Task
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
windowContainer);
// 注意最后一个参数为null
final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
localBounds, endBounds, startBounds);
// 加入到集合中
mPendingAnimations.add(adapters);
return adapters;
}
# RemoteAnimationController.RemoteAnimationRecord
RemoteAnimationAdapterWrapper mAdapter;
RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
Rect endBounds, Rect startBounds) {
mWindowContainer = windowContainer;
if (startBounds != null) {
......
} else {
// startBounds为null,走这
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
new Rect());
mStartBounds = null;
}
}
这里因为 startBounds = null,走的是下面的逻辑,所以 mThumbnailAdapter 自然也就是null, 主要关注 mAdapter 变量,是个 RemoteAnimationAdapterWrapper 对象。 这里要注意,RemoteAnimationRecord 一旦创建就会被添加到 mPendingAnimations 集合中,用于后面googToGo流程创建需要传递到launcher进程做动画的应用目标 目前看到了 RemoteAnimationAdapterWrapper 的构建流程,知道 RemoteAnimationRecord 下面的mAdapter其实就是 RemoteAnimationAdapterWrapper
2.2.2 动画主流程
在 WindowContainer::applyAnimationUnchecked 看到首先是获取了动画 Adapter ,现在继续看主流程。 这里的 AnimationRunnerBuilder 是 WindowContainer 的内部类,所以调用的 startAnimation 方法其实是 WindowContainer.IAnimationStarter::build 下的方法。
最终也是调用的 WindowContainer::startAnimation
less
# WindowContainer
protected final SurfaceAnimator mSurfaceAnimator;
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim) {
// 关键日志
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.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
根据前面的分析知道这里传递进来的 anim 就是 RemoteAnimationRecord 下的 mAdapter 也就是 RemoteAnimationAdapterWrapper ,这个的log打印也是这样
typescript
10-25 20:04:39.901 1422 1492 V WindowManager: Starting animation on Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}: type=1, anim=com.android.server.wm.RemoteAnimationController$RemoteAnimationAdapterWrapper@a80500d
这个日志也是是很关键的。
2.2.2.1 SurfaceAnimator的创建及动画结束回调的设置
这里看到后续的流程是交给了 SurfaceAnimator 这个类,这个类是专门处理动画的,不管是远端动画还是本地动画都是通过其执行,而且动画结束的回调也是一样的。所以需要看一下这个 SurfaceAnimator 的定义。
mSurfaceAnimator 对象其实是在 WindowContainer 构造的时候赋值的。
scala
# SurfaceAnimator
// 其实也是WindowContainer
final Animatable mAnimatable;
// 动画结束回调
final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
// 再次封装的动画结束回调
final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
SurfaceAnimator(Animatable animatable,
@Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
WindowManagerService service) {
mAnimatable = animatable;
mService = service;
mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;
mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
}
# WindowContainer
protected final WindowManagerService mWmService;
// WindowContainer实现了Animatable接口
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
InsetsControlTarget {
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mTransitionController = mWmService.mAtmService.getTransitionController();
mPendingTransaction = wms.mTransactionFactory.get();
mSyncTransaction = wms.mTransactionFactory.get();
// 构建 SurfaceAnimator 传递了动画结束回调
mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
mSurfaceFreezer = new SurfaceFreezer(this, wms);
}
}
protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
// 处理动画结束的事情
doAnimationFinished(type, anim);
// 执行到 WindowManagerService
mWmService.onAnimationFinished();
mNeedsZBoost = false;
}
SurfaceAnimator 下的 mAnimatable 就是 WindowContainer ,WindowContainer 构造的方法中会创建一个 SurfaceAnimator,也就是说每个 WindowContainer 都有一个 SurfaceAnimator 对象,然后将当前的onAnimationFinished方法作为动画结束回调传递进去。 也就是说动画结束的最终回调是执行到 WindowManagerService::onAnimationFinished 方法。
2.2.2.2 真正的创建leash图层
刚刚只是看了一下 SurfaceAnimator 构建以及动画回调。现在继续看主流程,SurfaceAnimator::startAnimation 方法。
less
# SurfaceAnimator
// anim 执行动画的adapter
// type 动画类型
// animationFinishedCallback 动画完成的回调
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
// mAnimation的值是anim
mAnimation = anim;
mAnimationType = type;
mSurfaceAnimationFinishedCallback = animationFinishedCallback;
mAnimationCancelledCallback = animationCancelledCallback;
// 拿到对应容器的SurfaceControl
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
cancelAnimation();
return;
}
mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
// 肯定为null
if (mLeash == null) {
// 重点* 1. 创建leash图层
mLeash = createAnimationLeash(mAnimatable, surface, t, type,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
0 /* y */, hidden, mService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
mAnimatable.onLeashAnimationStarting(t, mLeash);
if (mAnimationStartDelayed) {
ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
return;
}
// 重点*2. 开始动画,注意传进去的完成回调是经过处理的那个
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
......
}
首先对需要关注的参数进行了注释,mAnimatable 的值是参数 anim ,那么也就是 RemoteAnimationAdapterWrappe 对象。 这里主要关注2件事:
-
- 创建动画图层
-
- 执行动画
2.2.3 createAnimationLeash -- 创建leash图层
arduino
# SurfaceAnimator
static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
Transaction t, @AnimationType int type, int width, int height, int x, int y,
boolean hidden, Supplier<Transaction> transactionFactory) {
// 打印日志,
ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
.setParent(animatable.getAnimationLeashParent()) // 重点*1. 设置当前动画图层的父亲为需要做动画对象的父亲
.setName(surface + " - animation-leash of " + animationTypeToString(type)) //设置名字
.setHidden(hidden)
.setEffectLayer()
.setCallsite("SurfaceAnimator.createAnimationLeash");
final SurfaceControl leash = builder.build(); // 构建出leash动画图层
t.setWindowCrop(leash, width, height);
t.setPosition(leash, x, y);
t.show(leash);
t.setAlpha(leash, hidden ? 0 : 1);
t.reparent(surface, leash); //重点*2 . 将Task的父亲设置成leash
return leash;
}
重点分析:
-
- 这里设置了动画图层的父亲,根据上面的log和Winscope工具看到的,animatable就是启动"电话"创建的Task,那么这里就是将这个动画图层的父亲设置为了DefaultTaskDisplayArea
-
- 上一步操作完之后,task和leash的父亲都是DefaultTaskDisplayArea,然后这里再将task的父亲设置为leash,也就有了下面这个结构。也对应上了前面图片的开始动画的层级变化。
2.2.4 createAnimationLeash
根据之前的分析,直接去 RemoteAnimationAdapterWrappe 下看
less
# RemoteAnimationController.RemoteAnimationAdapterWrapper
SurfaceControl mCapturedLeash;
private OnAnimationFinishedCallback mCapturedFinishCallback;
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// 打印关键日志
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
// 当前案例mStartBounds为空
if (mStartBounds.isEmpty()) {
// Restore position and stack crop until client has a chance to modify it.
t.setPosition(animationLeash, mPosition.x, mPosition.y);
t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());
} else {
......
}
// 将创建的leash图层保存
mCapturedLeash = animationLeash;
// 经过处理的回调保存
mCapturedFinishCallback = finishCallback;
mAnimationType = type;
}
首先打印了一个"startAnimation"的 log ,这个也是一个关键的日志,日志中有这一步,说明远端动画的逻辑,在 system_service 进程已经处理好了。剩下的动画具体的执行就是在 launcher 进程 但是这里只是一些初始化,并没有像期望的那样真正的执行开始动画逻辑。 这就是适配器模式的特性,根据具体场景由对应的适配器做具体的业务处理,当前分析的远端动画,自然不会在这里直接开始动画,如果是本地动画,那本地动画的 LocalAnimationAdapter在这个方法就会真正的启动动画。
那么当前远端动画真正执行动画的逻辑在哪呢?在goodToGo!
2.3 调用链
这一块的调用链如下:
arduino
RootWindowContainer::checkAppTransitionReady
AppTransitionController::handleAppTransitionReady
AppTransitionController::transitionGoodToGo
AppTransitionController::applyAnimations
AppTransitionController::applyAnimations
WindowContainer::applyAnimation
Task::applyAnimationUnchecked
WindowContainer::applyAnimationUnchecked
WindowContainer::getAnimationAdapter
AppTransition.getRemoteAnimationController -- 拿到 RemoteAnimationController
RemoteAnimationController::createRemoteAnimationRecord
RemoteAnimationRecord::init
mPendingAnimations::add -- 添加进集合
WindowContainer.AnimationRunnerBuilder::startAnimation -- 实际上是 WindowContainer.IAnimationStarter::build
WindowContainer::startAnimation
SurfaceAnimator::startAnimation
SurfaceAnimator::createAnimationLeash --创建动画图层
RemoteAnimationAdapterWrappe::startAnimation --触发开始动画(实际上不是真正在开始)
3. goodToGo流程
现在可以正式来看AppTransitionController::handleAppTransitionReady方法处理的事情,整理如下: AppTransitionController::handleAppTransitionReady AppTransitionController::handleAppTransitionReady AppTransitionController::applyAnimations -- leash图层的创建 AppTransitionController::handleClosingApps AppTransitionController::handleOpeningApps --【Window各个状态流程--HAS_DRAWN】 AppTransition::goodToGo
前面跟踪 AppTransitionController::applyAnimations 后面的调用链看到了动画图层的创建 以及看到了通过 Adapter 启动动画(没有真的启动)。
现在继续接着看 AppTransitionController::handleAppTransitionReady 调用的另一个流程:AppTransition::goodToGo 流程,也是真正触发远端动画开始的地方。
less
# AppTransition
private RemoteAnimationController mRemoteAnimationController;
int goodToGo(@TransitionOldType int transit, ActivityRecord topOpeningApp) {
......
if (mRemoteAnimationController != null) {
// 交给RemoteAnimationController执行goodToGo
mRemoteAnimationController.goodToGo(transit);
} else ......
}
主要就是调用 RemoteAnimationController::goodToGo 来触发远端动画的执行。
less
# RemoteAnimationController
// 远端动画的Adapter
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
void goodToGo(@WindowManager.TransitionOldType int transit) {
// 打印goodToGo(),表现这才是真正的触发了goodToGo()逻辑
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
......
// 重点* 1. 创建动画完成回调
mFinishedCallback = new FinishedCallback(this);
// 重点* 2. 创建launcher需要做远程动画的targets
// Create the app targets
final RemoteAnimationTarget[] appTargets = createAppAnimations();
......
// 壁纸动画相关
final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
try {
linkToDeathOfRunner();
// 打印日志,真正开始触发动画
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
+ " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
AppTransition.appTransitionOldToString(transit), appTargets.length,
wallpaperTargets.length, nonAppTargets.length);
// 重点* 3. 这里是触发远端动画真正执行的地方
// 注意传递过去的动画结束回调是 FinishedCallback
mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
wallpaperTargets, nonAppTargets, mFinishedCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to start remote animation", e);
onAnimationFinished();
}
// 日志处理
if (ProtoLogImpl.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
writeStartDebugStatement();
}
});
setRunningRemoteAnimation(true);
}
在【动画-app_transition-1】中提到 system_service 会将要做动画的目标包装在一个 RemoteAnimationTarget 数组传递到 launcher 进程做动画,处理的地方就在这个方法里。 这里的3个数组,分别:appTargets ,wallpaperTargets ,nonAppTargets,分别代表应用目标,壁纸目标,非APP目标,我们只需要关注appTargets即可,这里的数组为2,分别代表"电话"应用的打开动画和launcher关闭的动画, wallpaperTargets 下自然就是壁纸的那根窗口。
这个代码会输出2个日志:
css
10-25 20:04:39.905 1422 1492 D WindowManager: goodToGo()
10-25 20:04:39.911 1422 1492 D WindowManager: goodToGo(): onAnimationStart, transit=TRANSIT_OLD_WALLPAPER_CLOSE, apps=2, wallpapers=1, nonApps=0
这里的"goodToGo()" log 也是经典log之一,然后输出3个 target 的大小,壁纸 target 的为1,说明也要做动画(实际上没看到使用)。 那么这个方法主要处理了2件事:
-
- appTargets的数组处理
-
- 触发远端动画执行,这里的 mRemoteAnimationAdapter 就是 launcher 传递过来的那个
这里壁纸也有目标,说明也要做动画,根据 Winscope 的分析壁纸确实有一个 window_animation 的动画,但是在 WallpaperAnimationAdapter 这个专门给壁纸做动画的 Adapter 并没有看到真正的动画执行,然后这边也看到其作为 wallpaperTargets 也传递到 launcher 了,但是最后也没发现有做动画的地方。这点就很奇怪,不过在 Winscope 也只是看到 有 window_animation 的图层,但是也没看到有相关数值的改变,所以个人觉得壁纸的 window_animation 可能并没有什么实际的动画。
3.1 获取需要在远端知道动画的目标图层 createAppAnimations
java
# RemoteAnimationController
private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
private RemoteAnimationTarget[] createAppAnimations() {
// log打印
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAppAnimations()");
// 定义返回的集合
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
// 重点* 1. 遍历RemoteAnimationRecord集合
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
// 之前存进来的RemoteAnimationRecord
final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
// 重点* 2. 调用RemoteAnimationTarget创建RemoteAnimationTarget
final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
// 如果创建成功则将RemoteAnimationTarget添加到需要返回的targets集合中,并打印日志
if (target != null) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd container=%s",
wrappers.mWindowContainer);
targets.add(target);
} else {
......// 忽略为null情况
}
}
return targets.toArray(new RemoteAnimationTarget[targets.size()]);
}
这里如果有数据被add都有日志,如下:
arduino
WindowManager: Add container=Task{9339170 #1 type=home ?? U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
WindowManager: Add container=Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
所以确实是2个,TaskID = 1的是 launcher 的,我们主要关心的是应用进入的动画目标。
这里有2个需要关注点:
-
遍历的 mPendingAnimations 是什么? mPendingAnimations 在 RemoteAnimationController::getAnimationAdapter 流程的时候提过,通过 RemoteAnimationController::createRemoteAnimationRecord 构建出RemoteAnimationRecord 的时候就会添加到 mPendingAnimations 集合中,就是等这个时候goodToGo用。
-
createRemoteAnimationTarget 方法
ini
# RemoteAnimationController.RemoteAnimationRecord
RemoteAnimationAdapterWrapper mAdapter;
RemoteAnimationTarget createRemoteAnimationTarget() {
// 先做条件判断,创建leash的流程提过,都满足
if (mAdapter == null
|| mAdapter.mCapturedFinishCallback == null
|| mAdapter.mCapturedLeash == null) {
return null;
}
// 由容器创建真正的RemoteAnimationTarget返回
mTarget = mWindowContainer.createRemoteAnimationTarget(this);
return mTarget;
}
这里的 mAdapter 已经很熟悉了,判断条件的几个值也是在创建完 leash 图层后,执行其 startAnimation 方法初始化的时候赋值了的,再次提一下,mCapturedLeash 就是创建的leash图层。 WindowContainer::createRemoteAnimationTarget ,具体的在子类实现,那么当前场景就是在 TaskFragment。
scss
# TaskFragment
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
android.util.Log.d("biubiubiu", "TaskFragment createRemoteAnimationTarget: "+record.getMode());
// MODE_OPENING值为0
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
// There may be a trampoline activity without window on top of the existing task
// which is moving to front. Exclude the finishing activity so the window of next
// activity can be chosen to create the animation target.
? getTopNonFinishingActivity()
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
# ActivityRecord
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final WindowState mainWindow = findMainWindow();
if (task == null || mainWindow == null) {
return null;
}
final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect();
InsetUtils.addInsets(insets, getLetterboxInsets());
// 重点* 构造RemoteAnimationTarget,注意传递了leash图层
final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId,
record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(),
new Rect(), insets,
getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
record.mAdapter.mEndBounds, task.getWindowConfiguration(),
false /*isNotInRecents*/,
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
target.hasAnimatingParent = record.hasAnimatingParent();
return target;
}
这里看到真正目标的创建是 ActivityRecord::createRemoteAnimationTarget 方法,这个方法真正创建了 RemoteAnimationTarget ,参数很多,主要看到传递了task的ID,和record.mAdapter.mCapturedLeash,这个指定是创建的动画图层,其他几个参数看到了再说吧。
到这里就知道了 RemoteAnimationTarget 是怎么创建的,以及知道其内部持有动画的 taskID 和做动画的动画图层。
3.2 触发launcher做动画
当前 system_service 的事情就告一段落了,后续的流程都是在launcher进程开始真正的执行动画。 也就是 RemoteAnimationController::goodToGo 方法调用的那句
scss
mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,wallpaperTargets, nonAppTargets, mFinishedCallback);
这里的 mRemoteAnimationAdapter 以及 getRunner 之前都已经分析后了。
arduino
AppTransition::goodToGo --真正触发launcher进程开始动画
RemoteAnimationController::goodToGo --处理需要传递到launcher做动画的目标
RemoteAnimationController::createAppAnimations
mPendingAnimations::get -- 取出RemoteAnimationRecord
RemoteAnimationRecord::createRemoteAnimationTarget
TaskFragment::createRemoteAnimationTarget
ActivityRecord::createRemoteAnimationTarget
IRemoteAnimationRunner.Stub::onAnimationStart --触发launcher真正做动画
4. 总结
本篇主要介绍了应用的 app_transition(打开)动画的 leash 图层是如何创建的,launcher 的app_transition(关闭)动画和本篇介绍的其实是一样的,流程,只不过是在 AppTransitionController::applyAnimations 多调用了一次 applyAnimations 方法,专递的参数为 false 罢了。
然后也提到了壁纸的 window_animation 动画。
另外也介绍了传递到 launcher做动画的3个 RemoteAnimationTarget 数组是怎么构建的。
本篇最后就是system_service 进程带着这些参数调用 launcher 的 RemoteAnimationAdapter 下 mRunner 对象的 onAnimationStart 方法开触发 launcher 进程做真正的动画播放处理。
本篇完整的调用链如下:
arduino
RootWindowContainer::checkAppTransitionReady
AppTransitionController::handleAppTransitionReady
AppTransitionController::transitionGoodToGo
AppTransitionController::applyAnimations
AppTransitionController::applyAnimations --(app_transition(打开))
WindowContainer::applyAnimation
Task::applyAnimationUnchecked
WindowContainer::applyAnimationUnchecked
RemoteAnimationController::getAnimationAdapter -- adapter相关处理
AppTransition.getRemoteAnimationController -- 拿到 RemoteAnimationController
RemoteAnimationController::createRemoteAnimationRecord
RemoteAnimationController.RemoteAnimationRecord::init
RemoteAnimationAdapterWrapper::init
mPendingAnimations::add -- 添加RemoteAnimationRecord进集合
WindowContainer.AnimationRunnerBuilder::startAnimation --实际上是WindowContainer$IAnimationStarter::build方法下
WindowContainer::startAnimation
SurfaceAnimator::startAnimation
SurfaceAnimator::createAnimationLeash --创建动画图层
RemoteAnimationController.RemoteAnimationAdapterWrapper::startAnimation --触发开始动画(实际上不是真正在开始)
AppTransitionController::applyAnimations (app_transition(关闭))
AppTransition::goodToGo --真正触发launcher进程开始动画
RemoteAnimationController::goodToGo --处理需要传递到launcher做动画的目标
RemoteAnimationController::createAppAnimations
mPendingAnimations::get -- 取出RemoteAnimationRecord
RemoteAnimationRecord::createRemoteAnimationTarget
TaskFragment::createRemoteAnimationTarget
ActivityRecord::createRemoteAnimationTarget
IRemoteAnimationRunner.Stub::onAnimationStart --触发launcher真正做动画