一、Timer TimerTask
参考Java中的Timer和TimerTask在Android中的用法
在开发中我们有时会有这样的需求,即在固定的每隔一段时间执行某一个任务。比如UI上的控件需要随着时间改变,我们可以使用Java为我们提供的计时器的工具类,即Timer和TimerTask。
Timer是一个普通的类,其中有几个重要的方法;而TimerTask则是一个抽象类,其中有一个抽象方法run(),类似线程中的run()方法,我们使用Timer创建一个他的对象,然后使用这对象的schedule方法来完成这种间隔的操作。
//true 说明这个timer以daemon方式运行(优先级低,程序结束timer也自动结束)
java.util.Timer timer = new java.util.Timer(true);
TimerTask task = new TimerTask() {
public void run() {
//每次需要执行的代码放到这里面。
}
};
//以下是几种调度task的方法:
//time为Date类型:在指定时间执行一次。
timer.schedule(task, time);
//firstTime为Date类型,period为long,表示从firstTime时刻开始,每隔period毫秒执行一次。
timer.schedule(task, firstTime, period);
//delay 为long类型:从现在起过delay毫秒执行一次。
timer.schedule(task, delay);
//delay为long,period为long:从现在起过delay毫秒以后,每隔period毫秒执行一次。
timer.schedule(task, delay, period);
//该任务每隔2秒更新主线程的UI(在主线程的TextView显示最新的系统时间System.currentTimeMillis())。
package zhangphil.timertask;
import java.util.Timer;
import java.util.TimerTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.widget.TextView;
public class MainActivity extends Activity {
private Timer timer;
private TimerTask task;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = (TextView) findViewById(R.id.textView);
final int WHAT = 102;
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT:
tv.setText(msg.obj + "");
break;
}
}
};
task = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = WHAT;
message.obj = System.currentTimeMillis();
handler.sendMessage(message);
}
};
timer = new Timer();
// 参数:
// 1000,延时1秒后执行。
// 2000,每隔2秒执行1次task。
timer.schedule(task, 1000, 2000);
}
@Override
protected void onStop() {
super.onStop();
// 暂停
// timer.cancel();
// task.cancel();
}
}
schedule方法有三个参数
第一个参数就是TimerTask类型的对象,我们实现TimerTask的run()方法就是要周期执行的一个任务;
第二个参数有两种类型,第一种是long类型,表示多长时间后开始执行,另一种是Date类型,表示从那个时间后开始执行;
第三个参数就是执行的周期,为long类型。
schedule方法还有一种两个参数的执行重载,第一个参数仍然是TimerTask,第二个表示为long的形式表示多长时间后执行一次,为Date就表示某个时间后执行一次。
Timer就是一个线程,使用schedule方法完成对TimerTask的调度,多个TimerTask可以共用一个Timer,也就是说Timer对象调用一次schedule方法就是创建了一个线程,并且调用一次schedule后TimerTask是无限制的循环下去的,使用Timer的cancel()停止操作。当然同一个Timer执行一次cancel()方法后,所有Timer线程都被终止。
若要在TimerTask中更新主线程UI,鉴于Android编程模型不允许在非主线程中更新主线程UI,因此需要结合Android的Handler实现在Java的TimerTask中更新主线程UI。
二、ScheduledThreadPoolExecutor
public static ExecutorService newScheduledThreadPool(int corePoolSize){
return new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new DelayedWorkQueue<Runnable>());
}
ScheduledThreadPoolExecutor核心线程数量固定,非核心线程数没有限制。主要用于执行定时任务和具有固定周期的重复任务。
参考[Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代](http://blog.csdn.net/lmj623565791/article/details/27109467)
**以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每个一段时间进行数据清洗;然而Timer是存在一些缺陷的,因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷.**
1.定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行
package com.zhy.concurrency.timer;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest
{
private static long start;
public static void main(String[] args) throws Exception
{
TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task1 invoked ! "
+ (System.currentTimeMillis() - start));
try
{
Thread.sleep(3000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked ! "
+ (System.currentTimeMillis() - start));
}
};
Timer timer = new Timer();
start = System.currentTimeMillis();
timer.schedule(task1, 1000);
timer.schedule(task2, 3000);
}
}
运行结果:
task1 invoked ! 1000
task2 invoked ! 4000
task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。下面使用ScheduledThreadPool解决这个问题:
package com.zhy.concurrency.timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExecutorTest
{
private static long start;
public static void main(String[] args)
{
/**
* 使用工厂方法初始化一个ScheduledThreadPool
*/
ScheduledExecutorService newScheduledThreadPool = Executors
.newScheduledThreadPool(2);
TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
try
{
System.out.println("task1 invoked ! "
+ (System.currentTimeMillis() - start));
Thread.sleep(3000);
} catch (Exception e)
{
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked ! "
+ (System.currentTimeMillis() - start));
}
};
start = System.currentTimeMillis();
newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
}
}
2.如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行:
package com.zhy.concurrency.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ScheduledThreadPoolDemo01
{
public static void main(String[] args) throws InterruptedException
{
final TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
throw new RuntimeException();
}
};
final TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked!");
}
};
Timer timer = new Timer();
timer.schedule(task1, 100);
timer.scheduleAtFixedRate(task2, new Date(), 1000);
}
}
上面有两个任务,任务1抛出一个运行时的异常,任务2周期性的执行某个操作,输出结果:
task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
由于任务1的一次,任务2也停止运行了。。。下面使用ScheduledExecutorService解决这个问题:
package com.zhy.concurrency.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo01
{
public static void main(String[] args) throws InterruptedException
{
final TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
throw new RuntimeException();
}
};
final TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked!");
}
};
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);
}
}
代码基本一致,但是ScheduledExecutorService可以保证,task1出现异常时,不影响task2的运行:
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
#####三、AlarmManager
>Java的Timer类可以用来计划需要循环执行的任务。简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的Thread在Timer的构造方法调用时被启动,这个Thread的run方法按照条件去循环这个TimerTask队列,然后调用TimerTask的run方法。
但是,**如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效**。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。
解决方案是:它**需要用WakeLock让CPU 保持唤醒状态。那么问题就来了,这样会大量消耗手机电量(CPU唤醒和屏幕唤醒不是同一概念)**,大大减短手机待机时间。这种方式不能满足我们的需求。
注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer是单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?我的答案是,老外只是想要表示它是可执行的方法。
关于休眠,可以参考
[Android 关于休眠引发的几个血案](http://www.ithtw.com/437.html)
[Android手机休眠后时间不准确的解决方案](http://blog.csdn.net/t12x3456/article/details/7826811)
AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。
以下参考[Android基础入门教程——10.5 AlarmManager(闹钟服务)](http://blog.csdn.net/coder_pig/article/details/49423531)
**核心流程如下:**
* `AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); `
获得系统提供的AlarmManager服务的对象
* Intent设置要启动的组件:
`Intent intent = new Intent(MainActivity.this, ClockActivity.class);`
* PendingIntent对象设置动作,启动的是Activity还是Service,又或者是广播!
`PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);`
* 调用AlarmManager的set( )方法设置单次闹钟的闹钟类型,启动时间以及PendingIntent对象!
`alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi);`
//10秒钟后执行一个任务
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
**相关方法:**
* set(int type,long startTime,PendingIntent pi):一次性闹钟
* setRepeating(int type,long startTime,long intervalTime,PendingIntent pi):
重复性闹钟,和3有区别,3闹钟间隔时间不固定
* setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi):
重复性闹钟,时间不固定
* cancel(PendingIntent pi):取消AlarmManager的定时服务
* getNextAlarmClock():得到下一个闹钟,返回值AlarmManager.AlarmClockInfo
* setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
和set方法类似,这个闹钟运行在系统处于低电模式时有效
* setExact(int type, long triggerAtMillis, PendingIntent operation):
在规定的时间精确的执行闹钟,比set方法设置的精度更高
* setTime(long millis):设置系统墙上的时间
* setTimeZone(String timeZone):设置系统持续的默认时区
* setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation):
设置一个闹钟在给定的时间窗触发。类似于set,该方法允许应用程序精确地控制操作系统调 整闹钟触发时间的程度。
**关键参数讲解:**
* Type(闹钟类型):
有五个可选值:
* AlarmManager.ELAPSED_REALTIME:
闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
* AlarmManager.ELAPSED_REALTIME_WAKEUP
闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
* AlarmManager.RTC
闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
* AlarmManager.RTC_WAKEUP
表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
* AlarmManager.POWER_OFF_WAKEUP
表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
* startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟使用的是相对时间 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间相对于系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime(); 如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP), 那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。
* intervalTime:表示两次闹钟执行的间隔时间,也是以毫秒为单位.
* PendingIntent:绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。
PendingIntent是Intent的封装类。需要注意的是,
* 如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService (Context c,int i,Intent intent,int j)方法;
* 如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
* 如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用
PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。
如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。
下面是一个每隔一小时就会在后台执行定时任务的服务。
public class LongRunningService extends Service{
public IBinder onBind(Intent intent){
return null;
}
public int onStartCommand(Intent intent, int flags, int startId){
new Thread(new Runnable(){
public void run(){
Log.d("LongRunningService","executed at"+new Date().toString());
}
}).start();
}
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this,AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent,flags,startId);
}
public class AlarmReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent){
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}
public class MainActivity extends Activity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Intent intent = new Intent(this,LongRunningService);
startService(intent);
}
}
#####四、CountDownTimer
参考
[Android实现倒计时之使用CountDownTimer](http://blog.csdn.net/qq_20785431/article/details/51571300)
[[Android] CountDownTimer 简单用法与源码解析](http://blog.qiji.tech/archives/7485)
>在开发中会经常用到倒计时这个功能,包括给手机发送验证码等等,之前我的做法都是使用Handler + Timer +TimerTask来实现,现在发现了这个类,果断抛弃之前的做法,相信还是有很多人和我一样一开始不知道Android已经帮我们封装好了一个叫CountDownTimer的类。`CountDownTimer timer = new CountDownTimer(10000,1000);`以毫秒为单位,第一个参数是指从开始调用start()方法到倒计时完成的时候onFinish()方法被调用这段时间的毫秒数,也就是倒计时总的时间;第二个参数表示间隔多少毫秒调用一次 onTick方法,例如间隔1000毫秒。在调用的时候直接使用`timer.start();`
//共有4个方法,前两个抽象方法需要重写
public abstract void onTick(long millisUntilFinished);//固定间隔调用
public abstract void onFinish();//倒计时完成时被调用
public synchronized final void cancel();//取消倒计时,当再次启动会重新开始倒计时
public synchronized final CountDownTimer start();//启动倒计时
//该类的成员变量与成员函数均较少,功能实现的关键部分在于 mHandler,下面看 mHandler 的源码:
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
![内部流程](http://upload-images.jianshu.io/upload_images/2354823-46005a2dd429b052.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
看一个使用例子:
package com.per.countdowntimer;
import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView mTvShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvShow = (TextView) findViewById(R.id.show);
}
/**
* 取消倒计时
* @param v
*/
public void oncancel(View v) {
timer.cancel();
}
/**
* 开始倒计时
* @param v
*/
public void restart(View v) {
timer.start();
}
private CountDownTimer timer = new CountDownTimer(10000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
mTvShow.setText((millisUntilFinished / 1000) + "秒后可重发");
}
@Override
public void onFinish() {
mTvShow.setEnabled(true);
mTvShow.setText("获取验证码");
}
};
}
#####五、new Handler().postDelayed()
该方法就是利用我们常说的消息处理器。该方法原理就是在主线程中创建一个Handler消息处理器,然后利用其中的一个postDelayed(Runnable r, long delayMillis)方法,该方法第一个参数需要传入一个Runnable接口,并实现run()方法,第二个参数就是延迟多少时间将run()方法中的代码通过一个消息发送给消息队列,然后在主线程中执行这个消息中的代码,即是run方法中的代码,从而实现在主线程中更新界面UI。
贴代码吧:
new Handler().postDelayed(new Runnable() {//在当前线程(也即主线程中)开启一个消息处理器,并在3秒后在主线程中执行,从而来更新UI
@Override
public void run() {
//有关更新UI的代码
}
}, 3000);//3秒后发送