Android 关于绘制的一个小细节分享

作者:陈小缘

很多时候我们在自定义 View 的需要做动画的时候,我们可以依赖属性动画的回调周期性修改 自定义的属性值,然后调用 invalidate 方法实现。

不过我还见过一个比较野的路子,它在 onDraw 里面直接修改属性,然后调用 invalidate() 方法。

运行起来好像也没问题。

那么问题来了:

  1. onDraw 里面调用 修改绘制相关属性(例如画圆,修改半径) invalidate() ,这种与属性动画的回调调用 invalidate()源码分析有什么区别?
  2. 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);
            }
        }
    };
}

可以看到它回调的正是AnimationHandlermFrameCallback

但是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之外,就没其他方法了吗?

肯定有啦,你想想ScrollViewRecyclerViewViewPager等等这些View的惯性滚动动画效果是怎么做的?

它们其实是通过一个叫postInvalidateOnAnimation的方法来invalidate的,关于这个方法,我记得在前面好几个回答都提到过了。

来看下它原理是怎么样的吧:

长话短说,它最终是调用Choreographer.postCallback方法来把一个会调用View.invalidateRunnable传进去:

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的流程:

Android 关于绘制的一个小细节分享

就算MQ里没有其他的msg,在每次Traversal任务即将处理完毕时又向MQ塞入了新的msg,所谓一波未平,一波又起,这样的话,IdleHandler肯定没机会回调了。

来看下在AnimatorUpdateListener中调用invalidate的流程:

Android 关于绘制的一个小细节分享

因为AnimatorUpdateListeneronAnimationUpdate方法是每次屏幕刷新时才回调的,也就是大概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路漫漫,共勉!

如果你有需要的话,只需私信我【进阶】即可获取

Android 关于绘制的一个小细节分享

Android 关于绘制的一个小细节分享

Android 关于绘制的一个小细节分享

展开阅读全文

页面更新:2024-02-10

标签:大佬   进阶   架构   标记   属性   细节   原理   代码   方法   动画   资料

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top