Android U WMS : 旋转动画(4) 准备动画数据 计算出的旋转动画的数据,如下
{
id=13
t=CHANGE
f=0x0
// track id
trk=0
r=[0@Point(0, 0)]
// TransitionInfo#mChanges
c=[
{
WCT{RemoteToken{4220415 Task{d77d5e9 #26 type=standard A=10206:com.awesome.helloworld}}}
m=CHANGE
f=NONE
p=WCT{RemoteToken{fec6a3d DefaultTaskDisplayArea@10996965}}
leash=Surface(name=Task=26)/@0xe4347ff
sb=Rect(0, 0 - 1280, 1840)
eb=Rect(0, 0 - 1840, 1280)
d=0
r=0->1:0
},
{
WCT{RemoteToken{fec6a3d DefaultTaskDisplayArea@10996965}}
m=CHANGE
f=NONE
p=WCT{RemoteToken{3f0e1d2 Display{#0 state=ON size=1840x1280 ROTATION_90}}}
leash=Surface(name=DefaultTaskDisplayArea)/@0x46effc9
sb=Rect(0, 0 - 1280, 1840)
eb=Rect(0, 0 - 1840, 1280)
d=0
r=0->1:-1
},
{
WCT{RemoteToken{3f0e1d2 Display{#0 state=ON size=1840x1280 ROTATION_90}}}
m=CHANGE
f=IS_DISPLAY
leash=Surface(name=WindowedMagnification:0:31)/@0x5399049
sb=Rect(0, 0 - 1280, 1840)
eb=Rect(0, 0 - 1840, 1280)
d=0
r=0->1:-1
snapshot=Surface(name=RotationLayer)/@0x48338b1
}
]
}
重点解释下本文会涉及到的几个重要数据
- 动画目标 DisplayContent 是没有 parent 的。
- 动画目标 DisplayContent 的 leash 是它的 child WindowedMagnification:0:31,而不是自己的 surface。
- sb 是 start bounds 的意思,eb 是 end bounds 的意思,它们代表旋转前后的 bounds。
- 旋转参数,例如 r=0->1:-1,其中 0->1 表示旋转方向从 0 到 1,最后一个数据 -1 表示旋转动画的方式。
- snapshot 代表的截图层的 surface。
transiton ready
Android U WMS : 旋转动画(4) 准备动画数据 计算出的旋转动画的数据之后,然后通知 WMShell transition ready,并传递的数据,如下
// Transitions.java
// 参数 t 是 start transaction
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
// 根据 token,从 mPendingTransitions 找到索引
final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
+ Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
// 根据索引获取 ActiveTransition,并保存 WMCore 传入的参数
final ActiveTransition active = mPendingTransitions.remove(activeIdx);
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
if (activeIdx > 0) {
Log.i(TAG, "Transition might be ready out-of-order " + activeIdx + " for " + active
+ ". This is ok if it's on a different track.");
}
if (!mReadyDuringSync.isEmpty()) {
// ...
} else {
// 分发 transition
dispatchReady(active);
}
}
根据 Android U WMS : 屏幕旋转动画(1)#WMShell 处理 transition 请求 的分析,当 WMShell 收到请求时,会创建 ActiveTransition ,并保存到 mPendingTransitions。
此时,WMShell 收到 transition ready 请求,根据 transition token,从 mPendingTransitions 中匹配到 ActiveTransition,然后开始分发这个 transition,如下
// Transitons.java
boolean dispatchReady(ActiveTransition active) {
final TransitionInfo info = active.mInfo;
if (info.getType() == TRANSIT_SLEEP || active.isSync()) {
// ...
}
// 1. 根据 track id 获取/创建 Track
final Track track = getOrCreateTrack(info.getTrack());
// active 保存到 Track 的 ready queue 的队尾
track.mReadyTransitions.add(active);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(
active.mToken, info, active.mStartT, active.mFinishT);
}
if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
// ...
}
final int changeSize = info.getChanges().size();
boolean taskChange = false;
boolean transferStartingWindow = false;
boolean allOccluded = changeSize > 0;
for (int i = changeSize - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// true
taskChange |= change.getTaskInfo() != null;
// false
transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
// false
allOccluded = false;
}
}
if (!taskChange && transferStartingWindow && changeSize == 2
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
// ...
}
// 2. 使用 start transaction 或者 finish transaction 初始化 surface 状态
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
// 如果 Track 的 ready queue 至少有两个 transition 等待执行,
// 那么不执行当前的 transition,因为下一个需要执行的是 ready queue 中队首的 transition
// TODO: 这里应该加一个 log,表明为何 transition 没有执行
if (track.mReadyTransitions.size() > 1) {
// There are already transitions waiting in the queue, so just return.
return true;
}
// 3. 处理 track 的 ready queue
processReadyQueue(track);
return true;
}
分发 transition 的过程
- 根据 track id 创建/获取 Track,然后使用 Track#mReadyTransitions( 别名为 ready queue ) 保存当前要分发的 transition。Track 与 transition 是一对多的关系,Track 中的多个 transition 可以依次(FIFO)执行,也可以合并后执行。当然,系统也支持多 Track 并行执行。对于当前分析的屏幕就旋转动画来说,会创建 Track,并且 Track 中也只保存一个 transition。
- 使用 start transaction 或者 finish transaction 初始化 leash surface 状态。例如,设置可见性,透明度,等等。参考【初始化 leash 状态】
- 处理 Track 的 ready queue 中的 transition。
初始化 leash 状态
// Transitions.java
/**
* Sets up visibility/alpha/transforms to resemble the starting state of an animation.
*/
private static void setupStartState(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
// 只有 TRANSIT_OPEN, TRANSIT_TO_FRONT,TRANSIT_KEYGUARD_GOING_AWAY 是 opening type
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
continue;
}
// 获取 Change 的 leash 和 transition mode
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_TO_FRONT
&& ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
|| change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
// ...
}
// 不能独立做动画的目标,对 leash 做一些基本操作
// Don't move anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
// 对 leash 执行操作
// t 是 start transaction
t.show(leash);
t.setMatrix(leash, 1, 0, 0, 1);
t.setAlpha(leash, 1.f);
t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
}
continue;
}
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
// ...
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
// ...
} else if (isOpening && mode == TRANSIT_CHANGE) {
// ...
}
}
}
对于屏幕旋转动画来说,初始化 leash 状态,只做了一件事,那就是对不能独立做动画的目标,在 start transaction 中对 leash 进行操作,例如,显示,设置透明度和位置,等等。不过对 leash 的操作,似乎对旋转动画来收,并没有什么用。
来看下,如何判断动画目标,是否可以独立于 parent 做动画,如下
// TransitionInfo.java
/**
* Indication that `change` is independent of parents (ie. it has a different type of
* transition vs. "going along for the ride")
*/
public static boolean isIndependent(@NonNull TransitionInfo.Change change,
@NonNull TransitionInfo info) {
// 没有 parent ,可以独立做动画
// If the change has no parent (it is root), then it is independent
if (change.getParent() == null) return true;
// 非可见性的改变,是不能独立做动画
// non-visibility changes will just be folded into the parent change, so they aren't
// independent either.
if (change.getMode() == TRANSIT_CHANGE) return false;
// 走到这里,表示动画目标的 mode 是可见性改变
TransitionInfo.Change parentChg = info.getChange(change.getParent());
while (parentChg != null) {
// 动画目标是可见性改变,同时 parent 也是可见性改变,也不能独立做动画
// If the parent is a visibility change, it will include the results of all child
// changes into itself, so none of its children can be independent.
if (parentChg.getMode() != TRANSIT_CHANGE) return false;
// 走到这里,表示 parent 是非可见性改变,也就是 mode 为 TRANSIT_CHANGE
// parent 是非可见性改变,但是 parent 没有 parent 了,那么可以独立做动画
// If there are no more parents left, then all the parents, so far, have not been
// visibility changes which means this change is independent.
if (parentChg.getParent() == null) return true;
// 获取 parent 的 parent 信息,继续遍历
parentChg = info.getChange(parentChg.getParent());
}
// 默认是不能独立做动画
return false;
}
判断一个动画目标,是否能独立于 parent 做动画,有很多种情况。这里,我们只讨论,旋转动画的几个动画目标,是否能独立于 parent 做动画
- Task 和 TaskDisplayArea,都不能独立于 parent 做动画,因为它们的 mode 都为 TRANSIT_CHANGE。
- DisplayContent 能独立于 parent 做动画,因为它根本就没有 parent。
对于这个设计理念,我暂时也没有完全明白。不过没关系,我们只要针对不同类型的动画,具体分析即可。一旦掌握了更多动画的执行过程,这里的设计理念,应该就会慢慢明白。
处理 ready queue 中的 transition
对 transition 的所有动画目标的 leash surface 做了初始化后,现在处理 Track 的 ready queue 中的 transition,如下
// Transitions.java
void processReadyQueue(Track track) {
// 刚刚,已经向 ready queue 中保存过 transition
if (track.mReadyTransitions.isEmpty()) {
// ...
}
// 获取队首的 transition,此时不为 null
final ActiveTransition ready = track.mReadyTransitions.get(0);
if (track.mActiveTransition == null) {
// 移除队首的 transition
track.mReadyTransitions.remove(0);
// Track#mActiveTransition 保存正在执行的 transition
track.mActiveTransition = ready;
if (ready.mAborted) {
// ...
}
// 执行 transition
playTransition(ready);
// 继续执行 ready queue 中的 transition
// Attempt to merge any more queued-up transitions.
processReadyQueue(track);
return;
}
// ...
}
根据前面的分析,Track 的 ready queue 中,只保存了一个屏幕旋转动画的 transition。因此,这里可以获取到 transition,并调用 playTransition() 执行,如下
// Transitions.java
private void playTransition(@NonNull ActiveTransition active) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionStarting(active.mToken);
}
// 初始化动画层级
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// 在 request start transition 时,没有 TransitionHandler 对请求感兴趣
// 因此,这里无法直接把 transition 发送给感兴趣的 TransitionHandler 来执行动画
if (active.mHandler != null) {
// ...
}
// 把 Transition 分发给所有 TransitionHandler,谁感兴趣,谁就处理
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
执行 transition
- 初始化动画层级。每一个动画目标的 leash,都不会在原本的层级上执行动画。因此,在动画执行前,需要将动画目标的 leash,直接/间接地 reparent 到 transition root leash 上。参考【初始化动画层级】
- 寻找 TransitionHandler,并让它执行动画。对于屏幕旋转动画来说,由 DefaultTransitionHandler 来执行。
初始化动画层级
// Transitions.java
/**
* Reparents all participants into a shared parent and orders them based on: the global transit
* type, their transit mode, and their destination z-order.
*/
// 参数 t 是 start transaction
// finishT 是 finish transaction
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
// 获取 transition type,旋转动画是 TRANSIT_CHANGE
final int type = info.getType();
//只有 TRANSIT_OPEN,TRANSIT_TO_FRONT,TRANSIT_KEYGUARD_GOING_AWAY 才是 opending type
final boolean isOpening = isOpeningType(type);
// 只有 TRANSIT_CLOSE, TRANSIT_TO_BACK 才是 closing type
final boolean isClosing = isClosingType(type);
// 1.在 start transaction 中 show transition root leash
for (int i = 0; i < info.getRootCount(); ++i) {
t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// 定义 Z 轴分割线的高度
// 要做动画的目标,设置的 Z 序要高于分割线,否则低于分割线的 Z 序
// Put animating stuff above this line and put static stuff below it.
final int zSplitLine = numChanges + 1;
// changes should be ordered top-to-bottom in z
for (int i = numChanges - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
final int mode = change.getMode();
// 2. 不能独立于 parent 做动画的动画目标,不执行层级初始化
if (!TransitionInfo.isIndependent(change, info)) {
continue;
}
// DisplayConten 动画目标是没有 parent 的
boolean hasParent = change.getParent() != null;
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
// 3. 如果动画目标没有 parent,在 start transaction 中,对动画目标的 leash 做了两件事
if (!hasParent) {
// reparent 到 transition root leash 上
t.reparent(leash, info.getRoot(rootIdx).getLeash());
// 设置相对位置坐标
t.setPosition(leash,
change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
// ...
} else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
// ...
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
// ...
} else { // CHANGE or other
if (isClosing || TransitionUtil.isOrderOnly(change)) {
// ...
} else {
// Put above CLOSE mode.
layer = zSplitLine + numChanges - i;
}
}
// 3. start transction 中,设置动画目标 leash 的 layer
t.setLayer(leash, layer);
}
}
初始化动画层级
- 在 start transcation 中 show transition root leash。
- 对于不能独立于 parent 做动画的动画目标,是不需要执行动画层级初始化。根据前面的分析,对于屏幕旋转动画来说,只有 DisplayContent 能独立做动画。那么,接下来的两步,都是对 DisplayContent 的 leash 进行操作的。
- 由于动画目标 DisplayContent 没有 parent,因此,在 start transcation 中,把它的 leash reparent 到 transition root leash 上,并设置了相对坐标的位置。
- 在 start transaction 中,设置 DisplayContent leash 在 transition root leash 上的 layer,这个值是 5。
现在一切就绪了,马上就要开始执行动画了,我想读者一定与我一样,想知道最终的层级是怎样的?虽然,我在文章里把每个 surface 的层级都描述的很清楚,但是用图来表达或许会更清晰,如下
执行旋转动画
现在,一切就绪,由 DefaultTransitionHandler 来执行旋转动画,如下
// DefaultTransitionHandler.java
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"start default transition animation, info = %s", info);
// ...
// animations 是用来保存需要执行的所有动画
// 这个参数到处传递,用来收集所有要执行的动画
final ArrayList<Animator> animations = new ArrayList<>();
// DefaultTransitionHandler#mAnimations 是以 transition token 为 KEY,
// 保存 transition 要执行的所有的动画
mAnimations.put(transition, animations);
// 任意一个动画执行完成的回调
final Runnable onAnimFinish = () -> {
// 检测是否所有动画都执行完毕
if (!animations.isEmpty()) return;
// 走到这里,代表所有动画都执行完毕
// 根据 transition token 清理数据
mAnimations.remove(transition);
// 4. 回调到 Transition#onFinish()
// 注意,参数都是为 null
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
// ...
// 1. 根据 surface layer 从低到高的顺序,依次处理动画目标
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// ...
final boolean isTask = change.getTaskInfo() != null;
final int mode = change.getMode();
boolean isSeamlessDisplayChange = false;
// 1.1 处理 DiplayContent 动画目标
if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
if (info.getType() == TRANSIT_CHANGE) {
// 解析动画的类型
final int anim = getRotationAnimationHint(change, info, mDisplayController);
// 判断是否执行无缝旋转动画
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
// 如果不执行无缝旋转动画,也不执行 jumpcut 动画,那么启动旋转动画
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
// 标记正在执行旋转动画
isDisplayRotationAnimationStarted = true;
continue;
}
} else {
// ...
}
}
if (mode == TRANSIT_CHANGE) {
// ...
// No default animation for this, so just update bounds/position.
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
// 1.2 处理 Task 动画目标
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
&& !change.hasFlags(FLAG_FILLS_TASK))) {
// Update Task and embedded split window crop bounds, otherwise we may see crop
// on previous bounds during the rotation animation.
startTransaction.setWindowCrop(change.getLeash(),
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
}
if (change.getParent() == null
&& change.getStartRotation() != change.getEndRotation()) {
// ...
}
}
// ...
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
// ...
}
// ...
// 2. apply start transaction
startTransaction.apply();
// 3. 在 Handler 线程中执行动画
mAnimExecutor.execute(() -> {
for (int i = 0; i < animations.size(); ++i) {
animations.get(i).start();
}
});
// ...
// run finish now in-case there are no animations
// 立即执行一次动画完成的回调,防止当前没有动画
onAnimFinish.run();
return true;
}
DefaultTransitionHandler 执行动画的过程
- 根据 surface layer 从低到高的顺序,依次处理动画目标。那么,对于屏幕旋转动画来说,它的动画目标的处理顺序是 DisplayContent -> TaskDisplayArea -> Task。
- 对于 DisplayContent 动画目标,先解析它的旋转动画类型,由于并没有指定特别的旋转动画类型,因此使用的是默认的旋转动画类型 ROTATION_ANIMATION_ROTATE ,之后会创建旋转动画。参考【创建并收集旋转动画]
- 对于 Task 动画目标,就是设置一个裁剪区域,不过对于屏幕旋转动画来说,没什么用。
- apply start transaction。start transaction 包含了很多操作,例如, WMCore 中收集的 sync tranction,WMShell 中对 leash 状态的初始化,以及对 leash 层级的初始化,等等。另外,WMCore 会在这个之后,收到 merged sync transaction 的提交回调。
- 在 Handler 线程中执行收集的动画。
- 当所有动画执行完成后,会执行回调,最终调用 Transition#onTransitionFinished() ,并且传递的两个参数都是为 null。
创建并收集旋转动画
// DefaultTransitionHandler.java
// 参数 animations 是根据 transition token 从 DefaultTransitionHandler#mAnimations 获取的 VALUE
// onAnimFinish 是 startAnimation() 中创建的一个回调 onAnimFinish
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
// 1. 创建 ScreenRotationAnimation
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
final ArrayList<Animator> animGroup = new ArrayList<>(3);
final ArrayList<Animator> animGroupStore = new ArrayList<>(3);
// 每一个动画执行完成的回调
final Runnable finishCallback = () -> {
if (!animGroup.isEmpty()) return;
anim.kill();
// 从 DefaultTransitionHandler#mAnimations 的 VALUE 中移除 transition 的所有动画
animations.removeAll(animGroupStore);
// 回调到 startAnimation() 中创建的一个回调 onAnimFinish
onAnimFinish.run();
};
// 2. ScreenRotationAnimation 构建动画,所有构建的动画都保存到参数 animGroup 中
anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
mMainExecutor);
for (int i = animGroup.size() - 1; i >= 0; i--) {
final Animator animator = animGroup.get(i);
// animGroupStore 和 animations 都保存 transition 要执行的所有动画
animGroupStore.add(animator);
animations.add(animator);
}
}
屏幕旋转动画的构建,是由 ScreenRotationAnimation 完成,首先看下它的构造函数
// ScreenRotationAnimation.java
// change 是 DispayContent 的
// t 是 start transaction
// animHint 动画类型是 ROTATION_ANIMATION_ROTATE
ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
mContext = context;
mTransactionPool = pool;
mAnimHint = animHint;
mSurfaceControl = change.getLeash();
mStartWidth = change.getStartAbsBounds().width();
mStartHeight = change.getStartAbsBounds().height();
mEndWidth = change.getEndAbsBounds().width();
mEndHeight = change.getEndAbsBounds().height();
mStartRotation = change.getStartRotation();
mEndRotation = change.getEndRotation();
// 1. 创建截图层的动画 leash
mAnimLeash = new SurfaceControl.Builder(session)
// 截图层 leash 的 parent 是 transition root leash
.setParent(rootLeash)
.setEffectLayer()
.setCallsite("ShellRotationAnimation")
.setName("Animation leash of screenshot rotation")
.build();
try {
if (change.getSnapshot() != null) {
mScreenshotLayer = change.getSnapshot();
// 1.1 把截图层 surface reparent 到 截图层 leash 上
t.reparent(mScreenshotLayer, mAnimLeash);
mStartLuma = change.getSnapshotLuma();
} else {
// ...
}
// 1.2 截图层的动画 leash 显示在最上层
// layer 为 10000 * 200 + 10000
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
// show 截图层 leash
t.show(mAnimLeash);
// 这是对真是图层 DisplayContent leash 进行裁剪
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
// 动画方式为 ROTATION_ANIMATION_CROSSFADE 和 ROTATION_ANIMATION_JUMPCUT 才是自定义动画
if (!isCustomRotate()) {
// 2. 创建一个背景色的 surface
mBackColorSurface = new SurfaceControl.Builder(session)
// 挂在 transition root leash 下
.setParent(rootLeash)
.setColorLayer()
.setOpaque(true)
.setCallsite("ShellRotationAnimation")
.setName("BackColorSurface")
.build();
// layer 为 -1
t.setLayer(mBackColorSurface, -1);
// 设置背景色
// 第二个参数是一个数组,分别代表 r,g,b 的值
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
// start transaction show 背景色 surface
t.show(mBackColorSurface);
}
} catch (Surface.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
// 3. 在 start transaction 中,设置截图层 surface 的转换矩阵
// 使截图层 surface 在新的旋转方向上,能正确显示
setScreenshotTransform(t);
// 5. 立即 apply start transaction
t.apply();
}
ScreenRotationAnimation 的构造函数,做了很多事情
- 创建并显示一个截图层的动画 leash,同时把它挂在 transition root leash 之下,然后把截图层的 surface 挂在它之下,最后把它的 layer 设置的非常高,以保证截图层在动画执行时,显示在最上层。
- 创建一个背景色的 surface,并挂在 transition root leash 下,layer 设置为 -1。这个背景色一般是黑色,所以在执行旋转动画的时候,会看到要给黑色的背景。
- 在 start transaction 中,为截图层 surface 设置一个转换矩阵。由于执行动画的时候,屏幕的旋转方向会立即改变,但是截图层是一个竖屏的大小,如何能正确显示在横屏坐标系上呢?那就需要设置一个转换矩阵。
- 立即 apply start tranaction。 这里我不是很明白,为何这么迫不及待 apply start tranaction ?通常 start tranaction 都是在即将执行动画之前 apply 的。
来看下,如何计算截图层的转换矩阵
// ScreenRotationAnimation.java
private void setScreenshotTransform(SurfaceControl.Transaction t) {
if (mScreenshotLayer == null) {
return;
}
// 计算转换矩阵
final Matrix matrix = new Matrix();
final int delta = deltaRotation(mEndRotation, mStartRotation);
if (delta != 0) {
// Compute the transformation matrix that must be applied to the snapshot to make it
// stay in the same original position with the current screen rotation.
switch (delta) {
// ...
case Surface.ROTATION_270:
matrix.setRotate(270, 0, 0);
matrix.postTranslate(0, mStartWidth);
break;
}
} else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
&& (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
// ...
}
// 从转换矩阵中获取偏移坐标
matrix.getValues(mTmpFloats);
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
// start transaction 中,给截图层设置偏移、缩放、错切
// 其实主要就是偏移和缩放
t.setPosition(mScreenshotLayer, x, y);
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
}
就是计算一个 Matrix,然后在 start transcation 中,对截图层设置偏移和缩放。至于 Matrix 的原理,这就是 app 开发的功底,读者自行去了解,这里不做过多分析。
ScreenRotationAnimation 构造函数并没有创建动画,这个工作是由他的 buildAnimation() 来完成的,如下
// ScreenRotationAnimation.java
boolean buildAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, float animationScale,
@NonNull ShellExecutor mainExecutor) {
if (mScreenshotLayer == null) {
return false;
}
// 自定义旋转动画只有两种 ROTATION_ANIMATION_CROSSFADE 和 ROTATION_ANIMATION_JUMPCUT
final boolean customRotate = isCustomRotate();
if (customRotate) {
// ...
} else {
// Figure out how the screen has moved from the original rotation.
int delta = deltaRotation(mEndRotation, mStartRotation);
switch (delta) { /* Counter-Clockwise Rotations */
// ...
case Surface.ROTATION_270:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.screen_rotate_minus_90_enter);
break;
}
}
// 1.构造旋转退出动画
// 截图层执行的是旋转退出动画
mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
mRotateExitAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateExitAnimation.scaleCurrentDuration(animationScale);
// 2.构造旋转进入动画
// DisplayContent leash 执行的是旋转进入动画
mRotateEnterAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
if (customRotate) {
// ...
} else {
// 3.构建 DisplayContent 的旋转动画
startDisplayRotation(animations, finishCallback, mainExecutor);
// 4.构建截图层的旋转动画
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
}
return true;
}
构建动画的过程非常清晰,首先构造两个动画资源,一个是退出动画,一个是进入动画。然后,利用进入动画资源, DisplayContent leash 构建一个进入动画,利用退出动画资源,为截图层构建一个退出动画。
DisplayContent 的动画代表真实图层的动画,截图层的动画代表假的图层的动画,它们的构建过程如下
// ScreenRotationAnimation.java
// DisplayContent 的旋转动画
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
// mSurfaceControl 是 DisplayContent leash
// DisplayContent 旋转动画使用的进入动画 mRotateEnterAnimation
buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */);
}
// 截图层的旋转动画
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
// 截图层动画操作的截图层动画 leash,也就是 mAnimLeash
// 截图层动画使用的是退出动画 mRotateExitAnimation
buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */);
}
static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Animation anim, @NonNull SurfaceControl leash,
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
@Nullable Rect clipRect) {
// 从池中获取一个 Transaction
final SurfaceControl.Transaction transaction = pool.acquire();
// 1.构建 ValueAnimator,代表一个动画
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
final float[] matrix = new float[9];
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
// 3.ValueAnimator 监听每一帧的绘制,对 surface 执行动画
final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
// 操作 surface
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
};
va.addUpdateListener(updateListener);
// 构建一个Runnable,作为动画执行完成的回调
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
pool.release(transaction);
mainExecutor.execute(() -> {
// 从集合中移除已经完成的动画
animations.remove(va);
// 执行回调
finishCallback.run();
});
};
va.addListener(new AnimatorListenerAdapter() {
private boolean mFinished = false;
@Override
public void onAnimationEnd(Animator animation) {
onFinish();
}
@Override
public void onAnimationCancel(Animator animation) {
onFinish();
}
private void onFinish() {
if (mFinished) return;
mFinished = true;
// 4. 动画执行完成,执行回调的 Runnable
finisher.run();
va.removeUpdateListener(updateListener);
}
});
// 2.保存构建的 ValueAnimator
animations.add(va);
}
所谓的构建动画,其实就是创建 ValueAnimator 来操作 surface。这里我还是要强调一下,真实图层的动画,操作的 DisplayContent leash,具体是 DisplayContent surface 下挂载的 WindowedMagnification:0:31。而截图层动画,并不是直接操作截图层 surface,而是截图层的动画 leash。
现在只是把创建并收集 ValueAnimator,等会就会执行。当 ValueAnimator 执行的时候,会不断操作 surface,如下
// DefaultTransitionHandler.java
private static void applyTransformation(long time, SurfaceControl.Transaction t,
SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
tmpTransformation.clear();
// 获取动画的转换信息
anim.getTransformation(time, tmpTransformation);
// position 为 null
if (position != null) {
// ...
}
// 在 transaction 中设置转换矩阵,透明度
// 注意,这个 transcation 不是 start transaction ,也不是 finish transaction
// 它是从池中获取的一个 Transaction
t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
t.setAlpha(leash, tmpTransformation.getAlpha());
// 参数 immutableClipRect 此时为 null
final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
// ...
}
// 参数 cornerRadius 此时为 0
if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
// ...
}
// transactihon 中设置 Vsync id
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
// apply transaction 之时,就是操作 surface 之时
t.apply();
}
终于,我们看到动画的庐山真面目。所谓的窗口动画,其实就是不断操作窗口 surface 的一个过程,例如,设置转换矩阵(偏移,缩放),设置透明度,等等。
动画执行完成
根据前面的分析,当所有动画执行完成后,最终会调用到 Transitions#onFinish()
// Transitions.java
private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB) {
// 获取当前正在执行的 transition 的 track
final Track track = mTracks.get(active.getTrack());
if (track.mActiveTransition != active) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
+ " a handler didn't properly deal with a merge. " + active,
new RuntimeException());
return;
}
// 1. 重置 Track#mActiveTransition
track.mActiveTransition = null;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ "(aborted=%b), notifying core %s", active.mAborted, active);
// 2.清理 start transaction
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
active.mStartT.clear();
}
// 3. apply finish transaction
SurfaceControl.Transaction fullFinish = active.mFinishT;
// 不考虑 merged transition 的情况
if (active.mMerged != null) {
// ...
}
if (fullFinish != null) {
fullFinish.apply();
}
// 4. 释放 TransitionInfo 中引用的 surface
// 也就是 截图层 surface 以及 transition root leash surface
releaseSurfaces(active.mInfo);
// 5.通知 WMCore 动画完成
// 参数 wct 和 wctCB 都是 null
mOrganizer.finishTransition(active.mToken, wct, wctCB);
if (active.mMerged != null) {
// ...
}
// 6. 继续处理 ready queue 中下一个 transition
processReadyQueue(track);
}
WMShell 处理 finish transition
- 重置 Track#mActiveTransition,表明 Tack 中没有正在执行的动画。
- clear start tranction。从注释中可以了解到,是为了清理一些引用(surface 引用?)。
- apply finish transaction。finish transaction 保存的是一些针对动画目标 leash 的重置操作,例如 reparent 到原本的 parent surface 之下,更新 layer 等等。
- 释放 TransitionInfo 中引用的截图层 surface,以及 transtion root leash surface。注意,这里 WMShell 中的释放引用的操作,WMCore 中其实也有释放引用的操作。
- 通知 WMCore 动画执行完成。
- 继续处理 Track 的 ready queue 中的 transition。
WMCore 处理 finish transition
动画执行完成后,WMShell 会通知 WMCore,最终会执行 Transition#finishTransition(),如下
// Transition.java
void finishTransition() {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
asyncTraceEnd(System.identityHashCode(this));
}
// 记录 finish transition 的日志
mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
mController.mLoggerHandler.post(mLogger::logOnFinish);
mController.mTransitionTracer.logFinishedTransition(this);
// 1.清理 ST,FT, CT
if (mStartTransaction != null) mStartTransaction.close();
if (mFinishTransaction != null) mFinishTransaction.close();
mStartTransaction = mFinishTransaction = null;
if (mCleanupTransaction != null) {
mCleanupTransaction.apply();
mCleanupTransaction = null;
}
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
}
// 标记正在 finishing 的 transition
mController.mFinishingTransition = this;
if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
// ...
}
boolean hasParticipatedDisplay = false;
boolean hasVisibleTransientLaunch = false;
boolean enterAutoPip = false;
boolean committedSomeInvisible = false;
// Commit all going-invisible containers
for (int i = 0; i < mParticipants.size(); ++i) {
final WindowContainer<?> participant = mParticipants.valueAt(i);
// ...
// 旋转动画有 DisplayContent 参与
if (participant.asDisplayContent() != null) {
hasParticipatedDisplay = true;
continue;
}
// ...
// ...
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
// 2.通知 AsyncRotationController 动画完成了
if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
asyncRotationController.onTransitionFinished();
}
// ...
}
// ...
// 3.Transition 状态切换到 STATE_FINISHED
mState = STATE_FINISHED;
// 4. 执行推迟的旋转动画
if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
// ...
// finishing transition 已经处理完成
mController.mFinishingTransition = null;
}
其实这里的代码量非常大,但是对于屏幕旋转动画来说,只有最后两步有点价值,但是本文不会对这两步进行分析。其他几步,看看就行。
结束
写屏幕旋转动画的文章,对于我的时间和精力来说,消耗巨大,让我非常吃不消,甚至让我想放弃。但是,我本着精益求精的精神,不断斟酌每一处细节,力求让自己理解更透彻,因此才坚持下来。
说个比较嚣张的话,我的屏幕旋转动画系列的文章,是全网独一无二的,可能只有从培训机构,花钱才能获取到。但是,要消化掉这些内容,也不是那么容易,需要读者有扎实的基本功,还要有强大的毅力。
OK,为了分析的严谨性,当我斟酌到新的细节时,我会不定期更新这系列的文章。祝各位读者看的愉快,看得忘我,但是不要忘记点赞收藏,bye~