設定鬧鐘

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

警報具有下列特性:

  • 可讓您在設定的時間和/或間隔觸發 Intent。

  • 您可以搭配廣播接收器使用,排定工作WorkRequest,執行其他作業。

  • 這些作業會在應用程式外部運作,因此即使應用程式未執行,甚至裝置處於休眠狀態,您仍可使用這些作業觸發事件或動作。

  • 可協助您盡量減少應用程式的資源需求。您可以安排作業,不必依賴計時器或持續執行的服務。

設定非精準鬧鐘

應用程式設定不精確的鬧鐘時,系統會在未來某個時間傳送鬧鐘。不精確的鬧鐘可確保鬧鐘在特定時間響起,同時遵守省電限制 (例如微光模式)。

開發人員可以運用下列 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

  • 使用者授予
  • 更多用途
  • 應用程式應確認權限未遭撤銷

如果應用程式以 Android 13 (API 級別 33) 以上版本為目標,系統不會預先授予 SCHEDULE_EXACT_ALARM 權限。如果使用者透過備份與還原作業,將應用程式資料轉移至執行 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 廣播傳送給應用程式。應用程式應實作廣播接收器,執行下列操作:

  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)。經過的實際時間會以「系統啟動後經過的時間」做為參考,而即時時鐘則會使用世界標準時間 (壁鐘時間)。也就是說,由於經過的實際時間不受時區或語言代碼影響,因此適合根據時間流逝設定鬧鐘 (例如每 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

取消鬧鐘

視您的應用程式而定,您可能需要加入取消鬧鐘的功能。 如要取消鬧鐘,請在 Alarm Manager 中呼叫 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 點同步,伺服器負載可能會導致高延遲,甚至「服務阻斷」。使用鬧鐘時,請遵循下列最佳做法:

  • 針對因重複鬧鐘而觸發的任何網路要求,加入隨機性 (抖動):

    • 在鬧鐘觸發時執行任何本機工作。「本機工作」是指不會連線至伺服器或需要伺服器資料的任何工作。

    • 同時,排定鬧鐘在隨機時間間隔觸發,其中包含網路要求。

  • 盡量減少鬧鐘頻率。

  • 請勿不必要地喚醒裝置 (這項行為取決於鬧鐘類型,詳情請參閱「選擇鬧鐘類型」一文)。

  • 請勿將鬧鐘的觸發時間設得比實際需要更精確。

    請改用 setInexactRepeating(),而非 setRepeating()。使用 setInexactRepeating() 時,Android 會同步處理多個應用程式的重複鬧鐘,並同時觸發。這樣一來,系統喚醒裝置的總次數就會減少,進而降低電池耗電量。自 Android 4.4 (API 級別 19) 起,所有重複鬧鐘都是不精確鬧鐘。請注意,雖然 setInexactRepeating()setRepeating() 進步,但如果應用程式的每個執行個體都在同一時間存取伺服器,伺服器仍可能不堪負荷。因此,對於網路要求,請如先前所述,在鬧鐘中加入一些隨機性。

  • 請盡量避免以時鐘時間設定鬧鐘。

    如果週期性鬧鐘是根據精確的觸發時間設定,就無法順利擴充。盡可能使用 ELAPSED_REALTIME。下節將詳細說明各類警報。