【原创】Android如何实现一个定时器,另附源码学习心得分享

250 阅读4分钟
吾日三省吾身见贤思齐焉 见不贤而内自省也。

年轻的时候做事没太多方法论,很多业务都是上来就构思+手写。少了借鉴这一步。最近越发觉得借鉴是个很关键的步骤。对自己的技术栈积累大有裨益。而且自己做的业务总是有切入点可以去模仿的。所以,以后还是多借鉴吧。顺便一说,Android 源码看似很多,实际也很多。不过看的越多也会越快。因为总是那么几个关键的“东西”不断的出现。随着时间推移,发现也就那么点“东西”。

Android 平台上 如何实现一个定时器

目前发现有两种流派:

  1. Thread Sleep派:我把他归类为直接让线程睡眠的方式。脑子里想想列出来的方式就是Thread,Timer,CountDownTimer,Rxjava.interval 等。这种方式是java原生类提供的方式。说白了就是需要额外的线程用于即使,通过睡一下完成各种业务。
  2. Handler派:也就是借助 android系统 handler + looper 那种方式。这种方式原理简述一下,android手机有一套用于屏幕绘制的一帧一帧的机制。 每一帧都有一个时间间隔。我自己不去创建任何额外的线程去计时。而是直接在每一帧绘制的时候都添加一个检查方法。从自己缓存的任务栈里面去比对当前时间和结束时间的大小关系。来判断是否计时完成。

太基础的Thread定时器很多了很容易搜到,我贴一个有一定业务逻辑的定时器吧

用线程实现一个输入过滤业务

所谓输入过滤:就是如果用连续输入,不触发任务,如果一但输入的停下的时间大于阈值,于是触发业务。



/**
 * 持续输入过滤定时器
 */
class ContinuousTimerCounter(
    var waitTime:Long ,
    var sleepIntervalTime:Long ) {

    var isStilling = false
    private var endTime: Long = -1L;
    private var obj:Any? = null


    var callBack: ContinuousCallBack? = null

    var isPaused = false

    //暂停计时
    fun pause(){
        isPaused = true
    }
    //继续计时
    fun resume(){
        isPaused = false
        newAction(this.obj)
    }

    //添加一个新值
    fun newAction(obj: Any?){

        this.obj = obj
        if (isStilling) {
            updateObj()
        } else {
            startTimeCount()
        }
    }

    //开始计时
    private fun startTimeCount() {

        if (isStilling) return

        updateObj()
        isStilling = true
        //开启Thread 然后 while 循环
        Thread {
            kotlin.run {
                while (isStilling || isPaused) {
                    Thread.sleep(sleepIntervalTime)//睡眠的时间间隔任意可调,相当于时间的精度

                    if (endTime == -1L) {
                        continue
                    }
                    //判断时间差值来检查任务跳出
                    if (endTime < System.currentTimeMillis()) {
                        isStilling = false
                    }
                }
                callBack?.doAction(obj)
                reset()
            }

        }.start()
    }


    private fun updateObj() {
        endTime = System.currentTimeMillis() + waitTime
    }


    private fun reset() {//重置任务,也可用于退出
        isStilling = false
        endTime = -1L
    }

    fun isContinue(): Boolean {
        return endTime > System.currentTimeMillis()
    }


}

interface ContinuousCallBack {

    fun doAction(obj:Any?)
}

属性动画,从setDuration开始发问,到通过帧率定时!

最新做业务用到了属性动画,属性动画有个api名为 setDuration(),这不就是一个定时器嘛。于是问题来了.他的定时器怎么实现的? 看源码之前先先做几个猜想

  1. 如果每个属性动画都搞一个定时器,岂不是线程分分钟挂掉,显然是不可能的!
  2. 如果那么维护一个Thread while循环sleep,不断检查任务入执行回调?可能是这样的

源码过程比较冗长,我就列几个关键字吧。感兴趣的顺着关键字看。

ValueAnimator.java
ValueAnimator.start()
ValueAnimator.setIntValues()
PropertyValuesHolder.java
ValueAnimator.setInterpolator()
ValueAnimator.setDuration()
ValueAnimator.doAnimationFrame()
ValueAnimator.animateBasedOnTime()

ValueAnimator.getAnimationHandler()
AnimationHandler.java
ThreadLocal<AnimationHandler> sAnimatorHandler
Choreographer.FrameCallback mFrameCallback
AnimationHandler#MyFrameCallbackProvider.class
mChoreographer.postCallback()

其实看到 Choreographer 我的问题就结束了。原来他也是我第二个猜想的模型, 不过他没有独立使用线程,而是使用的 android 司空见惯的 handler+loop 的机制。所谓 while 循环 sleep方案换成了,不断的帧率检查,检查添加到队列里的属性动画任务并去执行回调。没有使用sleep。 实际上这种方案对于百分之95的业务都是足够使用场景的。因为这个计时的精确度就是每一帧的间隔。足够了!

至此 源码阅读结束!如果下次业务有问题需要使用。我会考虑用第二种”帧检查“的方式试试。

哈哈,这个文章一切都从看到api setDuration() 开始的。毕竟提出一个好问题,问题就解决了一大半!据我和隔壁的ios同学讨论,他们的定时器主要都是用帧检查的方式实现!各个客户端可能都是同样的方案。如果读者有第三种方案,欢迎提出方向,共同切磋