属性动画的启动分析
在本文中,我们会分析属性动画如何启动的而且和Andoid 黄油计划
有什么关系
我们看看 ObjectAnimator.start()
1 | public void start() { |
AnimationHandler.getInstance().autoCancelBasedOn(this)
cancel 相同Target和相同属性的动画
AnimationHandler 实例在线程局部单例。autoCancelBasedOn(this)
会遍历 AnimationHandler
实例持有的所有未完成的 ValueAnimator
实例,cancel 掉符合条件的动画。
紧接着 super.start()
调用了ValueAnimator.start()
1 |
|
调用了带参数的 start
方法
1 | private void start(boolean playBackwards) { |
我们略去与主流程无关的逻辑代码。这个方法标记了动画的状态,如
1 | mStarted = true; |
然后这个方法结束啦,有没有很疑惑?有没有很懵逼?动画怎么执行的?什么时候执行的?
要解答这问题,我们还是要分析 这两行不起眼的代码
1 | AnimationHandler animationHandler = AnimationHandler.getInstance(); |
第一行在当前线程获取 AnimationHandler
实例;
第二行注册了一个callback。
1 | /** |
这两行代码 做了一件事情:保证一个ValueAnimator
对象只向Provider注册一次 mFrameCallback
瞅瞅 getProvider
是啥?
1 | private AnimationFrameCallbackProvider getProvider() { |
创建了一个 MyFrameCallbackProvider
实例, MyFrameCallbackProvider
继承 AnimationFrameCallbackProvider
1 | public interface AnimationFrameCallbackProvider { |
AnimationFrameCallbackProvider
接口定义了一些回调接口,按照注释说明主要作用是 提高 ValueAnimator
的可测性,通过这个接口隔离,我们可以自定义 定时脉冲,而不用使用系统默认的 Choreographer
,这样我们可以在测试中使用任意的时间间隔的定时脉冲.既然可以方便测试,那肯定有API来更改Provider 吧?
果不其然,我们猜对了。
1 | public void setProvider(AnimationFrameCallbackProvider provider) { |
ValueAnimator
提供了一个 setProvider
通过自定义的Provider 提供我们想要的任意时间间隔的回调,来更新动画。
明白了 接口AnimationFrameCallbackProvider
的作用,也知道了一个新的名词Choreographer
,它就是 Android 黄油计划 的核心。使用vsync(垂直同步)来协调View的绘制和动画的执行间隔。关于 Choreographer
在文章最后会做进一步解释
我们知道了默认情况下系统使用Choreographer
,我们可以简单的认为 它是一个与 绘制和动画有关的 消息处理器 。
继续我们的 代码 AnimationHandler.addAnimationFrameCallback
1 | public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { |
执行了 getProvider().postFrameCallback(mFrameCallback)
通过上面的分析我们知道 getProvider()
得到的是一个MyFrameCallbackProvider
1 | /** |
注释说明系统默认使用的 Choreographer
做定时脉冲来协调 frame 的更新 。
MyFrameCallbackProvider.postFrameCallback()
方法调用了mChoreographer.postFrameCallback(callback)
这里说明一下 Choreographer 实例也是线程局部单例的。从这些信息中我们知道了动画可以在子线程中执行的(注意:这不意味着可以在子线程更新UI),但是这个子线程必须有Looper。
接着分析 Choreographer.postFrameCallback
方法
1 | public void postFrameCallback(FrameCallback callback) { |
默认情况下USE_VSYNC
为true,当然设备厂商也可以设置为 false。如果 为false 会以 10 ms 为间隔计算下一次 doFrame 的时间,然后使用Handler来处理。
我们看看 scheduleVsyncLocked()
这行代码做了何事?
1 | private void scheduleVsyncLocked() { |
mDisplayEventReceiver
当USE_VSYNC
为true 时 实例化成 FrameDisplayEventReceiver
对象 ,它主要是和 native层 做交互,协调 vsync 信号。
mDisplayEventReceiver.scheduleVsync()
请求当下一帧开始时同步vsync信号。
当vsync 信号来时 回调 JNI 层会回调DisplayEventReceiver.dispatchVsync
方法
1 | // Called from native code. |
FrameDisplayEventReceiver
实现了doVsync
方法
1 | private final class FrameDisplayEventReceiver extends DisplayEventReceiver |
主要实现:根据JNI层的 timestampNanos
纳秒值计算成毫秒,通过 Handler 执行
Runable
,即执行了doFrame(mTimestampNanos, mFrame);
这行代码。
1 | void doFrame(long frameTimeNanos, int frame) { |
与动画相关的关键代码:
1 | mFrameInfo.markAnimationsStart(); |
以下是 doCallbacks的实现,有没有感觉即将看见胜利的曙光?
1 | void doCallbacks(int callbackType, long frameTimeNanos) { |
所以该方法最核心的功能是找到需要执行的 CallbackRecord
链表,然后循环执行 它们的 run
方法。
1 | private static final class CallbackRecord { |
在前面 注册回调postFrameCallbackDelayed
时 token 是 FRAME_CALLBACK_TOKEN
所以执行 run 方法中的第一个if 分支((FrameCallback)action).doFrame(frameTimeNanos);
回调 FrameCallback.doFrame(frameTimeNanos)
方法。
下面是 Choreographer.FrameCallback
的实现
1 | private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { |
看到doAnimationFrame(getProvider().getFrameTime());
这一行代码,我们已经确认,我们的动画逻辑即将开始了。
我们先搁置doAnimationFrame
这行代码,先分析完 doFrame
回调。
1 | if (mAnimationCallbacks.size() > 0) { |
这里如果 mAnimationCallbacks.size >0
会再次将该 callback 注册给 Choreographer
。为什么还要注册一次呢?之前不是注册过一次了吗?难道Choreographer
把这个callback 释放了。
我们回到Choreographer.doCallbacks
方法。
有一段 final 括号体中的代码我们没有分析
1 | finally { |
这里调用了recycleCallbackLocked(callbacks);
来回收整个链表中的节点记录。
1 | private void recycleCallbackLocked(CallbackRecord callback) { |
当 mAnimationCallbacks.size >0
时 需要再次添加回调,以便来获取下一帧的回调。
当动画 paused 或 end 后 mAnimationCallbacks 会相应的remove callback。
小结1
到目前为止 我们知道了当调用 ObjectAnimator.start()
后:
- 取消之前的动画对相同属性,相同target的动画,防止出现多个动画同时更新 Target 的属性,出现错乱。不过这一行为默认是关闭的,设置
ObjectAnimator.setAutoCancel(true)
来打开; - 执行
ValueAnimator.start()
方法; AnimationHandler.addAnimationFrameCallback
向Choreographer
注册Choreographer.FrameCallback
回调,通过该回调获得渲染时间脉冲的回调;- 通过系统的
vsync
垂直同步信号来协调 cpu,gpu 和渲染的时序; Choreographer
获得vsync
信号后 根据 当前帧的纳秒
来查找哪些Choreographer.FrameCallback
会被执行。- 执行
AnimationHandler.doAnimationFrame()
方法,开始真正的动画逻辑。
动画属性更新
刚才我们分析到 AnimationHandler.doAnimationFrame()
方法,现在看看这个方法的功能是什么?
1 | private void doAnimationFrame(long frameTime) { |
该方法主要作用是遍历所有的 AnimationFrameCallback
。 这个AnimationFrameCallback
是什么?什么时候添加到mAnimationCallbacks
的?
刚才分析Choreographer.FrameCallback.doFrame
时提到过,在动画paused 或 end 时会将AnimationFrameCallback
从mAnimationCallbacks
中移除,如果mAnimationCallbacks
的size 为0 就不再向Choreographer
注册 callback。也就代表没有动画要被执行了。
其实 AnimationFrameCallback
就是 ObjectAnimator
或ValueAnimaor
本身。我们看看 ValueAnimator
的定义:
1 | public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback { |
所以 这些AnimationFrameCallback
就是那些待执行的 属性动画
。
接下来看看isCallbackDue(callback, currentTime)
这个 if
判断:
1 | private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) { |
根据 当前的的 纳秒时间 判断 动画是否需要执行,因为有些动画做了delay
可能在当前的frame 窗口不需要执行。callback.doAnimationFrame(frameTime);
这行代码 开始回调 ValueAnimator.doAnimationFrame
方法。然后判断if (mCommitCallbacks.contains(callback))
为 true的话会再次向Choreographer
注册一个Runnable
的callback,当下一个 frame 时间到来时 执行 Runnable
。
我们关注重点代码callback.doAnimationFrame(frameTime)
也即是ValueAnimator.doAnimationFrame(frameTime)
1 | public final void doAnimationFrame(long frameTime) { |
当时第一帧动画时 会调整到下一个 frame 窗口执行。如果这个动画是 delay 动画,会执行startAnimation()
初始化动画,标记动画正在 mRunning
,并且 对PropertyValuesHolder
执行初始化操作–主要就是初始化估值器
。
下面分析 boolean finished = animateBasedOnTime(currentTime);
返回值 finished
标记是否动画已经执行完毕。如果最后一个关键帧(Keyframe)执行完毕,这里返回true,会执行endAnimation()
做一些状态位复位和动画结束回调等等。
1 | boolean animateBasedOnTime(long currentTime) { |
currentTime
是Choreographer
发出的计时脉冲时间,纳秒计时。
根据 currentTime
计算 fraction
系数,即动画时间流逝比。
然后执行animateValue(currentIterationFraction)
计算动画的在当前时间比例下属性动画的值,如果是 ObjectAnimator
还会降属性值设置该Target
。animateValue
方法被 ObjectAnimator
重载了。我们先分析ValueAnimator. animateValue(fraction)
然后再分析ObjectAnimator. animateValue(fraction)
1 | void animateValue(float fraction) { |
参数 fraction
这个时间流逝比系数 是线性的。通过mInterpolator.getInterpolation()
计算出我们想要的fraction。然后使用这个系数计算 PropertyValuesHolder.calculateValue(fraction)
。
计算完 属性值后 执行 mUpdateListeners
的更新操作。到目前为止我们终于知道我们经常使用的 AnimatorUpdateListener.onAnimationUpdate()
何时执行的了。
我们再分析 被 ObjectAnimator
重载后的animateValue(fraction)
方法
1 | void animateValue(float fraction) { |
主要功能是 通过调用 super.animateValue()
计算属性的值。然后调用PropertyValuesHolder.setAnimatedValue(Object)
来更新属性值到对应的Target
上。
深入理解Android属性动画的实现-1 这篇文章中介绍过 PropertyValuesHolder
具有更新属性的能力,也持有关键帧
的引用。
1 | void setAnimatedValue(Object target) { |
通过 Property
更新属性值。如果之前是通过 propertyName
来初始化的动画,这里通过 mSetter
来反射调用 set 方法,更新属性值。
深入理解Android属性动画的实现-1 这篇文章还介绍了如何通过fraction
计算动画的属性值。这里就不在赘述。
总结
到目前位置我们已经分析完 属性动画的创建—> 属性动画的启动 –> 属性的计算和更新。
这里我们再回顾一下:
ObjectAnimator.start()
开始启动动画;- 向
Choreographer
注册callback; Choreographer
获得vsync
垂直同步信号量后回调Choreographer.FrameCallback.doFrame()
执行逻辑进入到AnimationHandler
中;AnimationHandler
持有AnimationFrameCallback
也即是ValueAnimator
,然后执行ValueAnimator.doAnimationFrame(time)
;ValueAnimator.animateBasedOnTime(time)
执行,通过TimeInterpolator
计算最终的 时间流逝比fraction
,然后调用PropertyValuesHolder.calculateValue(fraction)
计算属性的值,并回调AnimatorUpdateListener.onAnimationUpdate()
方法。PropertyValuesHolder
调用Keyframes.getIntValue(fraction)
,这中间又使用到估值器TypeEvaluator
和Keyframe
最终结算处我们需要的属性值。- 然后
ObjectAnimator
调用PropertyValuesHolder.setAnimatedValue(target)
来更新target
的属性值。
附加知识点
文章中提到过Choreographer
和Android黄油计划
。其实在本文的流程分析中已经简单分析了 Choreographer
在动画类型上的执行流程:
- 创建
DisplayEventReceiver
的子类FrameDisplayEventReceiver
来与JNI 层交互。JNI层的vsync
信号量通过callback 这个类的dispatchVsync
方法来告诉应用层 可以开始新的一帧的渲染了。 Choreographer
接收到vsync
信号后执行doFrame(frameTimeNanos,frame)
方法,对三个支持的类型CALLBACK_INPUT
,CALLBACK_ANIMATION
,CALLBACK_TRAVERSAL
做相应的回调。
需要说明下: _源码中还有一个类型CALLBACK_COMMIT
主要处理注册 commit Runnable的需求,即延迟一个frame 的需求。因为它是一种业务辅助,不像其它三种,有明显的业务支持。所以在本文中倾向说三种支持类型,请知悉_。- 回调进入
Choreographer.FrameCallback.doFrame(timeNanos)
- 然后进入到业务层,例如 :
CALLBACK_ANIMATION
类型进入到AnimationHandler.mFrameCallback
;CALLBACK_TRAVERSAL
类型进入到ViewRootImpl
执行scheduleTraversals()
进而执行了View.layout
,View.measure
,View.draw
方法开启View的渲染操作;CALLBACK_INPUT
这个类型相比前两个特殊一些,因为输入事件由另一个引擎负责。让输入引擎产生输入事件后不是立刻在 视图层产生响应。而是要等待下一个vsync
垂直同步信号,跟着统一的时钟脉冲来响应。所以 在ViewRootImpl
中会使用到mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,mConsumedBatchedInputRunnable, null)
。具体这里不再展开描述。
简言之,Choreographer
就是一个消息处理器,根据 vsync
垂直同步信号 来处理三种支持类型的回调。
至于 Android黄油计划(Project Butter)
Choreographer
只是其中一个重要的特性。还有其他方便的优化。例如 引入了三重缓冲
和 JNI层的vsync
。至于 vsync
的好处以及和 该计划之前 的Android的渲染相比 请参考Android 4.4 Graphic系统详解(2) VSYNC的生成 这篇优质文章。在这里也对该篇文章的愿作者致敬。