關於使用AlarmManager的注意事項
.
最近在做一個需求:客戶端按照規定的時間間隔向服務端發送定位。一看到這個需求就想到了使用 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
和AlarmManager.RTC
來說,它們不會喚醒 CPU 。所以使用的頻率較少; - 第二個參數表示任務首次執行時間:與第一個參數密切相關。第一個參數若為
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
的“坑”填完了。這份代碼已經可以滿足日常的重復定時任務了。