本章目录

Part One:Timer

Part Two:AlarmManager

Android中有很多种实现定时任务的方式,比如Timer,CountDownTimer, AlarmManager,handler和Thread。不过,主要常用的有三种:

Timer(Java遗留的)

Handler(下雪动画那篇使用过了)

AlarmManager(Android官方推荐)

Part One:Timer

Timer是一个定时器工具,包含一系列的schedule方法用于实施定时计划。TimerTask是一个子线程的抽象类,方便在后台处理一些比较复杂的逻辑,然后利用Handler在主线程刷新UI。

下面我们通过修改先前的案例来认识一下这两个类的具体应用。

首先,在activity_main.xml的cacheContainer_main布局容器中,放一个TextView,用来显示滚动文字。

然后在MainActivity里面初始化这个TextView和SnowView(这里初始化SnowView是用来关闭自定义View的绘制)。

private TextView textView;
private SnowView snowView;
private void initViews() {
cacheContainer = findViewById(R.id.cacheContainer_main);
animContainer = findViewById(R.id.animContainer_main);
leftAnimImageView = findViewById(R.id.leftAnimContainer_main);
rightAnimImageView = findViewById(R.id.rightAnimContainer_main);
textView = findViewById(R.id.textView_main);
snowView = findViewById(R.id.snowView_main);
}

接着初始化一段文字数组,用来以滚动的方式,不断更新显示的文字。

private String[] contents = new String[]{"时间久了,我都快忘记我们相识多少年了,",

"我只知道我认识你很久了,", "漫长的时间却拉不近我们的距离,",

"一天一点爱恋,一夜一点思念,", "不想再等了,有些话想对你说。"};

最后,把先前定义的openWindow方法改成switchContent,让方法名字表达的更准确一些。同时在方法里初始化Timer和TimerTask,并调用相应的方法。

private int index = 0;//数组的索引,用于让TextView显示不同内容,初识从0开始
private void switchContent() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//刷新UI必须执行在主线程
runOnUiThread(new Runnable() {
@Override
public void run() {
//如果字符串数组全部显示完毕,下标清0,防止越界。同是,让自定义View停止绘制。
//Timer停止计划任务,并执行开窗动画
if (index == contents.length){
index = 0;
snowView.stopDraw();
initCache();
timer.cancel();
}
//让TextView滚动显示文字
textView.setText(contents[index]);
index++;
}
});
}
}, 50, 4000);
}

整个计划任务的实现逻辑并不复杂,就是不断变换数组索引坐标,让TextView显示不同的内容。

这里要重点说一下我们使用的timer.schedule(task, delay, period)的三个参数:

task:TimerTask对象,就是要周期执行的任务。

delay:从计时器初始化完毕后,开始启动的延迟时间。

period:定时器的间隔时间。

比如,我们的案例就是50毫秒后启动定时器,没4秒更新一下文字,文字刷新完执行动画。

滚动文字.gif

Part Two:AlarmManager

前面提到的Timer实现定时任务的方式使用的是Java的API,大部分情况下都是满足需求的,比如本例。但是如果遇到手机锁屏或者关机,唤醒CPU执行定时任务时,就会遇到一些问题,比如闹钟。所以,官方推荐使用AlrmManager来执行定时任务。

AlarmManager的实现方式也不是很复杂,只是需要用到PendingIntent,具体步骤如下:

先前的准备工作不变,只是改变switchContent方法。首先,注释掉先前写的代码,然后声明一个全局的AlarmManager

private void switchContent() {
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
}
private AlarmManager alarmManager;

然后声明一个全局的PendingIntent,并设置AlarmManager的重复方式

private void switchContent() {
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent();
intent.setAction("action.REFRESHTEXTVIEW");
pendingIntent = PendingIntent.getBroadcast(this,
100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
5000, 60000, pendingIntent);
}
private AlarmManager alarmManager;
private PendingIntent pendingIntent;

其中,PendingIntent是个延迟意图对象,这里是发送广播,所以使用getBroadcast方法,也可以使用getService启动Service或者getActivity启动Activity来执行某项固定任务。getBroadcast的参数前面都好理解,最后一个参数的效果有:

FLAG_CANCEL_CURRENT:如果系统中有一个相同的PendingIntent对象,那么就取消旧的,然后重新生成一个。

FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。

FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。

FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。

另外,setRepeating也需要注意,从Android5.0开始,第二个参数triggerAtMillis(触发时间)不得低于5秒,intervalMillis(重复间隔)不得低于60秒。这两个参数如果比这两个值小,默认分别是5秒和60秒,只有大于才会生效,因为间隔太短会费电~短时间的定时任务,官方推荐使用Handler方式。

所以,其实AlarmManager并不适合本例,这里只是讲一下用法。

setRepeating方法的第一个参数也单独说下:

AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;

AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;

AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;

AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;

AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;

接下来就是自定义一个广播接收者,当接收到广播时,执行的任务:

private class AlarmBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (index == contents.length) {
index = 0;
snowView.stopDraw();
initCache();
alarmManager.cancel(pendingIntent);//取消定时器
}
//让TextView滚动显示文字
textView.setText(contents[index]);
index++;
}
}

最后,将广播注册即可

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("action.REFRESHTEXTVIEW");
BroadcastReceiver receiver = new AlarmBroadcastReceiver();
registerReceiver(receiver, intentFilter);

结果就是每隔60秒发送一个广播,广播接收器收到广播后,执行相应的任务。