Android定时器Timer的使用及源码解析

新功能更新完毕,坐等测试妹妹反馈测试结果,心里美滋滋。

登上网页打开bugly,查看最近的bug情况。咦,情况不对呢,怎么又出现这个bug了,上次出现NullPointerException,我已经非空判断了,怎么又出现NullPointerException,这就很难受了。

于是仔仔细细检查代码,网络请求,数据更新,一遍下来,没问题呀,why?没有代码神兽保佑?

百思不得其解呀,咦,有情况,这个界面的网络请求好像是轮询(本项目有两个人开发,刚好这里不是我开发的,恰好那哥们请假了),轮询,emmmmmm,并发???????有没有可能是并发呢。带着问题重新去看代码,查看网络请求的触发。有意思,果然是轮询,还使用的是Timer。

Timer,这个嘛,定时任务,但是以前没玩过。没玩过不要紧,我们可以学嘛。

Timer的基本使用方法:

Timer mTimer = new Timer();
mTimer.schedule(new TimerTask() {
     @Override
     public void run() {
        //执行具体的任务
     }
}, 1000, 1000);

我们看到了schedule方法,第一次参数是执行的任务,第二次参数是第一次启动Timer的延时,第三个参数是轮询的间隔时间。

Timer是定时任务,怎么就会引发并发了呢?这个时候解决问题的方法就是,让我们大声的说出来“看~~源~~码~~”。确实,很多问题的解决方法,在源码中其实都已经为我们提供了解决方法。

我们进入Timer,我们首先都看到了两个东东

private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);

我们先看第一个东东是什么鬼。

第一个东东,从类的名字我们可以知道,是任务队列(好的命名在这个时候起了很大的关键作用),TaskQueue中定义了一个长度为128的TimerTask数组。TaskQueue类中的所有方法就是对TimerTask数字的操作,比如添加消息,移除消息等。TimerTask又是继承的Runnable接口。

TaskQueue就是维持的继承Runnable接口的消息队列。

我们再回到Timer的构造方法,Timer一共有四个构造方法。

    public Timer() {
        this("Timer-" + serialNumber());
    }
    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
    public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }

从构造方法中我们可以看出,最终都会启动一个异步线程,这个异步线程就是我们的第二个东东,TimerThread。

 

TimerThread集成Thread,在run方法中调用了mainLoop方法,我们进入mainLoop方法,此方法是整个Timer的关键。

    private void mainLoop() {
	//开启无限循环
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 如果消息队列为空,线程还活着,就堵塞线程
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
		    //如果消息队列为空,线程已经停止了,比如调用cancel(),就终止循环
                    if (queue.isEmpty())
                        break; 

                    long currentTime, executionTime;
		    //获取消息队列顶部的消息
                    task = queue.getMin();
                    synchronized(task.lock) {
			//如果消息已经被取消,则移除次消息,并进入下一次循环
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  
                        }
			//获取系统当前时间
                        currentTime = System.currentTimeMillis();
    			//获取任务执行的时间
                        executionTime = task.nextExecutionTime;
			//判断是否已经到了该执行的时间
                        if (taskFired = (executionTime<=currentTime)) {
			    //如果轮询的时间差为0,就移除,并更改状态为已执行
                            if (task.period == 0) { 
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { 
				//更新轮询时间,为下一次轮询设置时间
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
		    //如果还没有到需要轮询的时间,计算出时间差,堵塞对应的时间
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                }
                //已经到了需要执行的时间,开始执行任务
                if (taskFired)  
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

从上面源码中我们可以看出,Timer,到点之后只管执行任务,不管任务执行的过程和结果。这就容易导致并发问题。

https://mp.csdn.net/postedit/80297254

以上均为酒后胡言,如果有不对请各位大佬,批评教育。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值