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

2021-09-26  本文已影响0人  Lurky

       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做计时间隔设置。

上一篇下一篇

猜你喜欢

热点阅读