快過年了,更新春節前的最後一篇部落格。
最近在做一個需求:用戶端按照規定的時間間隔向服務端發送定位。一看到這個需求就想到了使用
AlarmManager
來實作。
AlarmManager
經常被用來執行定時任務,比如設定鬧鈴、發送心跳包等。也許有人會有疑問:為什麼不能使用相同具有定時效果的
Timer
和
Handler
呢?
其實答案非常簡單,相對于
Handler
來說,使用
sendEmptyMessageDelayed
方法是依賴于
Handler
所在的線程的,如果線程結束,就起不到定時任務的效果;而
AlarmManager
依賴的是 Android 系統的服務,具備喚醒機制。比起
Handler
也就更合适了。
而至于
Timer
可以精确地做到定時操作,但是相比于
AlarmManager
而言還是差了一截。同理,如果手機關屏後長時間不使用, CPU 就會進入休眠模式。這個使用如果使用
Timer
來執行定時任務就會失敗,因為
Timer
無法喚醒 CPU 。
是以,綜上所述,
AlarmManager
就成為了最佳選擇。
SDK API < 19
一般情況下,使用
AlarmManager
來執行重複定時任務的代碼如下所示:
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
或者
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
setRepeating
該方法用于設定重複定時任務。
- 第一個參數表示鬧鐘類型:一般為
或者AlarmManager.ELAPSED_REALTIME_WAKEUP
。它們之間的差別就是前者是從手機開機後的時間,包含了手機睡眠時間;而後者使用的就是手機系統設定中的時間。是以如果設定為AlarmManager.RTC_WAKEUP
,那麼可以通過修改手機系統的時間來提前觸發定時事件。另外,對于相似的AlarmManager.RTC_WAKEUP
和AlarmManager.ELAPSED_REALTIME
來說,它們不會喚醒 CPU 。是以使用的頻率較少;AlarmManager.RTC
- 第二個參數表示任務首次執行時間:與第一個參數密切相關。第一個參數若為
,那麼目前時間就為AlarmManager.ELAPSED_REALTIME_WAKEUP
;若為SystemClock.elapsedRealtime()
,那麼目前時間就為AlarmManager.RTC_WAKEUP
;System.currentTimeMillis()
- 第三個參數表示兩次執行的間隔時間:這個參數沒什麼好講的,一般為常量;
- 第四個參數表示對應的響應動作:一般都是去發送廣播,然後在廣播接收
中做相關操作。onReceive(Context context, Intent intent)
至此,一切順利,暢通無阻。
SDK API >= 19 && SDK API < 23
當你寫好代碼、滿心歡喜地将程式跑在手機上的時候,傻眼了!你會發現在 Android 4.4 及以上版本的定時任務不是按照規定時間間隔來執行的。比如你設定了每隔 3 分鐘發出一個 HTTP 請求,結果你一看莫名其妙地變成了隔 5 分鐘發一次。
What the fuck?
然後你查閱 Android 官網中關于 Android 4.4 API 會看到如下幾句話:
恍然大悟!原來是 Google 為了追求系統省電,是以“偷偷加工”了一下喚醒的時間間隔。但也正如上面官網中所說的那樣,如果在 Android 4.4 及以上的裝置還要追求精準的鬧鐘定時任務,要使用
setExact()
方法。
是以,相應的代碼就變成了這樣:
// pendingIntent 為發送廣播
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}
private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 重複定時任務
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
}
// to do something
doSomething();
}
};
當你寫好了“加強版”的
AlarmManager
之後,内心肯定無比小激動。這下總應該行了吧?運作一下,果然沒錯!在 Android 4.4 上的确按照規定的時間間隔在執行任務。哈哈,這下大功告成了!!!
SDK API >= 23
在 Android 4.4 上品嘗到勝利的甜頭後,你順便在 Android 6.0 的裝置上測試了一下。結果。。。。。。你又 TMD 傻眼了!
發現在裝置關屏靜止一段時間後,
AlarmManager
又又又不能正常工作了。相必此時你連日狗的心都有了吧!強忍着淚水,再次打開 Android 官網中關于 Android 6.0 變更 ,發現在 Android 6.0 中引入了低電耗模式和應用待機模式。然後接着往下看 對低電耗模式和應用待機模式進行針對性優化 ,發現會有下面一段話:
啊啊啊啊啊啊!之前在 Android 4.4 上能用的
setExact()
方法在 Android 6.0 上因為低電耗模式又不能正常使用了。但是,Google 又又又提供了新的方法
setExactAndAllowWhileIdle()
來解決在低電耗模式下的鬧鐘觸發。
是以,Attention!相關的代碼又被改寫為這樣:
// pendingIntent 為發送廣播
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}
private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 重複定時任務
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);
}
// to do something
doSomething();
}
};
到了這裡,總算是把因 Android 版本差異導緻
AlarmManager
的“坑”填完了。這份代碼已經可以滿足日常的重複定時任務了。
好了,該講的都講完了,上床睡覺。倉促地結尾,預祝大家新年快樂!
Goodbye !
References
- AlarmManager
- Android 4.4 API
- Android 6.0 變更
- 對低電耗模式和應用待機模式進行針對性優化
參考:http://yuqirong.me/2017/01/21/關于使用AlarmManager的注意事項/