Timer,和handler的post机制类似。都可以用于之后某个时刻执行任务的实现,Timer恒存在自己的子线程,任务也在该子线程中执行;handler.post出去的任务在什么线程执行取决于其Looper所在线程。
一、简介
1.1 Timer原理简述
Timer是一个用于执行定时任务的类,可以单次执行或按指定时间间隔循环执行(直到主动cancel或线程被杀掉)。Timer中任务处理采用了生产者-消费者模型的设计思想。具体知识点包含以下几点:
1)一个存储任务的任务池TaskQueue,包含一个初始大小为128的TimerTask数组,负责任务的存储(add)、排序(fixUp、fixDown)、取出(getMin)、清理(removeMin、quickRemove)、循环任务处理(rescheduleMin)以及一些其他基本操作。并通过排序保证队头任务的执行一定是最早的。
2)一个作为事件消费者的TimerThread,TimerThread中不断获取当前任务队列的队头任务,执行任务。并根据任务是否需要循环决定是移除任务还是将任务按下一次执行时间重新加入到任务队列中。在TimerThread中不断获取待执行任务时,采用了Object.wait()和Object.notify()的机制。Object.wait()保证了任务队列为空时及时释放资源,而当有新的任务时也通过Object.notify()及时恢复任务的遍历。
3)TimerTask是任务实体,Runnable接口的实现类。内部包含用于线程安全的锁lock、用于标记任务状态的字段state、以及一个供用户实现的任务内容抽象方法run().
4)Timer本身提供了对以上三者操作的封装、实例化和对外暴露运行任务的接口。同时,作为生产者,将用户任务加入到任务队列;对消费者层面,Timer也是和消费者线程唯一绑定的,负责启动消费者线程,并在生产了新的任务后及时通知已经休眠的消费者。提供了多种构造方法和清理接口。
Timer是一个控制中心,对外和用户交互,接收用户要定时执行的任务;对内既是生产者又是消费者,控制 任务的存储、调度、执行的发起者。具体到TimerThread、TimerTask其实是一种委托思想(类似android中事件机制),Timer委托二者一个作为生产者、一个作为消费者。Timer不断将新任务按序交给生产者保存,同时委托消费者线程不断消费生产者队列中待执行的任务,并根据是否循环进行调度。
对于取消操作,消费者线程的根据newTasksMayBeScheduled字段和消费队列是否为空双重保障。而任务本身通过资深的状态字段进行控制。
1.2 Timer和Handler(或view)的post/postDelay
Timer和Handler.post都可以执行延时任务。
二者的相同之处如下:
1)执行的任务均为Runnable的实现类。
2)均可以指定延时时长delay。
3)二者皆通过生产者-消费者模型实现。
二者的不同之处:
1)Timer的任务是在新开的子线程中执行的,所以不可以做更新UI的操作,但可以做耗时任务(对于存在多任务的case,不建议又耗时任务,因为耗时任务会影响后续任务的执行时间);Handler.post方式执行的任务是在Looper对应线程中执行的,能否更新UI及做耗时任务取决于Looper对应线程是否是主线程。
2)Handler.post的方式执行任务只能执行一次(可通过递归调用、自调用的方式实现repeat);Timer封装好了相关接口,并提供了两种不同的循环方式。
3)从代码层面,Looper既是生产者(容器)也是消费者。并采用ThreadLocal机制将自身和线程一一绑定,也就有了任务执行取决于Looper的产生。具体下一篇再分析。
4)handler中提供了handleMessage方法,是另一种消息分发。可以和以上两种机制结合使用,用来切线程。
二、源码分析
2.1 任务池TaskQueue
在其中存在一个初始大小128的array,根据注释可以知道这是一个用做一个平衡二叉树的模型,一个父节点array[n]下挂载的两个子节点为array[2n]和array[2n+1].
2.1.1 任务实体TimerTask
TimerTask是实现了Runnable的抽象类。
二叉树的插入和删除逻辑为
2.2 任务的消费者线程TimerThread
这样消费者线程在任务队列有任务时,通过循环不断执行任务,无任务时,若想让线程停止退出则要通过改变线程字段的方式。如下:
1、队列为空且newTasksMayBeScheduled成立,则wait等待
2、1不成立,且队列为空(即newTasksMayBeScheduled不成立),则跳出循环,线程执行完毕跳出。再看run方法中finally中为清理工作。
2.3 timer对外接口
结论:当period<0时,不论任务执行时间是否延误,下一次都在固定时间执行,参照系为当前时间
当period>0时,任务在下一次时间后一定时间执行,参照系为下一次具体执行时间。
三 使用方法
使用方法很简单,调用对应方法即可。
最后有几点需要注意:
1)task是一定执行在timer子线程中(不论调用线程是否为主线程),所以若需要在任务中执行更新ui的操作,可以通过runonuithread或显示通过handler方式切回主线程。
2)在不使用时一定要及时cancel清理,释放资源。
3)当timer中有多任务时,因为后边任务会依赖前边任务执行完,尤其是如果有耗时任务,会发生定时不准确的现象。
4)当存在多任务时,若其中某个因异常而终止,则会退出所有任务的执行(消费者线程被异常终止了)
头一次写东西,用来记录自己的所学所获,文笔不好,理解可能也有所片面,希望看到的朋友不吝赐教,共同进步。