定时器之Timer

概述

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中几种常用的定时器和延时方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • Timer 定时器相信都不会陌生,之所以拿它来做源码分析,是发现整个控制流程可以体现很多有意思的东西。 在业务开发...
    石先阅读 6,338评论 2 13
  • Timer和TimerTask Timer就是一个调度器,而TimerTask只是一个实现了run方法的类,而具体...
    史路比阅读 375评论 0 2
  • 这世间最珍贵的, 不是已经失去的东西, 而是你现在所拥有的一切, 你要明白, 失去的,未必就是好的, 能把握住的才...
    逍遥_9353阅读 399评论 0 0
  • 关键词:定位法 我能行的精神 读书心得: 心智中的小阶梯:心智有一个真对现有信息量的防御机制,它拒绝其所不能“运算...
    Krista谭谭阅读 364评论 0 1
  • 为什么是阿姨,因为感觉是我妈的岁数;为什么要道歉,因为自觉伤害了她。那天,为了一件事,我愤怒了,出于愤怒,我骂人了...
    上书房行走阅读 712评论 0 0