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