AlarmManager
類別為基礎) 可讓您在應用程式生命週期外執行以時間為準的作業。舉例來說,您可以使用鬧鐘來啟動長時間執行的作業,例如每天啟動一次服務,下載天氣預報。
鬧鐘具有以下特性:
可讓您按照設定的時間和/或間隔啟動意圖。
您可以搭配廣播接收器來安排工作或 WorkRequests 來執行其他作業。
這類事件會在應用程式之外運作,因此即使應用程式處於休眠狀態或裝置本身處於休眠狀態,也都能用來觸發事件或動作。
可協助您盡量減少應用程式的資源需求。您不必依賴計時器或持續執行的服務安排作業時程。
設定非精確鬧鐘
如果應用程式設定了「非精確鬧鐘」,系統會在未來的某個時間點發出鬧鐘。不精確鬧鐘可在遵守打盹等省電限制的情況下,提供鬧鐘傳遞時間的保證。
開發人員可以利用下列 API 保證來自訂不精確鬧鐘傳送的時間。
在指定時間後觸發鬧鐘
如果應用程式呼叫 set()
、setInexactRepeating()
或 setAndAllowWhileIdle()
,鬧鐘在提供的觸發時間之前不會響起。
在 Android 12 (API 級別 31) 以上版本中,除非有啟用任何省電限制 (例如省電模式或打盹),否則系統會在提供的觸發時間的一小時內叫用鬧鐘。
在指定時間範圍內傳送鬧鐘
如果應用程式呼叫 setWindow()
,鬧鐘就不會在提供的觸發時間之前響鈴。除非所有節約耗電量限制生效,否則鬧鐘就會在指定的時間範圍內傳送 (從指定的觸發時間開始)。
如果您的應用程式指定 Android 12 或以上版本,系統可能會將時間範圍不精確鬧鐘的叫用延遲至少 10 分鐘。因此,600000
底下的 windowLengthMillis
參數值會裁剪為 600000
。
以大致頻率傳送週期性鬧鐘
如果您的應用程式呼叫 setInexactRepeating()
,系統會叫用多個鬧鐘:
- 第一個鬧鐘會在指定時間範圍內啟動 (從指定的觸發時間開始)。
- 後續鬧鐘通常會在指定時間範圍後響起。兩次連續叫用鬧鐘的時間可能有所不同。
設定精確鬧鐘
系統會在未來的確切時間點叫用精確鬧鐘。
大多數應用程式都能使用非精確鬧鐘安排工作和活動,以完成幾個常見用途。如果應用程式的核心功能取決於精確的鬧鐘 (例如鬧鐘應用程式或日曆應用程式),您就可以改用精確鬧鐘。
可能不需要精確鬧鐘的用途
以下列出不需要精確鬧鐘的常見工作流程:
- 在應用程式的生命週期中安排時間作業
Handler
類別包含幾種處理時間作業的好方法,例如在應用程式運作期間,每 n 秒處理一次工作:postAtTime()
和postDelayed()
。請注意,這些 API 依賴於系統運作時間,而非即時。- 排定的背景作業,例如:更新應用程式和上傳記錄檔
WorkManager
可讓您安排具時效性的定期工作。您可以提供重複間隔和flexInterval
(至少 15 分鐘),藉此定義精細的工作執行階段。- 在特定時間後所應採取的使用者指定動作 (即使系統處於閒置狀態)
- 請使用非精準鬧鐘。具體來說,請呼叫
setAndAllowWhileIdle()
。 - 在特定時間後所應採取的使用者指定動作
- 請使用非精準鬧鐘。具體來說,請呼叫
set()
。 - 指定時間範圍內可以進行的使用者指定動作
- 請使用非精準鬧鐘。具體來說,請呼叫
setWindow()
。請注意,如果應用程式指定 Android 12 或以上版本,允許的最小時間間隔為 10 分鐘。
設定精確鬧鐘的方式
應用程式可以使用下列其中一種方法設定精確鬧鐘。這些方法的排列順序將讓靠近清單底部的方法執行較耗時的工作,但需要更多系統資源。
setExact()
只要沒有採用其他省電措施,即可在未來近乎精確地叫用鬧鐘。
除非應用程式的工作對使用者來說具有時間重要,否則請使用此方法設定精確的鬧鐘。
setExactAndAllowWhileIdle()
即使已啟用節約耗電量的測量功能,未來仍可在近乎精確的時間叫用鬧鐘。
setAlarmClock()
可以在未來的確切時間叫用鬧鐘。由於使用者很容易看到這些鬧鐘,因此系統不會調整發送時間。系統會將這些鬧鐘識別為最重要的鬧鐘,並在必要時保留低耗電模式,以便傳送鬧鐘。
系統資源用量
系統觸發應用程式設定的精確鬧鐘時,裝置會耗用大量資源,例如電池續航力 (尤其是在省電模式下)。此外,系統無法輕鬆批次處理這些要求,因此無法更有效率地使用資源。
強烈建議您盡可能建立不精確鬧鐘。如要延長工作執行時間,請使用鬧鐘 BroadcastReceiver
中的 WorkManager
或 JobScheduler
設定排程。如要在裝置處於打盹模式時執行工作,請使用 setAndAllowWhileIdle()
建立非精確鬧鐘,再透過鬧鐘啟動工作。
宣告適當的精確鬧鐘權限
如果應用程式指定 Android 12 以上版本,您必須取得「鬧鐘與提醒」特殊的應用程式存取權。方法是在應用程式的資訊清單檔案中宣告 SCHEDULE_EXACT_ALARM
權限,如以下程式碼片段所示:
<manifest ...> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <application ...> ... </application> </manifest>
如果應用程式指定 Android 13 (API 級別 33) 以上版本,您可以選擇宣告 SCHEDULE_EXACT_ALARM
或 USE_EXACT_ALARM
權限。
<manifest ...> <uses-permission android:name="android.permission.USE_EXACT_ALARM"/> <application ...> ... </application> </manifest>
SCHEDULE_EXACT_ALARM
和 USE_EXACT_ALARM
權限代表的功能相同,但授予的授予方式不同且支援不同的用途。只有在面向使用者的應用程式需要精確計時的操作時,應用程式才應使用精確鬧鐘,並宣告 SCHEDULE_EXACT_ALARM
或 USE_EXACT_ALARM
權限。
USE_EXACT_ALARM
- 已自動授予
- 使用者無法撤銷
- 須遵守即將發布的 Google Play 政策
- 少數用途
SCHEDULE_EXACT_ALARM
- 由使用者授予
- 更多用途
- 應用程式應確認權限未撤銷
SCHEDULE_EXACT_ALARM
權限未預先獲得指定 Android 13 (API 級別 33) 以上版本的應用程式新安裝檔。如果使用者透過備份與還原作業,將應用程式資料轉移至執行 Android 14 的裝置,系統會拒絕新裝置的 SCHEDULE_EXACT_ALARM
權限。不過,如果現有應用程式已具備這項權限,系統會在裝置升級至 Android 14 時預先授予權限。
注意:如果使用 OnAlarmListener
物件 (例如使用 setExact
API) 設定精確鬧鐘,則不需要 SCHEDULE_EXACT_ALARM
權限。
使用 SCHEDULE_EXACT_ALARM
權限
與 USE_EXACT_ALARM
不同的是,使用者必須授予 SCHEDULE_EXACT_ALARM
權限。使用者和系統均可撤銷 SCHEDULE_EXACT_ALARM
權限。
如要檢查是否已授予權限給應用程式,請先呼叫 canScheduleExactAlarms()
,再嘗試設定精確鬧鐘。當應用程式的 SCHEDULE_EXACT_ALARM
權限遭到撤銷時,應用程式會停止,日後的所有精確鬧鐘都會取消。這也表示 canScheduleExactAlarms()
傳回的值在應用程式的整個生命週期中皆有效。
授予應用程式 SCHEDULE_EXACT_ALARMS
權限後,系統會向應用程式傳送 ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED
廣播訊息。應用程式應實作廣播接收器來執行下列操作:
- 確認應用程式仍擁有特殊應用程式存取權。如要這麼做,請呼叫
canScheduleExactAlarms()
。如果使用者授予應用程式權限,這項檢查作業就能保護應用程式,之後幾乎立即撤銷權限。 - 根據應用程式目前的狀態,重新安排應用程式需要的確切鬧鐘時間。這個邏輯應與應用程式收到
ACTION_BOOT_COMPLETED
廣播時執行的動作類似。
請使用者授予 SCHEDULE_EXACT_ALARM
權限
如有必要,您可以將使用者導向系統設定中的「鬧鐘與提醒」畫面,如圖 1 所示。若要這樣做,請完成下列步驟:
- 在應用程式 UI 中,向使用者說明應用程式需要排定精確鬧鐘的原因。
- 叫用包含
ACTION_REQUEST_SCHEDULE_EXACT_ALARM
意圖動作的意圖。
設定週期性鬧鐘
重複鬧鐘可讓系統定期通知應用程式。
警示設計不良可能會導致電池耗電,並對伺服器造成嚴重負載。因此,在 Android 4.4 (API 級別 19) 以上版本中,所有重複鬧鐘都是不精確鬧鐘。
週期性鬧鐘具有以下特性:
鬧鐘類型。如需進一步討論,請參閱「選擇鬧鐘類型」。
觸發時間。如果指定的觸發時間是過去的時間,鬧鐘會立即觸發。
鬧鐘的間隔。例如每天、每小時或每 5 分鐘一次。
觸發鬧鐘時觸發的待處理意圖。如果您設定使用相同待處理意圖的第二個鬧鐘,則會取代原始鬧鐘。
如要取消 PendingIntent()
,請將 FLAG_NO_CREATE
傳遞至 PendingIntent.getService()
以取得意圖例項 (如果有的話),然後將該意圖傳遞至 AlarmManager.cancel()
。
Kotlin
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager val pendingIntent = PendingIntent.getService(context, requestId, intent, PendingIntent.FLAG_NO_CREATE) if (pendingIntent != null && alarmManager != null) { alarmManager.cancel(pendingIntent) }
Java
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getService(context, requestId, intent, PendingIntent.FLAG_NO_CREATE); if (pendingIntent != null && alarmManager != null) { alarmManager.cancel(pendingIntent); }
選擇鬧鐘類型
使用週期性鬧鐘時,第一個注意事項是應設定鬧鐘類型。
鬧鐘一般分為兩種類型:「經過時間」和「即時時鐘」(RTC)。 經過即時時間會使用「系統啟動後經過的時間」做為參照,即時時鐘則採用 UTC (實際時間) 時間。也就是說,經過即時時間適合根據時區設定鬧鐘 (例如每 30 秒觸發的鬧鐘),因為不受時區或語言代碼影響。即時時鐘類型較適合依賴目前語言代碼的鬧鐘。
這兩種類型都有「喚醒」版本,指示在螢幕關閉時喚醒裝置的 CPU。這樣可確保鬧鐘在排定的時間觸發。如果應用程式有時間依附元件,這項功能就非常實用。例如在有限的時間範圍內執行特定作業。如果您沒有使用鬧鐘類型的喚醒版本,則所有重複的鬧鐘會在裝置下次喚醒時觸發。
如果您只想讓鬧鐘按照特定間隔 (例如每半小時) 觸發,請使用其中一個經過的時間類型。一般而言,這是比較好的選擇
如果您希望鬧鐘在一天的特定時間觸發,請選擇其中一種以時鐘為基礎的即時時鐘類型。但請注意,這個方法可能會有一些缺點。應用程式可能無法正確轉譯至其他語言代碼,而且如果使用者變更裝置的時間設定,可能會導致應用程式發生非預期的行為。使用即時鬧鐘類型也無法縮放,如前文所述。建議您盡可能使用「經過時間」鬧鐘。
類型清單如下:
ELAPSED_REALTIME
:根據裝置啟動後經過的時間長度,啟用待處理意圖,但未喚醒裝置。經過時間包括裝置處於休眠狀態的任何時間。ELAPSED_REALTIME_WAKEUP
:在裝置啟動後經過指定時間長度,喚醒裝置並觸發待處理意圖。RTC
:在指定時間啟動待處理意圖,但不會喚醒裝置。RTC_WAKEUP
:喚醒裝置,在指定時間觸發待處理意圖。
經過時間的即時鬧鐘範例
以下列舉幾個 ELAPSED_REALTIME_WAKEUP
的使用範例
在 30 分鐘內喚醒裝置以觸發鬧鐘,之後每 30 分鐘:
Kotlin
// Hopefully your alarm will have a lower frequency than this! alarmMgr?.setInexactRepeating( AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent )
Java
// Hopefully your alarm will have a lower frequency than this! alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
喚醒裝置,在一分鐘內觸發單次 (非重複) 鬧鐘:
Kotlin
private var alarmMgr: AlarmManager? = null private lateinit var alarmIntent: PendingIntent ... alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent -> PendingIntent.getBroadcast(context, 0, intent, 0) } alarmMgr?.set( AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000, alarmIntent )
Java
private AlarmManager alarmMgr; private PendingIntent alarmIntent; ... alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0); alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000, alarmIntent);
即時鬧鐘範例
以下列舉幾個使用 RTC_WAKEUP
的範例。
在大約下午 2 點喚醒裝置以觸發鬧鐘,每天重複一次:
Kotlin
// Set the alarm to start at approximately 2:00 p.m. val calendar: Calendar = Calendar.getInstance().apply { timeInMillis = System.currentTimeMillis() set(Calendar.HOUR_OF_DAY, 14) } // With setInexactRepeating(), you have to use one of the AlarmManager interval // constants--in this case, AlarmManager.INTERVAL_DAY. alarmMgr?.setInexactRepeating( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, AlarmManager.INTERVAL_DAY, alarmIntent )
Java
// Set the alarm to start at approximately 2:00 p.m. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 14); // With setInexactRepeating(), you have to use one of the AlarmManager interval // constants--in this case, AlarmManager.INTERVAL_DAY. alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, alarmIntent);
在上午 8 點 30 分喚醒裝置,並在之後每 20 分鐘喚醒鬧鐘:
Kotlin
private var alarmMgr: AlarmManager? = null private lateinit var alarmIntent: PendingIntent ... alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent -> PendingIntent.getBroadcast(context, 0, intent, 0) } // Set the alarm to start at 8:30 a.m. val calendar: Calendar = Calendar.getInstance().apply { timeInMillis = System.currentTimeMillis() set(Calendar.HOUR_OF_DAY, 8) set(Calendar.MINUTE, 30) } // setRepeating() lets you specify a precise custom interval--in this case, // 20 minutes. alarmMgr?.setRepeating( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, 1000 * 60 * 20, alarmIntent )
Java
private AlarmManager alarmMgr; private PendingIntent alarmIntent; ... alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0); // Set the alarm to start at 8:30 a.m. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 8); calendar.set(Calendar.MINUTE, 30); // setRepeating() lets you specify a precise custom interval--in this case, // 20 minutes. alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 1000 * 60 * 20, alarmIntent);
決定鬧鐘的精確程度
如先前所述,建立鬧鐘的第一步通常是選擇鬧鐘類型。進一步區分為鬧鐘需要的準確度。對大多數應用程式來說,setInexactRepeating()
是正確的選擇。使用此方法時,Android 會同步處理多個不精確的重複鬧鐘,並同時觸發這些鬧鐘。這麼做可降低電池耗電量。
請盡量避免使用精確鬧鐘。但如果應用程式有嚴格的時間需求,則可呼叫 setRepeating()
來設定精確鬧鐘。
使用 setInexactRepeating()
,您無法透過 setRepeating()
指定自訂間隔。您必須使用其中一個間隔常數,例如 INTERVAL_FIFTEEN_MINUTES
、INTERVAL_DAY
等。如需完整清單,請參閱 AlarmManager
。
取消鬧鐘
視應用程式而定,您可能想加入取消鬧鐘的功能。如要取消鬧鐘,請在鬧鐘管理員上呼叫 cancel()
,並傳入您不想再觸發的 PendingIntent
。舉例來說:
Kotlin
// If the alarm has been set, cancel it. alarmMgr?.cancel(alarmIntent)
Java
// If the alarm has been set, cancel it. if (alarmMgr!= null) { alarmMgr.cancel(alarmIntent); }
在裝置重新啟動時啟動鬧鐘
根據預設,系統會在裝置關閉時取消所有鬧鐘。如要防止這種情況發生,您可以將應用程式設計為在使用者重新啟動裝置時自動重新啟動週期性鬧鐘。這可確保 AlarmManager
能繼續執行工作,而不需要使用者手動重新啟動鬧鐘。
步驟如下:
在應用程式資訊清單中設定
RECEIVE_BOOT_COMPLETED
權限。如此一來,應用程式就能接收系統啟動後廣播的ACTION_BOOT_COMPLETED
(只有在使用者已啟動應用程式至少一次時才適用):<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
實作
BroadcastReceiver
以接收廣播:Kotlin
class SampleBootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == "android.intent.action.BOOT_COMPLETED") { // Set the alarm here. } } }
Java
public class SampleBootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { // Set the alarm here. } } }
使用依
ACTION_BOOT_COMPLETED
動作篩選的意圖篩選器,將接收器新增至應用程式的資訊清單檔案:<receiver android:name=".SampleBootReceiver" android:enabled="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"></action> </intent-filter> </receiver>
請注意,在資訊清單中,啟動接收器設為
android:enabled="false"
。這表示除非應用程式明確啟用該接收器,否則系統不會呼叫該接收器。這樣就能防止系統在不必要的情況下呼叫啟動接收器。您可以啟用接收器 (例如使用者設定鬧鐘),方法如下:Kotlin
val receiver = ComponentName(context, SampleBootReceiver::class.java) context.packageManager.setComponentEnabledSetting( receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP )
Java
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
以這種方式啟用接收器後,即便使用者重新啟動裝置,接收器仍會保持啟用狀態。換句話說,透過程式輔助方式啟用接收器,即使在重新啟動後仍會覆寫資訊清單設定。在停用之前,接收器會保持啟用狀態。您可以停用接收器 (例如使用者取消鬧鐘),方法如下:
Kotlin
val receiver = ComponentName(context, SampleBootReceiver::class.java) context.packageManager.setComponentEnabledSetting( receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP )
Java
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
在裝置處於打盹模式時叫用鬧鐘
搭載 Android 6.0 (API 級別 23) 的裝置支援打盹模式,這有助於延長裝置電池續航力。裝置處於打盹模式時,鬧鐘不會觸發。所有預定鬧鐘都會延後,直到裝置退出「打盹」模式為止。如果您需要在裝置處於閒置狀態時完成工作,可以使用下列幾種選項:
設定精確鬧鐘。
請使用專為執行背景工作的 WorkManager API。您可以指出系統應加快作業速度,以便盡快完成作業。詳情請參閱「使用 WorkManager 安排工作」。
最佳做法
您在設計週期性鬧鐘時所做的任何選擇,都可能會影響應用程式使用 (或濫用) 系統資源的方式。舉例來說,假設有一個熱門應用程式會與伺服器同步。如果同步處理作業是以時鐘時間為準,且應用程式的每個執行個體都是在晚上 11 點同步處理,則伺服器上的負載可能會導致高延遲,甚至「阻斷服務」。使用鬧鐘時,請遵循下列最佳做法:
針對因重複鬧鐘而觸發的任何網路要求,加入隨機 (時基誤差):
在鬧鐘觸發時執行任何本機工作。「本機工作」是指不會命中伺服器,或需要來自伺服器資料的任何項目。
同時,將包含網路要求的鬧鐘安排在某個隨機的時段啟動。
請降低鬧鐘頻率。
不要在非必要的情況下喚醒裝置 (此行為會由鬧鐘類型決定,如「選擇鬧鐘類型」所述)。
不要將鬧鐘觸發時間設定得比預期更準確。
使用
setInexactRepeating()
,而非setRepeating()
。使用setInexactRepeating()
時,Android 會同步處理多個應用程式的重複鬧鐘,並同時觸發這些鬧鐘。這樣可以減少系統必須喚醒裝置的總次數,進而減少電池耗電量。從 Android 4.4 (API 級別 19) 開始,所有重複鬧鐘都是不精確鬧鐘。請注意,雖然setInexactRepeating()
是比setRepeating()
的改善項目,但如果應用程式的所有執行個體同時連至伺服器,伺服器仍可能會癱瘓伺服器。因此,如前所述,對於網路要求,請為鬧鐘增加一些隨機性。如果可以,請避免在時鐘時間響起。
如果是基於精確觸發時間的重複鬧鐘,則該鬧鐘不會適當縮放。如果可以,請使用
ELAPSED_REALTIME
。下一節將詳細說明不同鬧鐘類型。