Android 定时任务的8种实现方法
功能分析
功能描述
每隔5秒,打印一句,我爱你中国。
环境分析
我们知道,Android中分主线程(UI线程)和子线程,子线程无法操作UI的改变,我们目前不考虑UI问题,也不考虑线程通信问题,就考虑有多少方法可以实现上述功能。
功能分解
每需要实现一个功能的时候,我们在写代码前分析一下实现功能所需要的步骤,这样有利于我们用更清晰明了的逻辑来实现功能。根据功能描述,要实现此功能,可以拆分为两步:
- 打印 "我爱你中国" 这句话。
打印代码不需要讨论,我们就是用System.out.println("我爱你中国");这句就好。
- 每隔5秒的实现。
每隔5秒,需要通知打印的代码执行,所以我们需要实现一个带有定时发送指令或者定时可以执行逻辑的功能。
方案制定
基于上面的分析,所以我们需要在Android、java中找到可以带有定时逻辑的相关API,从原生java SDK到Android SDK自带API来一一列举:
以下是Java SDK
- while循环+sleep
- 递归+sleep
- Timer、TimerTask
- ScheduledExecutorService(带有定时任务的线程池)
//以下是Android SDK
- Handler循环发消息
- Handler的postDelayed方法
- BroadcastReceiver循环自发广播
- AlarmManger+BroadcastReceiver定时发送广播
功能实现
经过上面的分析,我们已经制定了8种实现方案,接下来我们依次写出实现代码并分析优缺点。
while循环+sleep
- 代码实现
while(true){
System.out.println("我爱你中国");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 分析
优点:代码简单,逻辑清晰
缺点:1、sleep会阻塞线程,也有被打断的风险
2、while(true)下面的代码无法执行,野路子
适用场景:间隔时间较短(秒级的),循环执行次数较少(有退出机制或者用for循环),执行逻辑简单
递归+sleep
- 代码实现
public void print() {
System.out.println("我爱你中国" );
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
print();//递归调用自身
}
- 分析
优点:代码简单,逻辑清晰
缺点:1、sleep会阻塞线程,也有被打断的风险
2、递归次数过多会出现java.lang.StackOverflowError堆栈溢出的异常,所以严格来说不算定时任务
适用场景:间隔时间较短(秒级的),循环执行次数较少(有退出机制或者用for循环)
Timer、TimerTask
- 代码实现
public class TimerTest {
static class MyTimerTask extends TimerTask {
public void run() {
System.out.println("我爱你中国");
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTimerTask(), 0, 5000);
}
}
- 分析
优点:代码正宗,没有野路子,纯正的定时任务,纯java SDK,单独线程执行,时间周期可以长些
缺点:1、Timer还有一个传入Date的方法,此方法是基于绝对时间,系统时间改变对应的时间也会改变
2、Timer线程不捕获异常,执行过程中出现异常就会终止Timer任务
3、手机关屏之后,CPU进入休眠,Timer无法唤醒CPU(据说)
适用场景:简单的定时任务,对于时间要求不是很精确,执行过程中处理好异常的捕获。可用timer.cancel()取消
ScheduledExecutorService
- 代码实现
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("我爱你中国");
}
},0,5,TimeUnit.SECONDS);
- 分析
优点:ScheduledExecutorService是一个线程池,多任务处理时效率高,基于相对时间,看起来高大上。
缺点:1、取消时需要打断线程池的运行
2、和外界的通信不太好处理
3、Android中适用度不高(android中会用AlarmManger+Service+BarocastReceiver替代)
适用场景:适合那种长期、定期执行的任务
Handler循环发消息
- 代码实现
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("我爱你中国");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
};
//调用
handler.sendEmptyMessage(0);
- 分析
优点:纯Android SDK,逻辑清晰
缺点:这种循环执行不适用于复杂任务,不适用于多次循环的,伪定时,sleep缺点上面说过了,Handler依赖其所在线程
适用场景:单纯这么写是没有适用场景,Handler就老老实实的实现消息分发吧
Handler的postDelayed方法
- 代码实现
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("我爱你中国");
handler.sendEmptyMessageDelayed(0,5000);
}
};
//调用
handler.sendEmptyMessageDelayed(0,5000);
- 分析
上面以上,没啥分析的
BroadcastReceiver循环自发广播
- 代码实现
public static final String TEST_ACTION = "XXX.XXX.XXX" + "_TEST_ACTION";
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TEST_ACTION.equals(action)) {
System.out.println("我爱你中国");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.sendBroadcast(intent);
}
}
};
//调用
IntentFilter filter = new IntentFilter();
filter.addAction(TEST_ACTION);
registerReceiver(receiver, filter);
//取消
unregisterReceiver(receiver);
- 分析
优点:Android SDK的代码,广播监听机制
缺点:单纯这么写就是缺点
适用场景:BroadcastReceiver适用于接受广播,执行一定的逻辑,可以根据注册方式不同适用不同的场景。
AlarmManger+BroadcastReceiver
- 代码实现
public class MyService extends Service {
int TIME_INTERVAL = 5000; // 这是5s
PendingIntent pendingIntent;
AlarmManager alarmManager;
@Override
public void onCreate() {
super.onCreate();
IntentFilter intentFilter = new IntentFilter(TEST_ACTION);
registerReceiver(receiver, intentFilter);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent();
intent.setAction(TEST_ACTION);
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//6.0低电量模式需要使用该方法触发定时任务
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4以上 需要使用该方法精确执行时间
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {//4。4一下 使用老方法
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
public static final String TEST_ACTION = "XXX.XXX.XXX" + "_TEST_ACTION";
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TEST_ACTION.equals(action)) {
System.out.println("我爱你中国");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
}
}
}
};
}
- 分析
优点:这是标准的Android触发定时任务的方式,依赖的是Android系统服务,有效唤醒
缺点:1、都说标准了,哪还有啥缺点(AlarmManager唤醒type字段可以区分是按照手机开机时间还是系统时间,系统时间受系统时间修改的影响,同时也有不唤醒CPU的type)
2、4.4以下,4.4到6.0,6.0以上这个类变动的较多,需要区分版本使用。
适用场景:Android应用中常见的定时任务,闹钟等等场景。
以上!