前言
接着上篇文章分析
Android T 远程动画显示流程其一
切入点——处理应用的显示过渡
下面,我们以从桌面点击一个应用启动的场景来分析远程动画的流程,窗口添加的流程见Android T WMS窗口相关流程
这里我们从AppTransitionController.handleAppTransitionReady方法开始跟踪代码流程
代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java
/**
* Handle application transition for given display.
*/
void handleAppTransitionReady() {
......
//通过getTransitCompatType方法获取transit的值
@TransitionOldType final int transit = getTransitCompatType(
mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
......
//方法收集正在打开 (mOpeningApps)、关闭 (mClosingApps) 和切换 (mChangingContainers) 的应用的activity类型
//并将它们存储在 activityTypes 集合中。
final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
//被用于查找与给定transit和activityTypes相关的 ActivityRecord
//也就是我们当前打开的应用的ActivityRecord
final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
mDisplayContent.mChangingContainers);
//获取正在打开的应用列表 (mOpeningApps) 中的顶层应用。
//ignoreHidden 参数设置为 false,意味着即使应用是隐藏的,也会被考虑在内
final ActivityRecord topOpeningApp =
getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
//获取正在关闭的应用列表 (mClosingApps) 中的顶层应用
final ActivityRecord topClosingApp =
getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
//获取正在切换的应用列表 (mChangingContainers) 中的顶层应用
//其取决于参数DisplayContent.mChangingContainers中是否有值
/**
有三种情况会给DisplayContent.mChangingContainers中添加值
1.{@link Task}在全屏和浮窗之间发生切换
2.{@link TaskFragment}已组织好并且正在更改窗口边界
3.{@link ActivityRecord}被重新分配到一个有组织的{@link TaskFragment}中
**/
final ActivityRecord topChangingApp =
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
//从之前找到的animLpActivity(正在打开的应用的ActivityRecord)的窗口中获取布局参数
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
......
try {
/*1.1应用app transition动画(远程动画)*/
applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
animLp, voiceInteraction);
/*1.2.1处理closing activity可见性*/
handleClosingApps();
/*1.2.2处理opening actvity可见性*/
handleOpeningApps();
//处理用于处理正在切换的应用
handleChangingApps(transit);
//处理正在关闭或更改的容器
handleClosingChangingContainers();
//设置与最后一次应用过渡动画相关的信息
appTransition.setLastAppTransition(transit, topOpeningApp,
topClosingApp, topChangingApp);
final int flags = appTransition.getTransitFlags();
/*1.3播放远程动画*/
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
//处理非应用窗口的过渡动画
handleNonAppWindowsInTransition(transit, flags);
//执行动画回调
appTransition.postAnimationCallback()
} finally {
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
......
// This has changed the visibility of windows, so perform
// a new layout to get them all up-to-date.
/*2.由于activity的可见性变更,将DisplayContent.mLayoutNeeded标志位置为true*/
mDisplayContent.setLayoutNeeded();
......
}
这个方法主要处理这三件事:
1.处理activity的过渡动画(远程动画)
2.分别调用 handleClosingApps以及handleOpeningApps对要关闭的和要打开的Activity进行可见性更新。
3.调用AppTransition.goodToGo方法走播放远程动画流程。
4.由于activity的可见性变更,将DisplayContent.mLayoutNeeded设置为true,该标志位在DisplayContent.performLayoutNoTrace中用来判断是否对当前DisplayContent下的所有窗口进行刷新。
这里我们主要关注远程动画的流程,主要分为两个部分。
- 处理并创建远程动画流程
applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);
- 播放显示远程动画流程
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
动画创建流程代码分析
applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);
基于一组ActivityRecord来应用动画,这些ActivityRecord表示正在进行切换的应用。mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps
这两个参数分别代表正在打开和关闭的应用;transit
通过前面getTransitCompatType方法中获取,是TRANSIT_OLD_WALLPAPER_CLOSE
(12);animLp
通过前面getAnimLp方法中获取,用于定义窗口的布局参数。这里就是代表正在打开的应用的ActivityRecord的窗口布局参数;voiceInteraction
:表示是否为语音交互。
处理并创建远程动画
代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java
/**
* Apply an app transition animation based on a set of {@link ActivityRecord}
*
* @param openingApps The list of opening apps to which an app transition animation applies.
* @param closingApps The list of closing apps to which an app transition animation applies.
* @param transit The current transition type.
* @param animLp Layout parameters in which an app transition animation runs.
* @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
* interaction session driving task.
*/
private void applyAnimations(ArraySet<ActivityRecord> openingApps,
ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
LayoutParams animLp, boolean voiceInteraction) {
//方法检查过渡类型是否未设置,或者打开和关闭的应用程序是否都为空。如果是,则方法直接返回,不执行任何动画。
if (transit == WindowManager.TRANSIT_OLD_UNSET
|| (openingApps.isEmpty() && closingApps.isEmpty())) {
return;
}
//调用getAnimationTargets方法获取打开和关闭的应用的窗口容器(WindowContainer)
final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
openingApps, closingApps, true /* visible */);
final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
openingApps, closingApps, false /* visible */);
//打开和关闭的窗口应用动画。这是通过调重载的applyAnimations方法完成的,传递相应的参数,如动画的目标、过渡类型等。
applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
//如果存在最近任务动画控制器(RecentsAnimationController),则发送任务出现任务
final RecentsAnimationController rac = mService.getRecentsAnimationController();
if (rac != null) {
rac.sendTasksAppeared();
}
//遍历打开和关闭的应用,并设置mOverrideTaskTransition为false
for (int i = 0; i < openingApps.size(); ++i) {
openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
}
for (int i = 0; i < closingApps.size(); ++i) {
closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
}
//如果存在辅助功能控制器(AccessibilityController)且有回调,则调用其onAppWindowTransition方法。
final AccessibilityController accessibilityController =
mDisplayContent.mWmService.mAccessibilityController;
if (accessibilityController.hasCallbacks()) {
accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
}
}
传递关键参数,处理应用程序窗口的打开和关闭动画。
通过getAnimationTargets
方法获取当前打开和关闭的应用的容器,即ActivityRecord的容器。
最关键的方法是调用的applyAnimations方法:
applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
我们这里openingWcs
和closingWcs
实际上表示的是应用的容器,即Task;openingApps
和 closingApps
就是前面传递的mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps
,分别代表正在打开和关闭的应用,也是挂在对应Task下面的ActivityRecord。并且传递了应用的可见性visible
,true可见,false不可见。
因此在我们桌面点击打开应用的流程中,openingWcs
实际上指的是应用的Task,openingApps
是应用的ActivityRecord(其实就是应用的主界面),其可见性为true;closingWcs
对应的是桌面的Task,closingApps
是桌面的ActivityRecord,其可见性为false。
这也对应了我们前面创建动画图层的堆栈中所打印的,先创建了应用的动画图层,后创建桌面的动画图层。
注:
从这里开始后续流程执行了两次,第一次是打开的应用流程,第二次是关闭的应用流程(一个应用的启动,伴随这另一个应用的退出,浮窗等特殊场景除外)。
从桌面点击开启应用的场景来说,一次是启动的应用角度执行流程,另一次是桌面角度执行流程。
从代码逻辑上来说,唯一的不同点是传递的可见性的值不同。
这个方法调用的是重载的applyAnimations方法
获取需要做动画的容器
/**
* Apply animation to the set of window containers.
*
* @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
* @param apps The list of {@link ActivityRecord}s being transitioning.
* @param transit The current transition type.
* @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
* invisible.
* @param animLp Layout parameters in which an app transition animation runs.
* @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
* interaction session driving task.
*/
private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
@TransitionOldType int transit, boolean visible, LayoutParams animLp,
boolean voiceInteraction) {
//获取窗口容器的数量
final int wcsCount = wcs.size();
//遍历每一个应用的窗口容器
for (int i = 0; i < wcsCount; i++) {
final WindowContainer wc = wcs.valueAt(i);
// If app transition animation target is promoted to higher level, SurfaceAnimator
// triggers WC#onAnimationFinished only on the promoted target. So we need to take care
// of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
// app transition.
//对于每一个应用的窗口容器,检查正在进行切换的应用(apps)中哪些是该窗口容器的后代。
//就比如应用的ActivityRecord是是应用的Task的后代
final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
for (int j = 0; j < apps.size(); ++j) {
final ActivityRecord app = apps.valueAt(j);
//app如果是wc的后代,将其添加到一个列表中。
if (app.isDescendantOf(wc)) {
transitioningDescendants.add(app);
}
}
//调用每个应用的窗口容器的applyAnimation方法,传入相应的参数
//这些参数包含动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。
wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
}
}
入参含义:wcs
: 一个WindowContainer对象的集合,这些对象是需要应用动画的窗口容器。apps
: 一个ActivityRecord对象的集合,这些对象表示正在进行切换的应用程序。transit
: 当前的过渡类型,例如淡入淡出、滑动等。visible
: 一个布尔值,表示应用是否变为可见。animLp
: 布局参数,定义了动画运行时的布局。voiceInteraction
: 一个布尔值,表示是否有语音交互。
关键代码解读:
-
final WindowContainer wc = wcs.valueAt(i);
获取窗口容器wcs
是前面传递过来的是Task,wc
就是依次获取当前应用的Task和桌面Task。 -
transitioningDescendants
存储的就是需要做动画的ActivityRecord。 -
传递动画参数
通过wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
方法,传递参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。wc
就是Task,其没有applyAnimation方法,向上找父类WindowContainer.applyAnimation方法调用。
判断是否应用动画,传递相关参数
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
/**
* Applies the app transition animation according the given the layout properties in the
* window hierarchy.
*
* @param lp The layout parameters of the window.
* @param transit The app transition type indicates what kind of transition to be applied.
* @param enter Whether the app transition is entering transition or not.
* @param isVoiceInteraction Whether the container is participating in voice interaction or not.
* @param sources {@link ActivityRecord}s which causes this app transition animation.
*
* @return {@code true} when the container applied the app transition, {@code false} if the
* app transition is disabled or skipped.
*
* @see #getAnimationAdapter
*/
boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
boolean enter, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
//判断是否禁用过渡动画
if (mWmService.mDisableTransitionAnimation) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transition animation is disabled or skipped. "
+ "container=%s", this);
//取消当前动画
cancelAnimation();
return false;
}
// Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
// to animate and it can cause strange artifacts when we unfreeze the display if some
// different animation is running.
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
//会判断是否有冻结,屏幕是否开启
if (okToAnimate()) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transit=%s, enter=%b, wc=%s",
AppTransition.appTransitionOldToString(transit), enter, this);
//传递相关参数,创建AnimationAdapter和AnimationRunnerBuilder,准备启动动画
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
} else {
//取消当前动画
cancelAnimation();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
//检查指定的窗口容器是否正在进行动画
return isAnimating();
}
下面说说里面的几个关键点:
-
判断是否禁用过渡动画
mWmService.mDisableTransitionAnimation
这个变量是在WindowManagerService的构造方法中初始化的mDisableTransitionAnimation = context.getResources().getBoolean( com.android.internal.R.bool.config_disableTransitionAnimation);
可以发现是读取
config_disableTransitionAnimation
配置项
代码路径:frameworks/base/core/res/res/values/symbols.xml<java-symbol type="bool" name="config_disableTransitionAnimation" />
定义了这个symbol
代码路径:frameworks/base/core/res/res/values/config.xml<!-- Flag to disable all transition animations --> <bool name="config_disableTransitionAnimation">false</bool>
定义了默认值为false,不禁用过渡动画
-
取消当前动画
cancelAnimation();
void cancelAnimation() { //处理动画结束时的一些后续操作 doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation()); //调用SurfaceAnimator.cancelAnimation方法来取消当前正在进行的动画 mSurfaceAnimator.cancelAnimation(); //调用unfreeze方法解除对显示的冻结状态,允许显示继续正常更新和渲染 mSurfaceFreezer.unfreeze(getSyncTransaction()); }
doAnimationFinished方法在动画播放结束时处理回调逻辑中会调用到,具体见后面【动画移除流程】。
-
准备动画
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
把前面传递的参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表,再次传递到applyAnimationUnchecked方法中。
注意,这里调用的是Task中重写的applyAnimationUnchecked方法,而不是直接调用的WindowContainer中的applyAnimationUnchecked方法。
因为我们前面是通过前面AppTransitionController.applyAnimations中wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
调用过来的,因此此时的this指针指的是变量wc
,即应用对应的Task。
后面细讲applyAnimationUnchecked方法。 -
检查动画
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.javafinal boolean isAnimating(int flags, int typesToCheck) { return getAnimatingContainer(flags, typesToCheck) != null; }
flags
用于确定要检查的动画类型和范围。typesToCheck
用于确定哪些类型的动画需要检查。
方法内部调用getAnimatingContainer
方法来获取正在进行动画的窗口容器,并根据返回值判断是否存在符合条件和目标标志的动画。
如果返回值为 true,则说明存在符合条件的动画;如果返回值为 false,则说明不存在符合条件的动画。
处理最近任务状态的动画
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
其中参数enter
代表的其实就应用的可见性,从前面AppTransitionController.applyAnimations方法中逐步传递过来值有两个
applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp, voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction);
启动的应用的可见性为true,桌面的可见性为false
代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java
@Override
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
//获取RecentsAnimationController
//只有在最近任务中,切换到另一个应用时才会创建
final RecentsAnimationController control = mWmService.getRecentsAnimationController();
//RecentsAnimationController不为空
if (control != null) {
// We let the transition to be controlled by RecentsAnimation, and callback task's
// RemoteAnimationTarget for remote runner to animate.
//应用可见性为true,且当前activity不是桌面或者最近任务
if (enter && !isActivityTypeHomeOrRecents()) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
"applyAnimationUnchecked, control: %s, task: %s, transit: %s",
control, asTask(), AppTransition.appTransitionOldToString(transit));
//执行最近任务动画逻辑
control.addTaskToTargets(this, (type, anim) -> {
for (int i = 0; i < sources.size(); ++i) {
sources.get(i).onAnimationFinished(type, anim);
}
});
}
//判断是否有返回手势
} else if (mBackGestureStarted) {
// Cancel playing transitions if a back navigation animation is in progress.
// This bit is set by {@link BackNavigationController} when a back gesture is started.
// It is used as a one-off transition overwrite that is cleared when the back gesture
// is committed and triggers a transition, or when the gesture is cancelled.
//返回手势mBackGestureStarted标志位置为false
mBackGestureStarted = false;
//设置一个标志为true,表示应跳过应用的过渡动画。
mDisplayContent.mSkipAppTransitionAnimation = true;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);
} else {
//调用父类WindowContainer的applyAnimationUnchecked方法
super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
}
}
只有在最近任务中,切换到另一个应用时才会创建RecentsAnimationController,因此control
的值为空。如果不为空,应用可见性为true,且当前activity不是桌面或者最近任务,则会进入到最近任务的动画处理逻辑。
我们在操作过程中也没有返回手势,因此mBackGestureStarted
为false。
所以调用了父类WindowContainer的applyAnimationUnchecked方法。
获取AnimationAdapter并创建动画图层
接着前面super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
进行分析
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
//获取当前Task
final Task task = asTask();
//当前Task不为空,应用变为不可见状态,且应用Task不为桌面或者最近任务
//可以理解为应用按home键回到桌面的场景
if