概述
Timer 是可以指定将来的某个时间在后台线程中调度任务的工具。每个Timer对应一个后台线程用来顺序执行这个Timer对应的所有任务(TimerTask)。正因为是单线程顺序执行的,所以每个任务必须被快速执行,不能做耗时操作,否则会阻塞后面任务的执行。下面基于Android 8.0来详细分析一下Timer的代码结构和工作原理。
示例
Timer还提供了多种schedule方法用来满足不同case的需要,比如可以设定未来的某个时间开始执行。下面是一个简单的Timer使用示例,表示延迟1s执行,每5s钟执行一次。
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// do something
}
},1000,5000);
相关类简介
分析之前先介绍两个相关联的类TimerTask、TaskQueue和TimerThread 。
TimerTask
Timer执行任务的封装类,里面定义了两个成员变量和四种任务状态。
成员变量:
// 下一次执行时间
long nextExecutionTime;
// 下一次执行的时间间隔
long period = 0;
任务状态:
// 未调度
static final int VIRGIN = 0;
// 被调度
static final int SCHEDULED = 1;
// 被执行
static final int EXECUTED = 2;
// 被取消
static final int CANCELLED = 3;
TaskQueue
计时器任务队列,内部定义了一个长度为128的TimerTask数组,根据TimerTask的nextExecutionTime值来决定优先级,里面提供了相关的增删查询和简单的排序功能。
TimerThread
Timer的辅助类,用来执行TimerTask的线程,也是Timer整个架构设计的核心。
Timer 运行原理介绍
了解了上面的介绍,经验丰富的同学应该已经想到了Timer的运行原理了。运行原理:一方面,通过Timer类的schedule方法把TimerTask放到任务序列TaskQueue中;另一方面,工作线程TimerThread会不停的从TaskQueue中读取TimerTask并执行。下面结合源码来详细分析一下这个过程。
初始化
Timer在初始化的时候会先创建一个任务序列TaskQueue,然后通过TaskQueue的实例创建工作线程TimerThread并启动这个线程。
// 创建任务序列TaskQueue
private final TaskQueue queue = new TaskQueue();
// 创建工作线程TimerThread
private final TimerThread thread = new TimerThread(queue);
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
// 启动工作线程
thread.start();
}
添加任务
Timer类中提供了多种重载的schedule方法来满足不同的应用场景,最终都会通过统一的方法sched把具体的任务TimerTask加入到任务队列TaskQueue中去。
private void sched(TimerTask task, long time, long period) {
...
synchronized(queue) {
...
synchronized(task.lock) {
// 封装任务
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
// 添加任务到任务序列
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
执行任务
前面初始化中介绍过了,初始化的时候就会启动工作线程TimerThread。在工作线程中会做一个死循环,不停的从任务序列中读取任务然后执行。
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 等待任务到来
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
// 当任务序列为空且不再执行其他任务则退出循环
if (queue.isEmpty())
break;
// 执行任务
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;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
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
可以通过Timer类的cancel方法结束Timer。
public void cancel() {
synchronized(queue) {
// 重置标志位,告诉线程不会有新的任务来了,当任务序列为空则跳出死循环结束线程
thread.newTasksMayBeScheduled = false;
// 清空任务序列
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
总结
通过上面的分析可以看出,Timer使用和运行原理都相对简单,如果你看过Handler源码的话会有一种似曾相识的感觉,两者在代码结构和运行原理上是相似的,Handler在实现上比Timer要复杂些功能也更强大。在结构上,TimerTask对应于Message,TaskQueue对应于MessageQueue,TimerThread对应于Looper。在运行原理上,都是把待处理的事务放到队列当中,然后由一个死循环的工作狂不停的从队列中获取事务并处理。个人认为Timer完全可以看做是简化版的Handler。
既然提到定时器,就顺带说一下我所知道的能用来做定时器的方法吧。除了上面提到的Timer和Handler之外,AlarmManager也是比较常用的方法,关于AlarmManager源码解析可以看一下我另一篇文章——系统服务之定时服务(AlarmManager)。其他的还有前几年比较火的RxJava的interval方法,可以看看RxJava2.x实现定时器。Jobscheduler应该也可以,感兴趣的可以移步Android Jobscheduler使用。
最后,给一个别人整理的一些示例代码链接:Android中几种常用的定时器和延时方法