前言
最近项目上有这么一个需求,实时监控车辆信息,要求每隔3秒钟刷新一次地图上的车辆位置信息。我的想法是先定时从服务端获取数据存储到SharedPreferences中,然后再定时从SharedPreferences中获取数据显示到地图。对这个逻辑我不满意,但是一时也找不到别的方法,望大神指教。
在使用定时任务的时候,最开始想到的是Timer。无意中看到一种Handler加Runnable方法,觉得还是有必要记录一下。
Timer方法
Timer一般结合TimerTask使用。先看TimerTask,它是一个抽象类,里面有一个run()方法。
public abstract class TimerTask implements Runnable {
......
/**
* The action to be performed by this timer task.
*/
public abstract void run();
......
}
查看TimerTask的源码,可以看到TimerTask其实就是实现了Runnable方法,也就是说,通过Timer执行定时任务,其实就是通过一个线程做到的。
再看Timer,它其实就是一个线程管理器,通过schedule方法来开启一个线程,并实现任务调度。Timer工作机制下篇文章再撸,这里简单理解并记录使用方法。
/**
* A facility for threads to schedule tasks for future execution in a
* background thread. Tasks may be scheduled for one-time execution, or for
* repeated execution at regular intervals.
......
* <p>After the last live reference to a <tt>Timer</tt> object goes away
* <i>and</i> all outstanding tasks have completed execution, the timer's task
* execution thread terminates gracefully (and becomes subject to garbage
* collection). However, this can take arbitrarily long to occur. By
* default, the task execution thread does not run as a <i>daemon thread</i>,
* so it is capable of keeping an application from terminating. If a caller
* wants to terminate a timer's task execution thread rapidly, the caller
* should invoke the timer's <tt>cancel</tt> method.
......
* <p>This class is thread-safe: multiple threads can share a single
* <tt>Timer</tt> object without the need for external synchronization.
*
* <p>This class does <i>not</i> offer real-time guarantees: it schedules
* tasks using the <tt>Object.wait(long)</tt> method.
......
*/
截几段源码注释
- 当所有TimerTask任务完成,并且Timer引用为空的时候,会被GC回收。
- 一般程序退出时,TimerTask任务会跟随着终止,主动结束则用Timer的cancel方法。
- 多个线程可共用一个Timer,也就是多个TimerTask可以共用一个Timer。
- Timer不能保证实时任务,所有的任务都得等待调度。
说人话,来个比喻。Timer是一个码头大哥(简称T老大),手底下有一帮小弟(Thread,简称w)跟一个管家(schedule,简称S管家)。TimerTask是一个商人(简称K老板),手底下有一帮业务经理(实例化的timerTask,负责某个具体的任务,简称p经理)。他有一批货要在码头卸载。于是乎,K老板找到了T老大。
商人:T老大,我有一批货要在贵码头卸载,能不能帮帮忙,价钱好商量!
老大:路过的都是朋友,好说!好说!
商人:我这批货很急,能不能立马就下?(想要实时)
老大:先来后到,这是规矩啊!能不能马上卸货,就要看K老板您的运气了。要是运气好,就能立马卸货,要是运气不好,等个一年半载都有可能啊。哈哈哈、、、还望海涵!(无法保证实时)
商人:那就只能等T老大的好消息了。
商人就只能在码头排队等候了。某天,轮到K老板卸货了。
老大:K老板久等了!接下来在下静候吩咐。
商人:岂敢!只是鄙人的货有点杂,需要送到不同的地方,还要麻烦T老大啊。
老大:小事一桩!只管说给我的S管家听,保证K老板满意!(多个任务可共用一个Timer)
管家:K老板只管吩咐,老夫一定保质保量完成。
商人:那就多谢S管家!具体的任务我让我的经理们给您汇报。
得到老板的指示,S管家跟p经理们开始干活了。一个经理代表一个具体的任务,一个w代表一个线程。
schedule的四个方法
- schedule(task, date),指定时间执行一次
经理1:S管家,这批布匹要晚上6点往城东的布衣店送去。
管家:w1,你来,把这个送到城东的布衣店去。晚上6点就去。
- schedule(task, delay), 从现在起,delay毫秒后,执行一次
经理2:S管家,这批水果过两个小时往城东的水果店送去。
管家:w2,你来,两个小时后,开始往城东的水果店送。
- schedule(task, firstTime, period),firstTime时刻开始,每隔period毫秒执行一次
经理3:S管家,这批黄金要明天凌晨4点开始,每隔1小时往城南的金铺送一次。
管家:w3,你来,明天早起,凌晨4点开始干活,没喊停,就一直干。
- schedule(task, delay, period),现在开始,delay毫秒后,每隔period毫秒执行一次
经理4:S管家,这批书要往城北的陈家送去,2个小时后他们家才有人,每隔一个小时送一次。
管家:w4,你来,吃点东西,2个小时后往陈家送书,一个小时送一次,没喊停,就一直干。
若干天后,S管家没有收到K老板的支付款,便向T老大汇报。
管家:老大,K老板不守信用啊,款项没有及时到账,如何处理?
老大:不守规矩,以后不跟他玩了,让他找别人。(cancel方法,主动结束timerTask)
商人:那我不得重新找码头了?T老大,通融通融吧。
老大:没门儿!(一旦取消,要想继续只能重新new一个Timer)
待K老板交足款项后,只能重新排队等待卸货。某天,海啸来了,码头被毁(程序退出),一切都没了(timerTask被动结束)。
啰嗦了半天,不知道说明白没有。还是上代码,直观显示。
......
private Timer timer = new Timer();
......
private void useTimer() {
//多个任务可共享一个timer
timer.schedule(new MarkerTask(), 1000, 2000);
timer.schedule(new MapTask(), 1000, 3000);
}
......
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.stopRefresh://取消定时任务
Log.d("MainActivity", "onClick: stopRefresh");
timer.cancel();
break;
case R.id.continueRefresh://继续定时任务
Log.d("MainActivity", "onClick: continueRefresh");
Timer timer1 = new Timer();
timer1.schedule(new MarkerTask(), 1000, 2000);
timer1.schedule(new MapTask(), 1, 3000);
break;
default:
break;
}
}
......
private class MapTask extends TimerTask {
@Override
public void run() {
refreshMapViewDetail();
}
}
private class MarkerTask extends TimerTask {
@Override
public void run() {
randomMarkerDetail();
}
}
......
Handler加Runnable方法
这个方法的核心思想就是在Runnable内部,将本runnable继续插入主线程队列中。理解了Handler的工作机制,这个方法就更好理解,这里直接贴代码。
......
mapRefreshRun = new Runnable() {
@Override
public void run() {
//更新地图上的数据
refreshMapViewDetail();
//将本runnable继续插入主线程中,再次执行。
handler.postDelayed(this, 3000);
}
};
handler.postDelayed(mapRefreshRun, 1);
......
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.stopRefresh:
Log.d("MainActivity", "onClick: stopRefresh");
handler.removeCallbacks(mapRefreshRun);
break;
case R.id.continueRefresh:
Log.d("MainActivity", "onClick: continueRefresh");
handler.postDelayed(mapRefreshRun, 1);
break;
default:
break;
}
}
......
要取消任务直接用Handler的removeCallbacks方法,要继续任务,则将任务继续插入主线程中。
总结
Timer方法是新建子线程,在子线程中执行想要的动作,故不可以直接更改UI;
Handler方法是在主线程中,插入Runnable,可以直接更改UI。对界面的操作,比如点击、滑动这些都是要在主线程中排队进行的,如果我们这个定时任务的period太短,比如设为0,会否影响用户的操作响应速度呢?在这个简单的Demo中,试了下,没有太明显的感觉,但这个问题还是要留意。
本Demo的代码请移步:GitHub