設定鬧鐘

鬧鐘 (以 AlarmManager 類別為基礎) 可讓您在應用程式的生命週期外執行以時間為準的作業。舉例來說,您可以使用鬧鐘啟動長時間執行的作業,例如每天啟動一次服務,以下載天氣預報。

鬧鐘具有以下特性:

  • 它們可讓您按設定的時間和/或間隔啟動 Intent。

  • 您可以將這些指令和廣播接收器搭配使用,以便安排工作WorkRequests 來執行其他作業。

  • 這些功能會在應用程式外執行,因此即使應用程式並未執行,甚至裝置本身處於休眠狀態,您可以利用這些功能觸發事件或動作。

  • 有助於盡可能減少應用程式的資源需求。您可以在不依賴計時器或持續執行服務的情況下安排作業時間。

設定非精準鬧鐘

如果應用程式設定了「不精確的鬧鐘」,系統會於未來的某個時間點傳送鬧鐘。非精確鬧鐘可保證鬧鐘的傳送時間,同時符合打盹等節約耗電量限制。

開發人員可利用下列 API 保證,自訂不精確鬧鐘傳送的時間。

在特定時間過後發出鬧鐘

如果應用程式呼叫 set()setInexactRepeating()setAndAllowWhileIdle(),鬧鐘一律不會在提供的觸發時間之前響起。

在 Android 12 (API 級別 31) 以上版本中,除非任何節約耗電量的限制 (例如省電模式打盹) 已啟用,否則系統會在提供的觸發時間後的一小時內叫用鬧鐘。

在時間範圍內發出鬧鐘

如果應用程式呼叫 setWindow(),鬧鐘絕不會在提供的觸發時間之前響起。除非已生效的節約耗電量限制,否則警報會在指定的時間範圍內 (從指定的觸發時間開始) 傳送。

如果您的應用程式指定 Android 12 或以上版本,系統可能會將這段時間內不精確鬧鐘的叫用延遲至少 10 分鐘。因此,600000 底下的 windowLengthMillis 參數值會裁剪為 600000

依照大致規律的間隔發出週期性鬧鐘

如果您的應用程式呼叫 setInexactRepeating(),系統會叫用多個鬧鐘:

  1. 第一個鬧鐘會在指定的時間範圍內響起 (從指定的觸發時間開始)。
  2. 後續的鬧鐘通常會在指定時間結束後響起。發出兩次鬧鐘連續叫用之間的時間有所差異。

設定精確鬧鐘

系統會在未來確切時間點叫用精確鬧鐘

大多數應用程式都可以使用不精確鬧鐘來排定工作和活動時間,以完成多項常見用途。如果應用程式的核心功能需依賴精確計時的鬧鐘 (例如鬧鐘應用程式或日曆應用程式),則可改用精確鬧鐘。

可能不需要精確鬧鐘的用途

以下列出不需要精確鬧鐘的常見工作流程:

在應用程式的生命週期中排定時間作業
Handler 類別提供多種處理時間作業的實用方法,例如在應用程式運作期間,每 n 秒執行一些工作:postAtTime()postDelayed()。請注意,這些 API 仰賴系統運作時間,而非即時
排定的背景作業,例如:更新應用程式和上傳記錄檔
WorkManager 可讓您安排具時效性的定期工作。您可以提供重複間隔和 flexInterval (至少 15 分鐘) 來定義精細的工作執行時間。
在特定時間後應採取的使用者指定動作 (即使系統處於閒置狀態)
請使用非精準鬧鐘。具體來說,請呼叫 setAndAllowWhileIdle()
在特定時間後所應採取的使用者指定動作
請使用非精準鬧鐘。具體來說,請呼叫 set()
指定時限內可以進行的使用者指定動作
請使用非精準鬧鐘。具體來說,請呼叫 setWindow()。請注意,如果應用程式指定 Android 12 以上版本,則系統允許的最小時間間隔為 10 分鐘。

設定精確鬧鐘的方式

應用程式可以使用下列其中一種方法設定精確鬧鐘。這些方法會按順序排列,讓較靠近清單下方的方法提供更多時間處理工作,但需要更多系統資源。

setExact()

只要其他省電措施沒有作用,就能在近乎精準的時間叫用鬧鐘。

除非應用程式的工作對使用者而言至關重要,否則請使用這個方法設定精確鬧鐘。

setExactAndAllowWhileIdle()

即使省電測量措施有效,也能在近乎精準的時間叫用鬧鐘。

setAlarmClock()

在未來的精確時間叫用鬧鐘。由於使用者可以看到這些鬧鐘,因此系統不會調整其運送時間。系統會將這些鬧鐘識別為最重要的鬧鐘,並在必要時留下低耗電模式以發出鬧鐘。

系統資源用量

當系統觸發應用程式設定的確切鬧鐘時,裝置會耗用大量資源 (例如電池續航力),特別是處於省電模式時。此外,系統也無法輕易批次處理這些要求,以更有效率地使用資源。

強烈建議您盡可能建立不精確鬧鐘。如要執行較長的工作,請使用鬧鐘 BroadcastReceiver 中的 WorkManagerJobScheduler 安排時程。如要在裝置處於打盹狀態時執行工作,請使用 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_ALARMUSE_EXACT_ALARM 權限。

<manifest ...>
    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
    <application ...>
        ...
    </application>
</manifest>

雖然 SCHEDULE_EXACT_ALARMUSE_EXACT_ALARM 權限所信號的功能相同,但兩者的授予方式不同,且支援不同的用途。只有在面向使用者的函式需要精確計時的操作時,應用程式才應使用精確鬧鐘,並宣告 SCHEDULE_EXACT_ALARMUSE_EXACT_ALARM 權限。

USE_EXACT_ALARM

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 廣播。應用程式應實作廣播接收器,以便執行下列操作:

  1. 確認應用程式仍擁有特殊的應用程式存取權。方法是呼叫 canScheduleExactAlarms()。這項檢查可避免應用程式在使用者授予應用程式權限的情況下立即撤銷,然後幾乎立即撤銷。
  2. 根據應用程式目前的狀態,重新排定應用程式所需的精確鬧鐘時間。這個邏輯應與應用程式收到 ACTION_BOOT_COMPLETED 廣播時的操作類似。

要求使用者授予 SCHEDULE_EXACT_ALARM 權限

選項稱為「允許設定鬧鐘和提醒」
圖 1. 「鬧鐘與提醒」特殊應用程式存取權頁面,可讓使用者允許應用程式設定精確鬧鐘。

如有必要,您可以將使用者導向系統設定中的「鬧鐘與提醒」畫面,如圖 1 所示。若要這樣做,請完成下列步驟:

  1. 在應用程式的 UI 中,向使用者說明應用程式需要排定精確鬧鐘時間的原因。
  2. 叫用包含 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)。經過時間使用「系統啟動後所經過的時間」做為參照,而即時時鐘使用世界標準時間 (Wall Clock) 時間。這表示經過的時間適合用來根據時間段設定鬧鐘 (例如,每 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:00 喚醒裝置,之後每天重複一次:

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_MINUTESINTERVAL_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 能繼續執行工作,使用者不必手動重新啟動鬧鐘。

步驟如下:

  1. 在應用程式資訊清單中設定 RECEIVE_BOOT_COMPLETED 權限。如此一來,應用程式就能接收在系統啟動完畢之後廣播的 ACTION_BOOT_COMPLETED (這個做法只會在使用者至少啟動過一次應用程式時才有效):

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. 實作 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.
            }
        }
    }
    
  3. 請使用可篩選 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) 的裝置支援打盹模式,這樣可以延長裝置的電池續航力。裝置處於打盹模式時,鬧鐘不會觸發。系統會將任何已排定的鬧鐘延後,直到裝置退出「打盹」模式為止。如果您需要在裝置處於閒置狀態時完成工作,可以採用以下幾種選項:

最佳做法

您在設計週期性鬧鐘時所做的任何選擇,都會影響應用程式使用 (或濫用) 系統資源的方式。舉例來說,假設有一個與伺服器同步的熱門應用程式。以時鐘時間為準,且每個應用程式執行個體都是在晚上 11:00 同步,則伺服器的負載可能會導致高度延遲,甚至造成「阻斷服務」。請按照下列最佳做法使用鬧鐘:

  • 為因重複鬧鐘而觸發的任何網路要求新增隨機性 (時基誤差):

    • 在鬧鐘觸發時執行任何本機工作。「本機工作」是指任何未傳輸到伺服器或需要伺服器資料的項目

    • 同時,將包含網路要求的鬧鐘排程在某個隨機時間觸發。

  • 請盡量縮短鬧鐘頻率。

  • 請勿不必要的喚醒裝置 (這個行為是由鬧鐘類型決定,如「選擇鬧鐘類型」一文所述)。

  • 請勿讓鬧鐘觸發時間更精準。

    使用 setInexactRepeating(),而非 setRepeating()。使用 setInexactRepeating() 時,Android 會同步處理多個應用程式的週期性鬧鐘,並同時觸發這些鬧鐘。這樣可減少系統必須喚醒裝置的總次數,進而降低電池耗電量。從 Android 4.4 (API 級別 19) 開始,所有重複鬧鐘都是「不精確鬧鐘」。請注意,雖然 setInexactRepeating()setRepeating() 要改善,但如果每個應用程式執行個體大約同時在伺服器命中伺服器,伺服器仍可能會造成伺服器負荷。因此,如前所述,請為網路要求增加鬧鐘的隨機性。

  • 盡量避免在時鐘時間響起鬧鐘。

    以精確觸發時間為依據的重複鬧鐘會無法妥善調整。如果可以,請使用 ELAPSED_REALTIME。下一節會詳細說明不同的鬧鐘類型。