设置闹钟

闹钟(基于 AlarmManager 类)提供了一种执行 基于时间的操作。 例如,您可以使用闹钟来启动长时间运行的操作,如 就是每天启动一次服务以下载天气预报。

闹钟具有以下特征:

  • 它们可让您按设定的时间和/或间隔触发 intent。

  • 您可以将它们与广播接收器结合使用 jobs 或 使用 WorkRequest 执行其他任务, 操作。

  • 它们在应用外部运行,因此您可以使用它们来 事件或操作,即使您的应用未运行, 本身处于休眠状态。

  • 它们可帮助您最大限度地降低应用的资源需求。您可以安排 而无需依赖计时器或持续运行的服务。

设置不精确闹钟

当应用设置不精确闹钟时,系统会在某个时间点发出闹钟 。不精确闹钟可保证 同时遵循省电限制 低电耗模式

开发者可以利用以下 API 保证来自定义 闹钟发送时间不精确。

在特定时间后发出闹钟

如果您的应用调用 set()setInexactRepeating(), 或setAndAllowWhileIdle(), 闹钟永远不会在提供的触发时间之前触发。

在 Android 12(API 级别 31)及更高版本中,系统会在 1 秒内调用闹钟。 小时(除非有任何省电限制) 有效的内容(例如省电模式低电耗模式

在某个时间范围内发出警报

如果您的应用调用 setWindow(),则闹钟将不会在提供的 触发时间。除非任何省电限制生效,否则闹钟会始终处于 在指定时间范围内(从指定触发器开始)内投放 。

如果您的应用以 Android 12 或更高版本为目标平台,系统可能会延迟 对有时间范围的不精确闹钟调用至少 10 分钟。对于 因此,600000 下的 windowLengthMillis 参数值会被裁剪为 600000

大致定期提供重复闹钟

如果您的应用调用 setInexactRepeating(), 系统会调用多个 alarm:

  1. 第一个闹钟会在指定的时间窗口内响铃,从 指定触发时间。
  2. 后续闹钟通常会在指定时间窗口后响铃 。连续两次调用闹钟的间隔时间可能会有所不同。

设置精确闹钟

系统会在未来的某个确切时刻调用一个精确闹钟。

大多数应用都可以使用不精确闹钟来安排任务和事件, 完成几个常见用例的学习。如果您的应用的核心组件 功能依赖于精确计时的闹钟,如闹钟应用 或日历应用程序),那么也可以改为使用精确的闹钟。

可能不需要精确闹钟的用例

以下列表显示了可能不需要精确闹钟的常见工作流:

在应用的生命周期内安排定时操作
Handler 类包含几个良好的 用于处理计时操作的方法, n 秒,当您的应用处于活动状态时: postAtTime()postDelayed()。 请注意,这些 API 依赖于系统正常运行时间 而不是实时
安排好的后台工作,例如更新应用和上传日志
WorkManager 提供了一种安排对时间敏感的定期周期 工作。 您可以提供重复间隔和flexInterval(至少 15 分钟)来 定义工作的精细运行时。
应在特定时间之后执行的用户指定操作(即使系统处于空闲状态)
使用不精确闹钟。具体来说,调用 setAndAllowWhileIdle()
应在特定时间过后执行的用户指定操作
使用不精确闹钟。具体来说,调用 set()
可在指定时间范围内执行的用户指定操作
使用不精确闹钟。具体来说,调用 setWindow()。 请注意,如果您的应用以 Android 12 或更高版本为目标平台,则最小的 允许的时间范围为 10 分钟。

设置精确闹钟的方法

您的应用可以使用以下方法之一设置精确的闹钟。这些方法 对广告有序,让靠近列表底部的广告能够投放 但需要更多系统资源。

setExact()

在指定时间(近乎精确的未来时间)触发闹钟,且闹钟时间不得超过 省电措施不起作用。

除非应用的工作 对用户而言具有高时效性

setExactAndAllowWhileIdle()

在接近精确的未来时间触发闹钟(即使节省电池电量) 措施。

setAlarmClock()

调出未来某个确切时间的闹钟。因为这些闹钟是 因此系统绝不会调整其送货时间。通过 系统会将这些警报识别为最关键的警报,因此会导致低功耗的警报 模式来发出警报。

系统资源消耗

当系统触发您的应用设置的精确闹钟时,设备 会消耗大量的资源,例如电池寿命,尤其是在 节能模式此外,系统无法轻松地批处理这些请求, 从而更高效地利用资源

强烈建议您在遇到以下情况时创建一个不精确闹钟: 如需执行时间更长的工作,请使用以下方式安排时间 WorkManagerJobScheduler(从闹钟所在地点) BroadcastReceiver。在以下时间内完成工作: 设备处于低电耗模式,请使用 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 权限 以 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 广播。您的应用应实现广播 接收器,用于执行 以下:

  1. 确认您的应用仍具有特殊应用访问权限。为此,请调用 canScheduleExactAlarms()。 此检查可保护您的应用,防止用户向您的应用授予 权限,然后几乎会立即撤消该权限。
  2. 根据应用的当前状态,重新安排应用需要的任何精确闹钟。 此逻辑应与您的应用接收 ACTION_BOOT_COMPLETED 广播时所执行的操作类似。

要求用户授予 SCHEDULE_EXACT_ALARM 权限

该选项称为“允许设置闹钟和提醒”
图 1. 系统设置中的“闹钟和提醒”特殊应用访问权限页面,用户可以在该页面中允许您的应用设置精确的闹钟。

如有必要,您可以将用户引导至闹钟和系统中的提醒屏幕 如图 1 所示。为此,请完成以下步骤:

  1. 在应用的界面中,向用户解释为什么您的应用需要调度精确的闹钟。
  2. 调用包含 ACTION_REQUEST_SCHEDULE_EXACT_ALARM intent 操作的 intent。

设置重复闹钟

重复闹钟允许系统定期通知您的应用 时间表。

设计不合理的闹钟可能会导致耗电过快,并严重增加 服务器因此,在 Android 4.4(API 级别 19)及更高版本中, 重复闹钟是不精确闹钟

重复闹钟具有以下特征:

  • 闹钟类型。要了解详情,请参阅选择闹钟类型

  • 触发时间。如果您指定的触发时间是过去的时间,则闹钟会启动。 立即触发

  • 闹钟的间隔。例如,每天一次、每小时一次或每 5 分钟一次。

  • 闹钟触发的待定 Intent。当您设置 使用同一待定 intent 的第二个闹钟,它会替换原始闹钟。

如需取消 PendingIntent(),请将 FLAG_NO_CREATE 发送至PendingIntent.getService() 来获取 intent 的实例(如果存在),然后将该 intent 传递给 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: 根据自设备被触发的时长触发待定 intent 但并未唤醒设备。通过 经过时间包括设备处于休眠状态期间的任何时间。

  • ELAPSED_REALTIME_WAKEUP: 唤醒设备并在指定时长后触发待定 intent 自设备启动以来经过了一段时间。

  • RTC: 在指定时间触发待定 intent,但不会唤醒设备。

  • RTC_WAKEUP:唤醒次数 启动设备以在指定的时间触发待定 intent。

“经过的时间”闹钟示例

以下是使用 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() 在 Alarm Manager 时,传入 您不再需要的 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. 使用符合以下条件的 intent 过滤器将接收器添加到应用的清单文件中: 过滤条件 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(如果 。与众不同的闹钟 类型。