博文出處:關(guān)于使用AlarmManager的注意事項(xiàng)哮伟,歡迎大家關(guān)注我的博客亮蒋,謝謝献幔!
快過(guò)年了煮寡,更新春節(jié)前的最后一篇博客屉佳。
最近在做一個(gè)需求:客戶(hù)端按照規(guī)定的時(shí)間間隔向服務(wù)端發(fā)送定位。一看到這個(gè)需求就想到了使用 AlarmManager
來(lái)實(shí)現(xiàn)洲押。 AlarmManager
經(jīng)常被用來(lái)執(zhí)行定時(shí)任務(wù)武花,比如設(shè)置鬧鈴、發(fā)送心跳包等杈帐。也許有人會(huì)有疑問(wèn):為什么不能使用相同具有定時(shí)效果的 Timer
和 Handler
呢体箕?
其實(shí)答案非常簡(jiǎn)單,相對(duì)于 Handler
來(lái)說(shuō)挑童,使用 sendEmptyMessageDelayed
方法是依賴(lài)于 Handler
所在的線程的累铅,如果線程結(jié)束,就起不到定時(shí)任務(wù)的效果站叼;而 AlarmManager
依賴(lài)的是 Android 系統(tǒng)的服務(wù)娃兽,具備喚醒機(jī)制。比起 Handler
也就更合適了尽楔。
而至于 Timer
可以精確地做到定時(shí)操作投储,但是相比于 AlarmManager
而言還是差了一截第练。同理,如果手機(jī)關(guān)屏后長(zhǎng)時(shí)間不使用玛荞, CPU 就會(huì)進(jìn)入休眠模式娇掏。這個(gè)使用如果使用 Timer
來(lái)執(zhí)行定時(shí)任務(wù)就會(huì)失敗,因?yàn)?Timer
無(wú)法喚醒 CPU 勋眯。
所以婴梧,綜上所述,AlarmManager
就成為了最佳選擇客蹋。
SDK API < 19
一般情況下塞蹭,使用 AlarmManager
來(lái)執(zhí)行重復(fù)定時(shí)任務(wù)的代碼如下所示:
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
或者
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
setRepeating
該方法用于設(shè)置重復(fù)定時(shí)任務(wù)。
- 第一個(gè)參數(shù)表示鬧鐘類(lèi)型:一般為
AlarmManager.ELAPSED_REALTIME_WAKEUP
或者AlarmManager.RTC_WAKEUP
讶坯。它們之間的區(qū)別就是前者是從手機(jī)開(kāi)機(jī)后的時(shí)間浮还,包含了手機(jī)睡眠時(shí)間;而后者使用的就是手機(jī)系統(tǒng)設(shè)置中的時(shí)間闽巩。所以如果設(shè)置為AlarmManager.RTC_WAKEUP
钧舌,那么可以通過(guò)修改手機(jī)系統(tǒng)的時(shí)間來(lái)提前觸發(fā)定時(shí)事件。另外涎跨,對(duì)于相似的AlarmManager.ELAPSED_REALTIME
和AlarmManager.RTC
來(lái)說(shuō)洼冻,它們不會(huì)喚醒 CPU 。所以使用的頻率較少隅很; - 第二個(gè)參數(shù)表示任務(wù)首次執(zhí)行時(shí)間:與第一個(gè)參數(shù)密切相關(guān)撞牢。第一個(gè)參數(shù)若為
AlarmManager.ELAPSED_REALTIME_WAKEUP
,那么當(dāng)前時(shí)間就為SystemClock.elapsedRealtime()
叔营;若為AlarmManager.RTC_WAKEUP
屋彪,那么當(dāng)前時(shí)間就為System.currentTimeMillis()
; - 第三個(gè)參數(shù)表示兩次執(zhí)行的間隔時(shí)間:這個(gè)參數(shù)沒(méi)什么好講的绒尊,一般為常量畜挥;
- 第四個(gè)參數(shù)表示對(duì)應(yīng)的響應(yīng)動(dòng)作:一般都是去發(fā)送廣播,然后在廣播接收
onReceive(Context context, Intent intent)
中做相關(guān)操作婴谱。
至此蟹但,一切順利,暢通無(wú)阻谭羔。
SDK API >= 19 && SDK API < 23
當(dāng)你寫(xiě)好代碼华糖、滿(mǎn)心歡喜地將程序跑在手機(jī)上的時(shí)候,傻眼了瘟裸!你會(huì)發(fā)現(xiàn)在 Android 4.4 及以上版本的定時(shí)任務(wù)不是按照規(guī)定時(shí)間間隔來(lái)執(zhí)行的客叉。比如你設(shè)置了每隔 3 分鐘發(fā)出一個(gè) HTTP 請(qǐng)求,結(jié)果你一看莫名其妙地變成了隔 5 分鐘發(fā)一次。
What the fuck?
然后你查閱 Android 官網(wǎng)中關(guān)于 Android 4.4 API 會(huì)看到如下幾句話:
恍然大悟兼搏!原來(lái)是 Google 為了追求系統(tǒng)省電卵慰,所以“偷偷加工”了一下喚醒的時(shí)間間隔。但也正如上面官網(wǎng)中所說(shuō)的那樣向族,如果在 Android 4.4 及以上的設(shè)備還要追求精準(zhǔn)的鬧鐘定時(shí)任務(wù),要使用 setExact()
方法棠绘。
所以件相,相應(yīng)的代碼就變成了這樣:
// pendingIntent 為發(fā)送廣播
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) {
// 重復(fù)定時(shí)任務(wù)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
}
// to do something
doSomething();
}
};
當(dāng)你寫(xiě)好了“加強(qiáng)版”的 AlarmManager
之后,內(nèi)心肯定無(wú)比小激動(dòng)氧苍。這下總應(yīng)該行了吧夜矗?運(yùn)行一下,果然沒(méi)錯(cuò)让虐!在 Android 4.4 上的確按照規(guī)定的時(shí)間間隔在執(zhí)行任務(wù)紊撕。哈哈,這下大功告成了I耐弧6苑觥!
SDK API >= 23
在 Android 4.4 上品嘗到勝利的甜頭后惭缰,你順便在 Android 6.0 的設(shè)備上測(cè)試了一下浪南。結(jié)果。漱受。络凿。。昂羡。絮记。你又 TMD 傻眼了!
發(fā)現(xiàn)在設(shè)備關(guān)屏靜止一段時(shí)間后虐先, AlarmManager
又又又不能正常工作了怨愤。相必此時(shí)你連日狗的心都有了吧!強(qiáng)忍著淚水蛹批,再次打開(kāi) Android 官網(wǎng)中關(guān)于 Android 6.0 變更 憔四,發(fā)現(xiàn)在 Android 6.0 中引入了低電耗模式和應(yīng)用待機(jī)模式。然后接著往下看 對(duì)低電耗模式和應(yīng)用待機(jī)模式進(jìn)行針對(duì)性?xún)?yōu)化 般眉,發(fā)現(xiàn)會(huì)有下面一段話:
啊啊啊啊啊傲苏浴!之前在 Android 4.4 上能用的 setExact()
方法在 Android 6.0 上因?yàn)榈碗姾哪J接植荒苷J褂昧说樵摺5鞘裂矗珿oogle 又又又提供了新的方法 setExactAndAllowWhileIdle()
來(lái)解決在低電耗模式下的鬧鐘觸發(fā)。
所以,Attention络断!相關(guān)的代碼又被改寫(xiě)為這樣:
// pendingIntent 為發(fā)送廣播
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) {
// 重復(fù)定時(shí)任務(wù)
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 版本差異導(dǎo)致 AlarmManager
的“坑”填完了。這份代碼已經(jīng)可以滿(mǎn)足日常的重復(fù)定時(shí)任務(wù)了貌笨。
好了弱判,該講的都講完了,上床睡覺(jué)锥惋。倉(cāng)促地結(jié)尾昌腰,預(yù)祝大家新年快樂(lè)!
Goodbye 膀跌!