Android关于绘制的一个小细节分享
作者:陈小缘
很多时候我们在自定义 View 的需要做动画的时候,我们可以依赖属性动画的回调周期性修改 自定义的属性值,然后调用 invalidate 方法实现。
不过我还见过一个比较野的路子,它在 onDraw 里面直接修改属性,然后调用 invalidate() 方法。
运行起来好像也没问题。
那么问题来了: 在 onDraw 里面调用 修改绘制相关属性(例如画圆,修改半径) invalidate() ,这种与属性动画的回调调用 invalidate() 源码分析有什么区别?在 onDraw 里面调用 invalidate() 会存在什么问题?
在View.onDraw方法里调用View.invalidate和在ValueAnimator.AnimatorUpdateListener中调用View.invalidate,有区别吗?
了解 ValueAnimator 的同学会知道,它播放动画的实现原理并不是直接使用线程来不断计算并回调AnimatorUpdateListener ,而是…来写代码测试下就知道了:ValueAnimator.ofInt(1).apply { addUpdateListener { // 因为animatedFraction=0时是直接回调的 if (it.animatedFraction > 0) { throw RuntimeException() } } start() }
代码很简单,随便创建一个 ValueAnimator 然后在它的UpdateListener 里面去抛出一个异常。
看看堆栈信息: E/AndroidRuntime: FATAL EXCEPTION: main Process: com.wuyr.wanandroidqa, PID: 16027 java.lang.RuntimeException at com.wuyr.wanandroidqa.activities.main.TestActivity$start$1$1.onAnimationUpdate(TestActivity.kt:112) at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1566) at android.animation.ValueAnimator.animateBasedOnTime(ValueAnimator.java:1357) at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1489) at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146) at android.animation.AnimationHandler.access$100(AnimationHandler.java:37) ////////// // 3 ////////// at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54) ////////// // 2 ////////// at android.view.Choreographer$CallbackRecord.run(Choreographer.java:970) at android.view.Choreographer.doCallbacks(Choreographer.java:796) ////////// // 1 ////////// at android.view.Choreographer.doFrame(Choreographer.java:727) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
看标记1处,来到了 Choreographer.doFrame 方法,View的绘制,各种输入/触摸事件等也是在这里开始处理的。
接着看2, Choreographer.java 第970行:private static final class CallbackRecord { public Object action; // Runnable or FrameCallback public void run(long frameTimeNanos) { ...... ((FrameCallback)action).doFrame(frameTimeNanos); // 970行 ...... } }
这里把action强转为 FrameCallback ,而标记3处:public class AnimationHandler { private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); // 54行 if (mAnimationCallbacks.size() > 0) { getProvider().postFrameCallback(this); } } }; }
可以看到它回调的正是 AnimationHandler 的mFrameCallback 。
但是 ValueAnimator 怎么会跟AnimationHandler 扯上关系呢?
其实在我们调用 start 方法播放动画的时候,它就已经把一个Callback添加到AnimationHandler 里面去了:public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback { @Override public void start() { start(false); } private void start(boolean playBackwards) { ...... addAnimationCallback(0); ...... } private void addAnimationCallback(long delay) { AnimationHandler.getInstance().addAnimationFrameCallback(this, delay); } /* 这里实现AnimationHandler.AnimationFrameCallback接口的方法 */ @Override public final boolean doAnimationFrame(long frameTime) { ...... } }
这个Callback正是 ValueAnimator 自身。
那它最终会被传到哪里呢?
代码套那么多层就不全贴了,它最终会通过 Choreographer.postFrameCallback 方法:public final class Choreographer { public void postFrameCallback(FrameCallback callback) { postFrameCallbackDelayed(callback, 0); } public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { ...... postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); } }
传到了 Choreographer 中。
可以看到它最后调用的是 postCallbackDelayedInternal 方法,记住!这个很重要!
好,回到主题。
通过刚刚一段分析,可以知道, ValueAnimator.AnimatorUpdateListener ,是在Choreographer.doFrame 回调时才回调的。也就是说,ValueAnimator 开始后,AnimatorUpdateListener 会在每一次屏幕刷新的时候回调!
还有一个区别就是,动画进度计算方式不同, ValueAnimator 是根据记录的开始时间来计算的,所以它不会受到Activity生命周期或其他因素影响。而直接在onDraw 里回调的就不同了,如果动画在播放过程中Activity Stopped了,onDraw 就会暂停回调,那么下一次的invalidate时间,也就无从确定了。不过,可能刚好有这样的需求,需要在Activity不可见时暂停动画呢?
在View.onDraw中直接调用invalidate方法会有什么问题?
看了 @xujiafeng 同学的回答,他说这样做的话, IdleHandler 不会被回调。公众号文章在这里:Android 避坑指南:实际经历来说说IdleHandler的坑
emmmm,其实我觉得这不应该是一个问题,因为Handler的机制就是这样的啊, MessageQueue 还有事情没处理完,肯定不会告诉你说它有空啦。
等动画播放完毕, IdleHandler 还是会正常回调的。
不过你说是要无限循环播放的话,让 MessageQueue 一直忙碌,导致IdleHandler 一直没能被回调的话,那确实是个问题,就拿常见的场景来说:每日一问 | Activity 调用了finish()方法会立即调用onDestory()吗? ,Activity的Destory 也是依赖IdleHandler 来完成的(虽然有超时机制)。(以后会跟大家一起debug AMS来详细分析这个问题)
https://www.wanandroid.com/wenda/show/13244
如果真的有这样的需求,除了改用 ValueAnimator 之外,就没其他方法了吗?
肯定有啦,你想想 ScrollView 、RecyclerView 、ViewPager 等等这些View的惯性滚动动画效果是怎么做的?
它们其实是通过一个叫 postInvalidateOnAnimation 的方法来invalidate 的,关于这个方法,我记得在前面好几个回答都提到过了。
来看下它原理是怎么样的吧:
长话短说,它最终是调用 Choreographer.postCallback 方法来把一个会调用View.invalidate 的Runnable 传进去:public final class Choreographer { public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { ...... postCallbackDelayedInternal(callbackType, action, token, delayMillis); } }
!!!!!看到了没?!它最终调用的是 postCallbackDelayedInternal 方法!还记得刚刚分析ValueAnimator 的时候,叫记住的那个方法吗?就是它啊!
这就说明了,使用 View.postInvalidateOnAnimation 方法,跟在ValueAnimator.AnimatorUpdateListener 中调用invalidate ,效果是一样的!
同样是调用invalidate方法,为什么在AnimatorUpdateListener.onAnimationUpdate里面调用,就不会阻止IdleHandler回调呢?
看图就明白了,这是在 onDraw 里调用invalidate 的流程:
就算MQ里没有其他的msg,在每次Traversal任务即将处理完毕时又向MQ塞入了新的msg,所谓一波未平,一波又起,这样的话, IdleHandler 肯定没机会回调了。
来看下在 AnimatorUpdateListener 中调用invalidate 的流程:
因为 AnimatorUpdateListener 的onAnimationUpdate 方法是每次屏幕刷新时才回调的,也就是大概16ms左右,在这16ms的间隔内,Looper可能已经把MQ里剩下的msg都取出来了,所以如果在AnimatorUpdateListener 里调用invalidate 的话,会看到这样的log:onAnimationUpdate: invoked onAnimationUpdate: invoked onDraw: invoked queueIdle: invoked onAnimationUpdate: invoked onDraw: invoked queueIdle: invoked onAnimationUpdate: invoked onDraw: invoked queueIdle: invoked onAnimationUpdate: invoked onDraw: invoked queueIdle: invoked onAnimationUpdate: invoked onDraw: invoked queueIdle: invoked最后
在这里还分享一份由大佬亲自收录整理的学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
当然,你也可以拿去查漏补缺,提升自身的竞争力。
真心希望可以帮助到大家,Android路漫漫,共勉!
如果你有需要的话,只需私信我【进阶】即可获取