天天看點

關于使用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?

關于使用AlarmManager的注意事項

然後你查閱 Android 官網中關于 Android 4.4 API 會看到如下幾句話:

關于使用AlarmManager的注意事項

恍然大悟!原來是 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的注意事項

發現在裝置關屏靜止一段時間後, 

AlarmManager

 又又又不能正常工作了。相必此時你連日狗的心都有了吧!強忍着淚水,再次打開 Android 官網中關于 Android 6.0 變更 ,發現在 Android 6.0 中引入了低電耗模式和應用待機模式。然後接着往下看 對低電耗模式和應用待機模式進行針對性優化 ,發現會有下面一段話:

關于使用AlarmManager的注意事項

啊啊啊啊啊啊!之前在 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的注意事項/