美文网首页
Android Timer:系统时间同步后引起的计时间隔异常

Android Timer:系统时间同步后引起的计时间隔异常

作者: Lurky | 来源:发表于2021-09-26 18:04 被阅读0次

           Android开发中,我们通常会使用Timer/TimerTask实现计时器轮询,但轮询的任务执行间隔在系统完成时间同步后,会发生一些诡异的问题:

    1. 莫名同时发生两次时间相同的TimerTask调用,引起服务端的业务逻辑异常;

    2. TimerTask轮询时间间隔为0,导致终端陷入死循环;

            为了找到具体的原因,今天特意分析了一下Android Timer的源码:

    A. scheduleAtFixedRate函数:

    public void scheduleAtFixedRate(TimerTask task,long delay,long period) {

    if (delay <0)

    throw new IllegalArgumentException("Negative delay.");

    if (period <=0)

    throw new IllegalArgumentException("Non-positive period.");

    sched(task,System.currentTimeMillis()+delay, period); //System.currentTimeMillis()+delay:Task的延迟执行时间是当前系统时间+延时;

    }

    B. sched函数:

    private void sched(TimerTask task,long time,long period) {

    if (time <0)

    throw new IllegalArgumentException("Illegal execution time.");

    // Constrain value of period sufficiently to prevent numeric

    // overflow while still being effectively infinitely large.

        if (Math.abs(period) > (Long.MAX_VALUE >>1))

    period >>=1;

    synchronized(queue) {

    if (!thread.newTasksMayBeScheduled)

    throw new IllegalStateException("Timer already cancelled.");

    synchronized(task.lock) {

    if (task.state !=TimerTask.VIRGIN)

    throw new IllegalStateException(

    "Task already scheduled or cancelled");

    //这里设置了Task的下次执行时间:System.currentTimeMillis()+delay,即当前系统时间+延时

    task.nextExecutionTime = time;

    //轮询间隔

    task.period = period;

    task.state =TimerTask.SCHEDULED;

    }

    queue.add(task);

    if (queue.getMin() == task)

    queue.notify();

    }

    }

    C.timer中Task的执行过程

    private void mainLoop() {

    while (true) {

    try {

    TimerTask task;

    boolean taskFired;

    synchronized(queue) {

    // Wait for queue to become non-empty

                    while (queue.isEmpty() &&newTasksMayBeScheduled)

    queue.wait();

    if (queue.isEmpty())

    break;// Queue is empty and will forever remain; die

    // Queue nonempty; look at first evt and do the right thing

                    long currentTime,executionTime;

    task =queue.getMin();

    synchronized(task.lock) {

    if (task.state ==TimerTask.CANCELLED) {

    queue.removeMin();

    continue;// No action required, poll queue again

                        }

    currentTime =System.currentTimeMillis();

    executionTime =task.nextExecutionTime;

    //判断是否运行Task,如果Task的计划运行时间小于等于当前时间,需要执行Task任务。

    //当Timer设置轮询任务时,如果那一刻的系统时间是0,而此刻完成了系统时间同步[获得了当前时间],那么Task任务就会立刻执行。

    if (taskFired = (executionTime<=currentTime)) {

    if (task.period ==0) {// Non-repeating, remove

                                queue.removeMin();

    task.state =TimerTask.EXECUTED;

    }else {// Repeating task, reschedule

    //这里会设置下一次Task的执行时间,executionTime +task.period,即当前Task的计划时间+间隔。所以,Timer设置轮询任务时系统时间是0,那么就需要计划时间+若干个时间间隔,直到超过当前系统时间才会停止执行Task任务。就会看到Task任务被死循环似的执行,直至系统崩溃。

                                queue.rescheduleMin(

    task.period<0 ?currentTime  -task.period

    :executionTime +task.period);

    }

    }

    }

    if (!taskFired)// Task hasn't yet fired; wait

                        queue.wait(executionTime -currentTime);

    }

    if (taskFired)// Task fired; run it, holding no locks

                    task.run();

    }catch(InterruptedException e) {

    }

    }

    }

    总结,这个Timer的实现逻辑,在Android 4.2开始就这样了。这就解释了为什么系统时间同步会影响Timer的Task执行周期。

    解决方案:

    1. TimerTask执行时,检查本次系统时间与前次系统时间的差,如果时间差为0或者不满足间隔阈值,即判定Timer计时间隔异常,需要重新建立Timer。

    2. 使用线程的Sleep做计时间隔设置。

    相关文章

      网友评论

          本文标题:Android Timer:系统时间同步后引起的计时间隔异常

          本文链接:https://www.haomeiwen.com/subject/cyaqnltx.html