本章目錄
- Part One:Timer
- Part Two:AlarmManager
Android中有很多種實(shí)現(xiàn)定時任務(wù)的方式,比如Timer,CountDownTimer, AlarmManager,handler和Thread。不過,主要常用的有三種:
- Timer(Java遺留的)
- Handler(下雪動畫那篇使用過了)
- AlarmManager(Android官方推薦)
Part One:Timer
Timer是一個定時器工具,包含一系列的schedule方法用于實(shí)施定時計劃。TimerTask是一個子線程的抽象類,方便在后臺處理一些比較復(fù)雜的邏輯,然后利用Handler在主線程刷新UI。
下面我們通過修改先前的案例來認(rèn)識一下這兩個類的具體應(yīng)用。
- 首先,在activity_main.xml的cacheContainer_main布局容器中,放一個TextView,用來顯示滾動文字。
<TextView
android:id="@+id/textView_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:textColor="@android:color/white"
android:layout_marginBottom="64dp"/>
- 然后在MainActivity里面初始化這個TextView和SnowView(這里初始化SnowView是用來關(guān)閉自定義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);
}
- 接著初始化一段文字?jǐn)?shù)組,用來以滾動的方式,不斷更新顯示的文字。
private String[] contents = new String[]{"時間久了,我都快忘記我們相識多少年了,",
"我只知道我認(rèn)識你很久了,", "漫長的時間卻拉不近我們的距離,",
"一天一點(diǎn)愛戀,一夜一點(diǎn)思念,", "不想再等了,有些話想對你說。"};
- 最后,把先前定義的openWindow方法改成switchContent,讓方法名字表達(dá)的更準(zhǔn)確一些。同時在方法里初始化Timer和TimerTask,并調(diào)用相應(yīng)的方法。
private int index = 0;//數(shù)組的索引,用于讓TextView顯示不同內(nèi)容,初識從0開始
private void switchContent() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//刷新UI必須執(zhí)行在主線程
runOnUiThread(new Runnable() {
@Override
public void run() {
//如果字符串?dāng)?shù)組全部顯示完畢,下標(biāo)清0,防止越界。同是,讓自定義View停止繪制。
//Timer停止計劃任務(wù),并執(zhí)行開窗動畫
if (index == contents.length){
index = 0;
snowView.stopDraw();
initCache();
timer.cancel();
}
//讓TextView滾動顯示文字
textView.setText(contents[index]);
index++;
}
});
}
}, 50, 4000);
}
整個計劃任務(wù)的實(shí)現(xiàn)邏輯并不復(fù)雜,就是不斷變換數(shù)組索引坐標(biāo),讓TextView顯示不同的內(nèi)容。
這里要重點(diǎn)說一下我們使用的timer.schedule(task, delay, period)的三個參數(shù):
- task:TimerTask對象,就是要周期執(zhí)行的任務(wù)。
- delay:從計時器初始化完畢后,開始啟動的延遲時間。
-
period:定時器的間隔時間。
比如,我們的案例就是50毫秒后啟動定時器,沒4秒更新一下文字,文字刷新完執(zhí)行動畫。
滾動文字.gif
Part Two:AlarmManager
前面提到的Timer實(shí)現(xiàn)定時任務(wù)的方式使用的是Java的API,大部分情況下都是滿足需求的,比如本例。但是如果遇到手機(jī)鎖屏或者關(guān)機(jī),喚醒CPU執(zhí)行定時任務(wù)時,就會遇到一些問題,比如鬧鐘。所以,官方推薦使用AlrmManager來執(zhí)行定時任務(wù)。
AlarmManager的實(shí)現(xiàn)方式也不是很復(fù)雜,只是需要用到PendingIntent,具體步驟如下:
- 先前的準(zhǔn)備工作不變,只是改變switchContent方法。首先,注釋掉先前寫的代碼,然后聲明一個全局的AlarmManager
private void switchContent() {
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
}
private AlarmManager alarmManager;
- 然后聲明一個全局的PendingIntent,并設(shè)置AlarmManager的重復(fù)方式
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是個延遲意圖對象,這里是發(fā)送廣播,所以使用getBroadcast方法,也可以使用getService啟動Service或者getActivity啟動Activity來執(zhí)行某項固定任務(wù)。getBroadcast的參數(shù)前面都好理解,最后一個參數(shù)的效果有:
- FLAG_CANCEL_CURRENT:如果系統(tǒng)中有一個相同的PendingIntent對象,那么就取消舊的,然后重新生成一個。
- FLAG_NO_CREATE:如果當(dāng)前系統(tǒng)中不存在相同的PendingIntent對象,系統(tǒng)將不會創(chuàng)建該P(yáng)endingIntent對象而是直接返回null。
- FLAG_ONE_SHOT:該P(yáng)endingIntent只作用一次。在該P(yáng)endingIntent對象通過send()方法觸發(fā)過后,PendingIntent將自動調(diào)用cancel()進(jìn)行銷毀,那么如果你再調(diào)用send()方法的話,系統(tǒng)將會返回一個SendIntentException。
- FLAG_UPDATE_CURRENT:如果系統(tǒng)中有一個和你描述的PendingIntent對等的PendingInent,那么系統(tǒng)將使用該P(yáng)endingIntent對象,但是會使用新的Intent來更新之前PendingIntent中的Intent對象數(shù)據(jù),例如更新Intent中的Extras。
另外,setRepeating也需要注意,從Android5.0開始,第二個參數(shù)triggerAtMillis(觸發(fā)時間)不得低于5秒,intervalMillis(重復(fù)間隔)不得低于60秒。這兩個參數(shù)如果比這兩個值小,默認(rèn)分別是5秒和60秒,只有大于才會生效,因為間隔太短會費(fèi)電~短時間的定時任務(wù),官方推薦使用Handler方式。
所以,其實(shí)AlarmManager并不適合本例,這里只是講一下用法。
setRepeating方法的第一個參數(shù)也單獨(dú)說下: - AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態(tài)下會喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘使用絕對時間,狀態(tài)值為0;
- AlarmManager.RTC表示鬧鐘在睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用絕對時間,即當(dāng)前系統(tǒng)時間,狀態(tài)值為1;
- AlarmManager.ELAPSED_REALTIME_WAKEUP表示鬧鐘在睡眠狀態(tài)下會喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘也使用相對時間,狀態(tài)值為2;
- AlarmManager.ELAPSED_REALTIME表示鬧鐘在手機(jī)睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用相對時間(相對于系統(tǒng)啟動開始),狀態(tài)值為3;
- AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機(jī)關(guān)機(jī)狀態(tài)下也能正常進(jìn)行提示功能,所以是5個狀態(tài)中用的最多的狀態(tài)之一,該狀態(tài)下鬧鐘也是用絕對時間,狀態(tài)值為4;不過本狀態(tài)好像受SDK版本影響,某些版本并不支持;
- 接下來就是自定義一個廣播接收者,當(dāng)接收到廣播時,執(zhí)行的任務(wù):
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);
結(jié)果就是每隔60秒發(fā)送一個廣播,廣播接收器收到廣播后,執(zhí)行相應(yīng)的任務(wù)。
