目录
属性动画简介
什么是属性动画
属性动画的基本模型
android 属性动画使用示例
ValueAnimator
ObjectAnimator
ViewPropertyAnimator
属性动画的执行流程
动画循环
代码实现
帧刷新机制
Android Display系统的工作方式
早期帧刷新机制
优化:Project Butter
代码实现
根据动画已播放时长计算属性值
线性动画示例
非线性动画示例
时间插值器
类型估值器
代码实现
设置属性值
代码实现
界面绘制
Android 绘制模型
总结
Android属性动画实现原理
兼容性问题
硬件加速
内存泄漏
参考文档
Choreographer相关
属性动画
硬件加速
顾名思义,属性动画就是通过改变对象的属性做动画
想象一个自由落体运动,在下落过程中,从最高点落到最低点,物体的位置不断变化,物体其他属性没有发生变化。我们要模拟自由落体运动,就可以通过改变物体的位置来实现这个动画效果。在这个例子中,位置就是物体的一个属性,动画效果被抽象成位置属性的连续变化,属性控制了画面的显示效果。通过这种方式制作的动画就称为属性动画
一个属性动画通常由以下基本元素构成:
Android属性动画是在Android 3.0 之后出现的,属性动画系统是一个功能强大的框架,可用于为几乎任何对象添加动画效果。属性动画会在指定时长内更改属性(对象中的字段)的值
Android 属性动画支持定义动画的以下特性:
要为对象属性添加动画效果,需要指定要添加动画效果的对象属性,例如对象在屏幕上的位置、动画效果持续多长时间以及要在哪些值之间添加动画效果
Android提供了一组属性动画API,可以方便的创建属性动画。属性动画的使用方法按照封装程度从低到高(自由度从高到低)依次为ValueAnimator、ObjectAnimator、ViewPropertyAnimator三种,其中ValueAnimator是核心,ObjectAnimator继承于ValueAnimator,ViewPropertyAnimator 内部采用ValueAnimator 实现动画
ValueAnimator是一个数值发生器,它并不会直接改变属性的值,而是用来产生随动画进度变化的数值,间接控制动画的实现过程。我们需要做的就是监听这些值的改变,改变View的属性,进而产生动画效果
用法
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setTarget(mTextView); // 这行代码没用,ValueAnimator的该方法是空实现
animator.setDuration(4000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTextView.setAlpha(animation.getAnimatedFraction());
}
});
animator.start();
ObjectAnimator提供了更简便的属性设置方式。在ValueAnimator的基础之上,其内部方法通过反射方式调用对象某个属性的set方法。因此这个对象的属性需要具有set/get方法
用法
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);
objectAnimator.setDuration(4000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
如果需要对控件的多个属性执行动画,有两种方式可以实现:
1、使用PropertyValuesHolder实现
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("alpha", 0, 1);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 0, 1);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 0, 1);
ObjectAnimator.ofPropertyValuesHolder(mTextView, pvh1, pvh2, pvh3)
.setDuration(4000)
.start();
2、使用AnimatiorSet实现。相对于第一种方法,AnimatorSet可以对播放顺序进行更精准的控制。该类通过playTogether、playSequentially、play().with()、play().before()、play().after()等方法支持协调多个动画的播放顺序,可以实现连续动画、多属性复杂动画等。
ObjectAnimator o1 = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);
ObjectAnimator o2 = ObjectAnimator.ofFloat(mTextView, "scaleX", 0, 1);
ObjectAnimator o3 = ObjectAnimator.ofFloat(mTextView, "scaleY", 0, 1);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(4000);
animatorSet.playTogether(o1, o2, o3);
animatorSet.start();
为了更方便地对View使用属性动画,Google 很贴心地为View增加了animate方法直接创建属性动画,开发者可以更方便、更优雅地实Vview的属性动画,此时返回的对象是ViewPropertyAnimater的实例。
ViewPropertyAnimator 内部使用单个 ValueAnimator 对象为 View 的多个属性并行添加动画效果。它的行为方式与 ObjectAnimator 非常相似,它会修改视图属性的实际值,但在同时为多个属性添加动画效果时,更为高效。使用 ViewPropertyAnimator 的代码也更简洁,也更易读
mTextView.animate()
.alpha(1)
.scaleX(2f)
.scaleY(2f)
.setDuration(4000)
.start();
我们了解了属性动画的基本组成与用法,接下来看看属性动画是如何工作的。简化的属性动画的执行过程可以借助以下伪代码描述:
long startTime = System.currentTimeMillis();
int propStartVal = 0, propEndVal = 100;
int propVal = propStartVal;
long dt = 0, duration = 1000;
while(dt < duration){ // --1、动画循环
Thread.sleep(20); // --2、等待下一帧刷新
dt = System.currentTimeMillis() - starttime;// --3、根据动画进度计算属性值
float fraction = dt / duration;
float currVal = fraction * (propEndVal - propStartVal);
propVal = currVal; // --4、设置属性值
draw(); // --5、执行绘制
}
属性动画的执行流程可以分为动画循环、等待帧刷新、计算属性值、更新属性值和绘制画面五个部分,下面将对每一部分进行详细说明
在动画周期中,动画一直进行,画面需要不断地刷新。在Android系统中,一切事件都由消息机制驱动,UI线程如果在5秒内不能响应用户交互,就会发生崩溃。所以动画循环不能一直运行阻塞住UI线程,需要遵循消息机制实现重绘循环。我们需要在每一次帧刷新消息到来时,更新属性并重绘View。因此动画处理程序要在动画开始时,注册帧刷新的回调;在动画结束时,解除注册的帧刷新回调
注册帧刷新回调
// ValueAnimator.class
private void start(boolean playBackwards) {
/***部分代码省略***/
addAnimationCallback(0);
/***部分代码省略***/
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public AnimationHandler getAnimationHandler() {
return AnimationHandler.getInstance();
}
AnimationHandler是一个负责管理应用程序注册的动画帧回调接口的类, 应用程序通过该类注册和解注册帧刷新回调,该类维护一个回调队列,当动画帧刷新事件到来时,统一调用队列中的回调接口来通知应用程序,该类实例是一个ThreadLocal类型的对象,一个线程仅有一份
调用其addAnimationFrameCallback 方法会将该回调接口添加到其内部维护的回调队列 mAnimationCallbacks中
public class AnimationHandler {
public final static ThreadLocal sAnimatorHandler = new ThreadLocal<>();
public static AnimationHandler getInstance() {
if (sAnimatorHandler.get() == null) {
sAnimatorHandler.set(new AnimationHandler());
}
return sAnimatorHandler.get();
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// mFrameCallback:FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
}
AnimationFrameCallbackProvider是一个跟动画协调对象Choreographer交互的一个接口
MyFrameCallbackProvider 内部持有一个 Choreographer 对象,该对象也是线程局部变量,即线程唯一的。MyFrameCallbackProvider的postFrameCallback方法内部调用了Choreographer 对象 的postFrameCallback方法添加FrameCallback类型的接口回调,该回调接口只有一个doFrame 方法,即在下一帧刷新时会回调该方法进行处理
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
}
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
解注册帧刷新回调
// ValueAnimator.class
private void endAnimation() {
if (mAnimationEndRequested) {
return;
}
removeAnimationCallback();
// ...
}
private void removeAnimationCallback() {
// ...
getAnimationHandler().removeCallback(this);
}
当下一帧刷新时,动画刷新属性值,然后重新绘制页面。那属性动画值是在什么时候刷新的呢?我们先了解一下Android 帧刷新的机制
手机屏幕是由许多微小的像素点组成的。通过为每个像素点设置不同的颜色,屏幕可以呈现丰富多彩的图像
屏幕展示的颜色数据
GPU的Frame Buffer中的数据
早期Android系统的帧刷新是通过发送定时消息实现,默认是每10毫秒刷新一次。然而通过定时消息来刷新帧会导致UI流畅性差:
分析上图可知
为了解决掉帧问题,Android4.1 启动了Project Butter来解决这一系列问题
Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。其中, VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种在PC上已经很早就广泛使用的技术。 可简单的把它认为是一种定时中断。该中断触发的时机与交换Buffer的时机相同,当中断发生时,可以通知应用程序绘制下一帧。下图表示引入了VSYNC机制后的绘制流程:
每当应用程序收到VSYNC中断,CPU就开始处理下一帧的数据,整个过程非常完美。只要每一帧的绘制时间不超过帧间隔(16ms),就不会发生丢帧的现象。
不过,仔细琢磨图2却会发现一个新问题:图2中,CPU和GPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPU的FPS(帧率,Frames Per Second)要高于Display的FPS。确实如此。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与Display的FPS相同。但这种处理并没有什么问题,因为Android设备的屏幕刷新率一般是60,其对应的显示效果非常平滑,高于这一频率的帧实际上不会展示到屏幕上。另一方面,假设CPU/GPU的FPS小于屏幕刷新的FPS,会是什么情况呢?请看下图:
分析这段绘制流程,我们可以发现:
1.在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。
2.同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer当前展示在屏幕上,B Buffer被GPU在使用,此时没有可以用于绘制的Buffer。注意,由于使用了VSYNC机制,CPU只会在VSYNC信号到来时检查Buffer的可用状态,导致CPU资源可能被浪费。
三级缓存
为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer。如果有第三个Buffer的存在,CPU就能直接使用它, 而不至于空闲。出于这一思路就引出了Triple Buffer,即三级缓存。优化后的绘制过程如下图所示:
分析该绘制过程,在第二个16ms时间段,CPU使用C Buffer绘制,避免空等。虽然还是会多显示A帧一次,但后续可以实现流畅刷新。这种类似流水线的工作方式降低了流畅动画的速度要求,只需CPU和GPU单独处理时间不超过一帧间隔即可(即CPU处理时间不超过16ms,GPU处理时间不超过16ms,二者相加总时间可以超过16ms)。
让我们回顾一下Project Buffer的关键点:
为了实现将绘制工作都统一到VSYNC时间点上,需要一个组织者来协调绘制过程,这就是Choreographer的作用。在它的统一指挥下,应用的绘制工作将变得井井有条
Choreographer
中文翻译是编舞者,用于协调input、animation和drawing的时机
Choreographer通过接收显示系统的时间脉冲(即VSYNC信号), 来触发下一帧的渲染工作
通常App层通过高层抽象的API调用和Choreographer交互, 如ValueAnimator#start / View#postOnAnimation等
Choreographer 工作过程分为 2 部分来分析:监听VSYNC和接收到VSYNC后的处理流程
请求VSYNC
AnimationHandler通过MyFrameCallbackProvider的postFrameCallback 方法向Choreographer添加一个帧回调,即调用了Choreographer 的postFrameCallback方法
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
// 省略一些代码
}
那我们就来看看Choreographer 对象的postFrameCallback方法做了什么
该方法内部调用了postFrameCallbackDelayed(callback,0)方法,其内部又调用了postCallbackDelayedInternal 方法,并加上了一个callback 类型参数 CALLBACK_ANIMATION,指明这是一个动画帧回调监听,token参数是一个常量,应用程序通过该方法添加的回调都使用这个token,后面在新一帧到来后通知这些callback后,会将应用程序添加的callback移除,所以这里需要用token标记这些callback
// Choreographer.class
private final CallbackQueue[] mCallbackQueues;
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
// ......
postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 把任务都放在 mCallbackQueues[callbackType] 队列中
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
addCallbackLocked就是向对应回调类型CALLBACK_ANIMATION的消息队列中按照时间顺序添加一个元素
// CallbackQueue.class
@UnsupportedAppUsage
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
if (entry == null) {
mHead = callback;
return;
}
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = mCallbackPool;
if (callback == null) {
callback = new CallbackRecord();
} else {
mCallbackPool = callback.next;
callback.next = null;
}
callback.dueTime = dueTime;
callback.action = action;
callback.token = token;
return callback;
}
添加到回调队列后,Choreographer 请求显示系统接收下一次的VSYNC信号:
// Choreographer.class
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) { // 是否请求过执行帧 flag,没有就发送请求
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on VSYNC.");
}
// 采用了VSYNC机制,且在主线程,则立即执行 scheduleVSYNCLocked(),
if (isRunningOnLooperThreadLocked()) {
scheduleVSYNCLocked();
} else {
// 如果不是主线程,则通过 mHandler 发消息给主线程,最终也是执行scheduleVSYNCLocked()
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 没有采用VSYNC机制,就自己在一段时间后发送帧刷新消息
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
该方法的功能:
// Choreographer.class
private final FrameDisplayEventReceiver mDisplayEventReceiver; // 显示器事件接收器
@UnsupportedAppUsage
private void scheduleVSYNCLocked() {
mDisplayEventReceiver.scheduleVSYNC();
}
Choreographer的scheduleVSYNCLocked 方法内部调用了 mDisplayEventReceiver 的scheduleVSYNC 方法,mDisplayEventReceiver 是显示屏事件接收器,接收显示器事件如VSYNC事件。该对象是在Choreographer的构造方法中初始化的,只有在开启VSYNC机制时才会被初始化
private Choreographer(Looper looper, int VSYNCSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, VSYNCSource) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
FrameDisplayEventReceiver 类继承自DisplayEventReceiver类,且实现了Runnable接口。该对象在创建时就会将自己初始化以显示屏事件,具体实现逻辑是由native代码实现的
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
private boolean mHavePendingVSYNC;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int VSYNCSource) {
super(looper, VSYNCSource);
}
}
FrameDisplayEventReceiver初始化
// FrameDisplayEventReceiver.class
private static native long nativeInit(WeakReference receiver,
MessageQueue messageQueue, int VSYNCSource);
public DisplayEventReceiver(Looper looper, int VSYNCSource) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue, VSYNCSource);
mCloseGuard.open("dispose");
}
回到我们之前说的Choreographer类的scheduleVSYNCLocked方法调用了 mDisplayEventReceiver的scheduleVSYNC方法,看看这个方法里面做了什么
// Choreographer.class
@UnsupportedAppUsage
private void scheduleVSYNCLocked() {
mDisplayEventReceiver.scheduleVSYNC();
}
// FrameDisplayEventReceiver.class
/**
* 计划在下一个显示帧开始时发送单个垂直同步脉冲
*/
@UnsupportedAppUsage
public void scheduleVSYNC() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed.");
} else {
nativeScheduleVSYNC(mReceiverPtr); // native方法
}
}
具体的接收VSYNC的请求由native代码处理,我们就不继续看下去了。接收VSYNC的请求到此结束,我们接下来看Choreographer 如何接收VSYNC
收到 VSYNC
当显示系统下一次发生VSYNC 信号脉冲时,会回调FrameDisplayEventReceiver类的dispatchVsync方法
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
该方法调用了onVsync方法,该方法是空实现,需要子类实现
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// ......
// 向handler post一条VSYNC事件消息
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVSYNC = false;
doFrame(mTimestampNanos, mFrame); // Choreographer类的方法
}
}
onVsync方法向handler post了一条VSYNC事件消息,当handler处理该消息时,会调用该类的run方法,进而调用Choreographer的doFrame方法,执行下一帧的渲染流程
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
// ......
try {
// ......
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
//标记动画开始时间
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
// ......
}
// ......
}
}
doFrame方法会依次执行输入、动画、遍历和提交事件的回调,就是调用doCallbacks方法,传不同的callback类型
frameTimeNanos是底层VSYNC信号到达的时间戳
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
// ......
try {
// 从队列取出执行时间在当前时间之前的所有 CallbackRecord,callbacks 是一个链表,然后遍历 callbacks 执行 run 方法
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
// ......
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// ......
c.run(frameTimeNanos);
}
} finally {
// 回收callbacks,加入对象池mCallbackPool
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks); // 回收回调过的callback对象
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
该方法主要功能:
每个CallbackRecord关联一个应用程序注册的回调,在run方法里会调用FrameCallback对象的doFrame方法
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 是应用程序注册的回调,就执行该 doFrame 方法
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
private void recycleCallbackLocked(CallbackRecord callback) {
callback.action = null;
callback.token = null;
callback.next = mCallbackPool;
mCallbackPool = callback;
}
这里的回调方法run()有两种执行情况:
recycleCallbackLocked方法将其关联的action对象和token都置为空,且将自己放入缓存池中了,也就是说,应用注册的VSYNC回调只会被调用一次,回调完如果还想要接收下一次的VSYNC事件,需要再次注册
回到我们说到的在run方法里会调用FrameCallback对象的doFrame方法,对应到我们之前AnimationHandler的成员变量 mFrameCallback,他的doFrame方法内部调用了doAnimationFrame
public class AnimationHandler {
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// 如果应用程序向他注册的回调列表不空,说明还需要监听下一次的VSYNC信号,因此还需要再次向Choreographer注册回调
getProvider().postFrameCallback(this);
}
}
};
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
}
mFrameCallback 在帧刷新回调方法中,调用了doAnimationFrame 方法,还记得之前在动画开始的时候,valueAnimator对象 在AnimationHandler 注册了自己,监听帧刷新事件吗?就是这句代码
getAnimationHandler().addAnimationFrameCallback(this, delay),doAnimationFrame 方法中,会回调 AnimationFrameCallback#doAnimationFrame 方法,我们回过头来看看,valueAnimator 对象的
doAnimationFrame 方法做了什么
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
// 处理一个动画帧
public final boolean doAnimationFrame(long frameTime) {
// 省略一些代码
// 处理暂停/恢复的情况,
// 省略。。。
final long currentTime = Math.max(frameTime, mStartTime);
// 根据当前流逝的时间,更新动画属性,并根据返回值判断动画是否完成
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
// 动画完成就结束动画
endAnimation();
}
return finished;
}
}
ValueAnimator#animateBasedOnTime方法
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) && (mRepeatCount != INFINITE); // 最后一轮动画是否结束
if (scaledDuration == 0) {
// 动画时长为0,直接跳到结束动画阶段
done = true;
} else if (newIteration && !lastIterationFinished) {
// 动画重复播放回调
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
} else if (lastIterationFinished) {
done = true;
}
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction); // 更新属性值
}
return done;
}
世界上的动画效果有千千万,有模拟现实世界物体运动规律的动画,也有违反现实世界物体运动规律的动画,不同的动画效果对应的是动画已播放时长与属性值的不同映射关系,即属性值是动画进度的函数,动画进度是自变量,属性值是因变量
根据映射关系的特性,可以将动画分为线性动画和非线性动画,线性动画是指随着时间推移,动画执行的速率不变,属性值按照恒定的速度进行变化;非线性动画则是指随着时间推移,动画执行速率变化,属性值改变的速率也会改变,从而导致属性值的改变趋势是一条曲线,如下所示:
下图描绘了一个假设的对象,该对象的 x 属性添加了动画效果。动画时长设置为 40 毫秒,要移动的距离为 40 像素。该对象每隔 10 毫秒会水平移动 10 像素。在 40 毫秒时,动画停止,同时对象在水平位置 40 处停止。这是使用线性插值(表示对象以恒定速度移动)的动画示例
下图展示了一个假设的对象,它在动画开始时加速,在动画结束前减速。该对象仍在 40 毫秒内移动了 40 像素,但这种移动是非线性的。开始时,此动画加速移动到中间点,然后从中间点减速移动,直至动画结束。如图所示,动画在开头和结尾移动的距离小于在中间移动的距离
为了更灵活地支持丰富的动画效果,Android引入动画执行进度(即当前属性值改变的百分比)变量,将动画已播放时长百分比与属性值的映射关系分为两段,第一段是动画已播放时长百分比到属性变化百分比的映射,这种映射关系称为时间插值器;第二段是属性变化百分比到属性值的映射,这种映射关系称为类型估值器。Q:为什么需要类型估值器呢?
时间插值器的作用是改变动画的执行速率,根据时间流逝的百分比计算出当前属性值改变的百分比。一般情况我们不需要自己实现插值器,Android 系统为我们提供了9种插值器。使用插值器后会让动画执行的效果更酷炫,如果想自定义插值器也不难,可以查看已经实现插值器源码做参考
9种插值器
类型估值器的作用是根据当前属性改变的百分比来计算改变后的属性值,属性的类型除了基本数值类型之外,还有Point、Rect、数组和存储argb颜色的整数等不适用于数值插值实现的类型。因此Android提供这样一个接口,允许开发者自由实现如何根据当前属性变化的百分比计算出自定义属性的值
public interface TypeEvaluator {
public T evaluate(float fraction, T startValue, T endValue);
}
ValueAnimator.animateValue
public class ValueAnimator extends Animator {
/***部分代码省略***/
void animateValue(float fraction) {
// 计算当前属性改变的百分比
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 计算最新属性值
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
//属性值改变的回调
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
}
PropertyValuesHolder
PropertyValuesHolder这个类的意义就是保存动画过程中所需要操作的属性和对应的值。在封装成PropertyValuesHolder实例以后,后期的对属性值的操作也是在PropertyValuesHolder进行
public class PropertyValuesHolder implements Cloneable {
/***部分代码省略***/
public static PropertyValuesHolder ofObject(Property property, TypeEvaluator evaluator, V... values) {
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
pvh.setObjectValues(values);
pvh.setEvaluator(evaluator);
return pvh;
}
public void setObjectValues(Object... values) {
mValueType = values[0].getClass();
// mKeyframes:KeyframeSet
mKeyframes = KeyframeSet.ofObject(values);
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
mKeyframes.setEvaluator(evaluator);
}
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
}
KeyFrameSet#getValue 方法
public Object getValue(float fraction) {
// Special-case optimization for the common case of only two keyframes
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),, mLastKeyframe.getValue());
}
// ......处理多于2个 KeyFrame的情况
}
我们计算出下一帧的属性值之后,需要赋值给对应的属性。那Android 中如何更新属性值的呢?就是通过反射来获取属性的 get 和 set 方法,再调用set方法为属性赋值,从而实现属性值的更新。所以,这就需要我们的View (如自定义 View 中)动画属性具有 set 和 get 方法
public class PropertyValuesHolder implements Cloneable {
/**
* 属性名称
*/
String mPropertyName;
/**
* 属性的set方法
*/
Method mSetter = null;
/**
* 属性的get方法
*/
private Method mGetter = null;
/**
* 类型估值器
*/
private TypeEvaluator mEvaluator;
/**
* 当前最新的属性值
*/
private Object mAnimatedValue;
}
动画初始化时,会初始化属性的set方法和get方法
// ObjectAnimator.class
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}
// FloatPropertyValueHolder.class
void setupSetterAndGetter(Object target) {
mKeyframes.invalidateCache();
if (mProperty != null) {
/***部分代码省略***/
}
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
//初始化mSetter
setupSetter(targetClass);
}
List keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
// ......
}
}
}
}
ObjectAnimator.animateValue
public final class ObjectAnimator extends ValueAnimator {
/***部分代码省略***/
@Override
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up.
cancel();
return;
}
// ValueAnimatoranimateValue方法
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//设置target的属性值,进行View的移动,产生动画
mValues[i].setAnimatedValue(target);
}
}
}
ObjectAnimator#setAnimatedValue方法
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
Object getAnimatedValue() {
return mAnimatedValue;
}
当属性值更新后,需要执行绘制操作,得到新的帧数据。Android的View绘制与动画过程中的属性计算一样,由Choreographer对象统一调度,当doFrame方法调度动画更新完属性后,再调用绘制帧回调接口
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
// ......
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// ......
}
}
ViewRootImpl类注册了帧刷新的绘制回调接口,因此Choreographer 类的绘制处理方法会调用 TraversalRunnable类的run方法,开始View的三大绘制流程
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
Android 有两种绘制模型:基于软件的绘制模型和硬件加速绘制模型
页面渲染背景知识
基于软件的绘制模型
软件绘制时,界面的渲染工作都会由主线程完成,主线程在处理完input、animation、measure、layout后,开始执行draw时,直接通过调用 skia库实现drawLine、drawText等操作;每当应用需要更新界面时,都需要计算重新绘制的屏幕区域,然后进行重绘
缺点:
每次绘制时都需要处理大量数据。例如,如对某个按钮调用 invalidate() ,Android 系统会重新绘制该View;此时CPU需要重新计算该View每一个像素的值,这个计算通常会花费较多的时间。CPU 相对更擅长处理逻辑,应当由更擅长批量计算的GPU承担界面的绘制工作
硬件加速绘制模型
硬件加速,实际上应该叫 GPU 加速,软件绘制与硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU;如果是 GPU,就认为是硬件加速绘制,反之,则是软件绘制
硬件加速的主要原理,就是通过底层软件代码,将CPU不擅长的图形计算转换成GPU专用指令,由GPU完成
从 Android 3.0(API 级别 11)开始,Android 2D 渲染管道支持硬件加速,也就是说,在 View 的画布上执行的绘制操作都会使用 GPU;从API 14 开始,硬件加速默认处于启用状态
在硬件加速模型中,app会存在主线程和渲染线程,界面的最终绘制工作是由渲染线程驱动GPU完成
支持硬件加速后,Android View 源码新增了RenderNode 数据结构,View被抽象成RenderNode 节点,View的绘制内容被抽象成一系列绘制操作,称为显示列表(Display list),一个View的RenderNode 中包含了自身的显示列表及子View的显示列表的信息
当我们使用invalidate() 和 draw() 请求屏幕更新和渲染View时,view 会将这些绘制命令记录在显示列表中,且只记录和更新被invalidate()标记的View的显示列表,然后由GPU绘制显示列表。如果view没有invalidate,就无需再次执行View的onDraw方法
启用硬件加速后,属性动画( alpha \ translation \ scale \ rotation \ )过程中只更新 View 的 属性,无需更新显示列表;display list主要由渲染线程负责绘制,减少了主线程的工作,提高了主线程的响应速度
目前在硬件加速模式下不需要重新生成display list的动画属性有:
- alpha
- x、y
- translationX、translationY
- scaleX、scaleY
- rotation、rotationX、rotationY
- pivotX、pivotY
DisplayList:
DisplayList是一个基本绘制元素,包含元素原始属性(位置、尺寸、角度、透明度等),对应Canvas的drawXxx()方法(如下图)
https://cloud.tencent.com/developer/article/1176313
https://cloud.tencent.com/developer/article/1634784
https://blog.csdn.net/innost/article/details/8272867
https://juejin.cn/post/6844903870863278094
https://developer.android.com/guide/topics/graphics/prop-animation?hl=zh-cn
https://cloud.tencent.com/developer/article/1634796
https://blog.csdn.net/carson_ho/article/details/79860980
https://tech.meituan.com/2017/01/19/hardware-accelerate.html
https://developer.android.com/guide/topics/graphics/hardware-accel?hl=zh-cn
https://www.jianshu.com/p/c100d42eb42f
https://zhuanlan.zhihu.com/p/75458539
https://juejin.cn/post/6844903517803511822