动画分析

我首先说哈,可能有人说我写的很烂,很不清楚或者很难懂,等于没讲,哈哈 我这里讲的可能对有些人来说写的好浅显易懂,对有些人只想知道各种细节的人就有些失望了,因为我一贯的思路都是看主干不看细节。有了主干 当想做某件事的时候才会去主干的某一个枝节细看。

android 内有很多类型 这里主要讲过度动画和窗口动画的实现原理,包括动画如何启动以及后续如何更新帧数据。

android 分了各种动画 比如过度动画或者窗口动画 然后过度动画又分了好多种什么Activity 打开动画 关闭动画等等,这里不要被这些花里胡哨的分类给搞昏了头,这里不管分了多少类,其实也只是区别场景而已,比如是app内子activity打开就为TRANSIT_OLD_ACTIVITY_OPEN ,然后应用间切换 其实本质也就是TASK 间切换 就为TRANSIT_OLD_TASK_OPEN(当然还有其他的 这里就只是举一个说明一下 不要咬文嚼字。 这些分类只是为了区分场景而已,不影响动画本身的实现。说到底过度动画和窗口动画实现的本质是一样的。

说了这些就来看一下这个动画实现的本质吧。我记忆力不好,一般我会去把问题看清他的实质,不去记忆一些细节,反正也记不住。

为了说清楚这个动画 我接下来会从下面几个方面来详细说:

动画作用对象

也就动画的主体对象如何启动动画动画的帧回调如何实现的(也就是动画启动后如何持续跑起来直到结束)每一帧数据如何更新

这几个问题说明了 一个动画也就出来了对吧。

首先看动画作用对象吧:

无论是过渡动画还是窗口动画,实质上都是各个窗口容器(如 WindowState ActivityRecord)对自己的WindowContainer对应的SurfaceControl应用动画,然而直接对SurfaceControl做动画 又会存在不稳定的情况,因为动画线程一般不会持有WMS 全局锁(就算持有也不合理,主线程去等各种动画?那还不把system卡死)。也会导致同步困难,如我动画还没做完,systemServer线程就对SurfaceControl做移除相关操作,那就GG了。所以android引入对自己的WindowContainer对应的SurfaceControl引入leash SurfaceControl,动画作用在leash之上,这个leash 很简单就是在各个窗口容器和他的父亲节点之间插入一个节点(简单的说就是在链(本质上是树)上插入一个节点。

好再来看如何启动动画:

过度动画一般是作用在Task ,windowState ,activityrecord等WindowContainer

要启动动画就直接来看WindowContainer 的startAnimation 吧

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,

@AnimationType int type,

@Nullable OnAnimationFinishedCallback animationFinishedCallback) {

if (DEBUG_ANIM) {

Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + 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,

mSurfaceFreezer);

}

其实就是mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,

mSurfaceFreezer);

看看定义

/**

* Starts an animation.

*

* @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the

* component responsible for running the animation. It runs the animation with

* {@link AnimationAdapter#startAnimation} once the hierarchy with

* the Leash has been set up.

* @param hidden Whether the container holding the child surfaces is currently visible or not.

* This is important as it will start with the leash hidden or visible before

* handing it to the component that is responsible to run the animation.

* @param animationFinishedCallback The callback being triggered when the animation finishes.

*/

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,

@AnimationType int type,

@Nullable OnAnimationFinishedCallback animationFinishedCallback,

@Nullable SurfaceFreezer freezer) {

额这些乱七八糟的说明,看不懂也没关系,可以不看 哈哈,我来说吧

Transaction t

这个Transaction 主要是负责保存作用在SurfaceControl的第一帧数据,并在随后适合的时机下发给SurfaceFlinger,了解即可

AnimationAdapter anim

这个比较重要,算是核心了,这个就是动画实际的帧处理(也可以说是实际动画的帧处理回调)也就是后面要说的第四点

int type

这个就是前面说的定义的各种动画类型了,不做过多说明了,没啥意义 ,为了区分动画场景,随你怎么理解了。

其他几个参数就不说了,这里你只需要知道一个关键参数anim 即可,这里这个虽然是anim,实际上他只是一个帧数据处理。我觉得这么叫更为合理。

好,我们继续

下一步就是

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,

@AnimationType int type,

@Nullable OnAnimationFinishedCallback animationFinishedCallback,

@Nullable SurfaceFreezer freezer) {

cancelAnimation(t, true /* restarting */, true /* forwardCancel */);

mAnimation = anim;

mAnimationType = type;

mAnimationFinishedCallback = animationFinishedCallback;

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;

if (mLeash == null) {

/**/ *重点:创建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) {

if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");

return;

}

// 继续start

mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);

}

就是说先创建动画的操作对象leash surfacecontrol

紧接着就是调用前面我们说的那个AnimationAdapter anim的 startAnimation

AnimationAdapter是个接口类,像窗口动画和过度动画大部分场景,其实现类为LocalAnimationAdapter

以窗口动画来说吧,前面为了简单,我们直接从WindowContainer的startAnimation来切入的

主要是为了不去看各个子类(WindowState, Task 等)在启动的时候各种乱七八糟的处理,我们既然是为了看清一个事情的本质,所谓本质我个人的理解就是事物的核心主干,不管一个人的皮囊各式各样,但是他的骨架大致都是一样的,只留下这个骨架我们才能不负重前行,血肉都抛弃了吧,需要的时候我们自己再为骨架添加血肉。

来看看WinddowState启动一个动画 其实主要就是创建一个LocalAnimationAdapter ,这个你也可以实现不同的LocalAnimationAdapter,我们只讲源生的了。

其需要初始化一个AnimationSpec 这里是一个WindowAnimationSpec 这个也是可以根据自己动画需要自己去实现不同的AnimationSpec

void startAnimation(Animation anim) {

// If we are an inset provider, all our animations are driven by the inset client.

if (mControllableInsetProvider != null) {

return;

}

final DisplayInfo displayInfo = getDisplayInfo();

anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),

displayInfo.appWidth, displayInfo.appHeight);

anim.restrictDuration(MAX_ANIMATION_DURATION);

anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());

final AnimationAdapter adapter = new LocalAnimationAdapter(

new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,

0 /* windowCornerRadius */),

mWmService.mSurfaceAnimationRunner);

startAnimation(getPendingTransaction(), adapter);

commitPendingTransaction();

}

我们就来看就是调用

private final SurfaceAnimationRunner mAnimator;

private final AnimationSpec mSpec;

@Override

public void startAnimation(SurfaceControl animationLeash, Transaction t,

@AnimationType int type, OnAnimationFinishedCallback finishCallback) {

mAnimator.startAnimation(mSpec, animationLeash, t,

() -> finishCallback.onAnimationFinished(type, this));

}

其就调用了SurfaceAnimationRunner (大部分过度动画都是共用的 mWmService.mSurfaceAnimationRunner)的startAnimation 参数传入AnimationSpec(以WindowAnimationSpec为例)

好了简单了,来看SurfaceAnimationRunner

void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,

Runnable finishCallback) {

synchronized (mLock) {

final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,

finishCallback);

mPendingAnimations.put(animationLeash, runningAnim);

if (!mAnimationStartDeferred) {

mChoreographer.postFrameCallback(this::startAnimations);

}

// Some animations (e.g. move animations) require the initial transform to be applied

// immediately.

applyTransformation(runningAnim, t, 0 /* currentPlayTime */);

}

}

好了简单了,只做了三件事

对即将启动的动画加入列表 mPendingAnimations.put(animationLeash, runningAnim);(实际上动画还没启动)

mPendingAnimations.put(animationLeash, runningAnim);

applyTransformation(runningAnim, t, 0 /* currentPlayTime */);

如注释所说就是首先应用动画初始状态,也可以叫初始帧数据,随你怎么叫了,就是第一帧数据这里就设置了。

然后注册了个回调mChoreographer.postFrameCallback(this::startAnimations); 去真正的启动动画

mChoreographer 就是Choreographer ,这个对于熟悉动画的应该知道吧,简单可以理解为vsync的接收和处理回调的即可,动画本质上主要就是向Choreographer 注册回调,待vsync来了后在回调注册的回调去处理动画帧数据,反复循环,直到动画结束。

好了,再看看如何真正启动动画吧:

@GuardedBy("mLock")

private void startAnimationLocked(RunningAnimation a) {

final ValueAnimator anim = mAnimatorFactory.makeAnimator();

// Animation length is already expected to be scaled.

anim.overrideDurationScale(1.0f);

anim.setDuration(a.mAnimSpec.getDuration());

anim.addUpdateListener(animation -> {

synchronized (mCancelLock) {

if (!a.mCancelled) {

final long duration = anim.getDuration();

long currentPlayTime = anim.getCurrentPlayTime();

if (currentPlayTime > duration) {

currentPlayTime = duration;

}

applyTransformation(a, mFrameTransaction, currentPlayTime);

}

}

// Transaction will be applied in the commit phase.

scheduleApplyTransaction();

});

anim.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationStart(Animator animation) {

synchronized (mCancelLock) {

if (!a.mCancelled) {

// TODO: change this back to use show instead of alpha when b/138459974 is

// fixed.

mFrameTransaction.setAlpha(a.mLeash, 1);

}

}

}

@Override

public void onAnimationEnd(Animator animation) {

synchronized (mLock) {

mRunningAnimations.remove(a.mLeash);

synchronized (mCancelLock) {

if (!a.mCancelled) {

// Post on other thread that we can push final state without jank.

mAnimationThreadHandler.post(a.mFinishCallback);

}

}

}

}

});

a.mAnim = anim;

mRunningAnimations.put(a.mLeash, a);

anim.start();

if (a.mAnimSpec.canSkipFirstFrame()) {

// If we can skip the first frame, we start one frame later.

anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);

}

// Immediately start the animation by manually applying an animation frame. Otherwise, the

// start time would only be set in the next frame, leading to a delay.

anim.doAnimationFrame(mChoreographer.getFrameTime());

}

可以看到实际上是启动了个ValueAnimator 继承自Animator,这才是真正的动画发动机(哈哈也许这样说有人反驳,他并不是真正的发动机,因为里面的AnimationHander才是,无所谓了你开心就好,我就要这么讲),就是我们下面要讲的第三点。待会讲

所以启动动画最终就是启动一个Animator(哈哈 PS 竟然不是SurfaceAnimationRunner,我最初一直以为是SurfaceAnimationRunner,不好意思 我最初没怎么了解动画具体实现,所以开始我一直想在SurfaceAnimationRunner里面找到动画的持续回调(也就是第三点要讲的动画过程),然而并找不到,而且我也疑惑这里不是应该可以处理么?那为什么源生没有在这处理,开始我也很奇怪干嘛还要有ValueAnimator,其实啊这么设计是有道理的SurfaceAnimationRunner主要是负责启动动画的帧同步,另一方面如果所有的动画回调都在这处理,性能你能保证么?所以就有了ValueAnimator去处理各种的动画,个人理解哈 可能并不完全正确,但是不影响这个动画实现流程说明)。

然后呢

anim.addUpdateListener(animation -> {

synchronized (mCancelLock) {

if (!a.mCancelled) {

final long duration = anim.getDuration();

long currentPlayTime = anim.getCurrentPlayTime();

if (currentPlayTime > duration) {

currentPlayTime = duration;

}

applyTransformation(a, mFrameTransaction, currentPlayTime);

}

}

// Transaction will be applied in the commit phase.

scheduleApplyTransaction();

});

注册一个帧处理回调

applyTransformation(a, mFrameTransaction, currentPlayTime); 这玩意就是我们要说的第四点。

待会讲。

突然不想讲了,因为到这了基本大部分人应该都明白了对吧。

哈哈还说说吧,那第三点动画怎么循环跑起来

那就得看ValueAnimator 的start

private void start(boolean playBackwards) {

if (Looper.myLooper() == null) {

throw new AndroidRuntimeException("Animators may only be run on Looper threads");

}

mReversing = playBackwards;

mSelfPulse = !mSuppressSelfPulseRequested;

// Special case: reversing from seek-to-0 should act as if not seeked at all.

if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {

if (mRepeatCount == INFINITE) {

// Calculate the fraction of the current iteration.

float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));

mSeekFraction = 1 - fraction;

} else {

mSeekFraction = 1 + mRepeatCount - mSeekFraction;

}

}

mStarted = true;

mPaused = false;

mRunning = false;

mAnimationEndRequested = false;

// Resets mLastFrameTime when start() is called, so that if the animation was running,

// calling start() would put the animation in the

// started-but-not-yet-reached-the-first-frame phase.

mLastFrameTime = -1;

mFirstFrameTime = -1;

mStartTime = -1;

addAnimationCallback(0);

if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {

// If there's no start delay, init the animation and notify start listeners right away

// to be consistent with the previous behavior. Otherwise, postpone this until the first

// frame after the start delay.

startAnimation();

if (mSeekFraction == -1) {

// No seek, start at play time 0. Note that the reason we are not using fraction 0

// is because for animations with 0 duration, we want to be consistent with pre-N

// behavior: skip to the final value immediately.

setCurrentPlayTime(0);

} else {

setCurrentFraction(mSeekFraction);

}

}

}

这里面一个关键是调用了addAnimationCallback

private void addAnimationCallback(long delay) {

if (!mSelfPulse) {

return;

}

getAnimationHandler().addAnimationFrameCallback(this, delay);

}

哈哈 AnimationHandler出场了,没错他才是真正负责Choreographer ,发动机的主件(哈哈我是把他看成发动机的一部分了,无所谓了这玩意你想怎么理解就怎么理解,你说他是发动机也行,对吧,这玩意本身就是个定义,自己觉得怎么样对就怎么样定义吧)。

看一眼吧:

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {

if (mAnimationCallbacks.size() == 0) {

getProvider().postFrameCallback(mFrameCallback);

}

if (!mAnimationCallbacks.contains(callback)) {

mAnimationCallbacks.add(callback);

}

if (delay > 0) {

mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));

}

}

这里就是getProvider().postFrameCallback(mFrameCallback); 不继续说了,有兴趣的自己看,我就说他是向Choreographer 注册回调了

那这里就是注册一个回调,如何让他循环起来呢,秘密就在mFrameCallback

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {

@Override

public void doFrame(long frameTimeNanos) {

doAnimationFrame(getProvider().getFrameTime());

if (mAnimationCallbacks.size() > 0) {

getProvider().postFrameCallback(this);

}

}

};

这个就是一旦你把回调注册进来了,不移除,hander就会自动给你注册下一次回调。

好了第三点也出来了

第四点就很简单了

前面讲第二点的时候,也就是这里的doAnimationFrame ,就会回调第二点注册的addUpdateListener

最终也就是回调

SurfaceAnimationRunner 里的

private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {

a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);

}

其实也就是前面说的WindowAnimationSpec 的apply,(额不一定是WindowAnimationSpec,严格说就是AnimationSpec哈哈,我相信你懂得)。

就看一眼WindowAnimationSpec的apply呗

@Override

public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {

final TmpValues tmp = mThreadLocalTmps.get();

tmp.transformation.clear();

mAnimation.getTransformation(currentPlayTime, tmp.transformation);

tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);

t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);

t.setAlpha(leash, tmp.transformation.getAlpha());

boolean cropSet = false;

if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {

if (tmp.transformation.hasClipRect()) {

t.setWindowCrop(leash, tmp.transformation.getClipRect());

cropSet = true;

}

} else {

mTmpRect.set(mRootTaskBounds);

if (tmp.transformation.hasClipRect()) {

mTmpRect.intersect(tmp.transformation.getClipRect());

}

t.setWindowCrop(leash, mTmpRect);

cropSet = true;

}

float cornerRadius = mWindowCornerRadius;

if (mActivityThumbnailHelper != null) {

final float curScaleX = tmp.floats[Matrix.MSCALE_X];

final float curScaleY = tmp.floats[Matrix.MSCALE_Y];

float scale = Math.max(curScaleX, curScaleY);

float thumbLeashCornerRadius = cornerRadius;

boolean isScaledThumbnail =

if (scale != 0.0f) {

cornerRadius /=scale;

}

if (scale != 0.0f && isScaledThumbnail) {

thumbLeashCornerRadius /= scale;

}

if (hasScaleWithClipAnimation) {

mActivityThumbnailHelper.stepScaleUpDownAnimation(t, tmp.transformation, isScaledThumbnail);

}

final SurfaceControl tempLeash = mActivityThumbnailHelper.getLeash();

if (tempLeash != null && thumbLeashCornerRadius > 0.0f ) {

t.setCornerRadius(tempLeash, thumbLeashCornerRadius);

}

}

// END

// We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,

// since it doesn't have anything it's relative to.

}

嗯 简单 了,就是将private Animation mAnimation; 计算出来的数据设置给leash完事了

这样动画就跑起来了对吧。

好吧重点来了,总结下吧,我的简单说:

我就喜欢把这些玩意用自己的一句话说清楚,

不管是过度动画还是窗口动画就是就是把你定义的Animation,以AnimationSpec的形式封装,并创建对应的动画操作leash,然后通过SurfaceAnimationRunner启动一个ValueAnimator让你的动画跑起来就完事了,是不是相当简单。

额 所以问题就来了,窗口或者过度动画本质上是SurfaceCtrol动画,而且动画实质上和窗口以及SurfaceAnimationRunner 没任何鸟关系,我们只需要Animation ValueAnimator 以及leash 其实就完事了。甚至说只需要ValueAnimator 和leash 就完事了 因为ValueAnimator 和Animation 可合并。哈哈 是不是就是常见的动画结构

哈哈 是不是说成这样 觉得尼玛,这只剩下骨头确实有点丑了对吧,啥都不剩了。哈哈 本来就这样 事情你看透了 ,就啥也不是了。