AlarmManager
类)提供了一种执行
基于时间的操作。
例如,您可以使用闹钟来启动长时间运行的操作,如
就是每天启动一次服务以下载天气预报。
闹钟具有以下特征:
它们可让您按设定的时间和/或间隔触发 intent。
您可以将它们与广播接收器结合使用 jobs 或 使用 WorkRequest 执行其他任务, 操作。
它们在应用外部运行,因此您可以使用它们来 事件或操作,即使您的应用未运行, 本身处于休眠状态。
它们可帮助您最大限度地降低应用的资源需求。您可以安排 而无需依赖计时器或持续运行的服务。
设置不精确闹钟
当应用设置不精确闹钟时,系统会在某个时间点发出闹钟 。不精确闹钟可保证 同时遵循省电限制 低电耗模式。
开发者可以利用以下 API 保证来自定义 闹钟发送时间不精确。
在特定时间后发出闹钟
如果您的应用调用 set()
,
setInexactRepeating()
,
或setAndAllowWhileIdle()
,
闹钟永远不会在提供的触发时间之前触发。
在 Android 12(API 级别 31)及更高版本中,系统会在 1 秒内调用闹钟。 小时(除非有任何省电限制) 有效的内容(例如省电模式或 低电耗模式。
在某个时间范围内发出警报
如果您的应用调用 setWindow()
,则闹钟将不会在提供的
触发时间。除非任何省电限制生效,否则闹钟会始终处于
在指定时间范围内(从指定触发器开始)内投放
。
如果您的应用以 Android 12 或更高版本为目标平台,系统可能会延迟
对有时间范围的不精确闹钟调用至少 10 分钟。对于
因此,600000
下的 windowLengthMillis
参数值会被裁剪为
600000
。
大致定期提供重复闹钟
如果您的应用调用 setInexactRepeating()
,
系统会调用多个 alarm:
- 第一个闹钟会在指定的时间窗口内响铃,从 指定触发时间。
- 后续闹钟通常会在指定时间窗口后响铃 。连续两次调用闹钟的间隔时间可能会有所不同。
设置精确闹钟
系统会在未来的某个确切时刻调用一个精确闹钟。
大多数应用都可以使用不精确闹钟来安排任务和事件, 完成几个常见用例的学习。如果您的应用的核心组件 功能依赖于精确计时的闹钟,如闹钟应用 或日历应用程序),那么也可以改为使用精确的闹钟。
可能不需要精确闹钟的用例
以下列表显示了可能不需要精确闹钟的常见工作流:
- 在应用的生命周期内安排定时操作
Handler
类包含几个良好的 用于处理计时操作的方法, n 秒,当您的应用处于活动状态时:postAtTime()
和postDelayed()
。 请注意,这些 API 依赖于系统正常运行时间 而不是实时。- 安排好的后台工作,例如更新应用和上传日志
WorkManager
提供了一种安排对时间敏感的定期周期 工作。 您可以提供重复间隔和flexInterval
(至少 15 分钟)来 定义工作的精细运行时。- 应在特定时间之后执行的用户指定操作(即使系统处于空闲状态)
- 使用不精确闹钟。具体来说,调用
setAndAllowWhileIdle()
。 - 应在特定时间过后执行的用户指定操作
- 使用不精确闹钟。具体来说,调用
set()
。 - 可在指定时间范围内执行的用户指定操作
- 使用不精确闹钟。具体来说,调用
setWindow()
。 请注意,如果您的应用以 Android 12 或更高版本为目标平台,则最小的 允许的时间范围为 10 分钟。
设置精确闹钟的方法
您的应用可以使用以下方法之一设置精确的闹钟。这些方法 对广告有序,让靠近列表底部的广告能够投放 但需要更多系统资源。
setExact()
在指定时间(近乎精确的未来时间)触发闹钟,且闹钟时间不得超过 省电措施不起作用。
除非应用的工作 对用户而言具有高时效性
setExactAndAllowWhileIdle()
在接近精确的未来时间触发闹钟(即使节省电池电量) 措施。
setAlarmClock()
调出未来某个确切时间的闹钟。因为这些闹钟是 因此系统绝不会调整其送货时间。通过 系统会将这些警报识别为最关键的警报,因此会导致低功耗的警报 模式来发出警报。
系统资源消耗
当系统触发您的应用设置的精确闹钟时,设备 会消耗大量的资源,例如电池寿命,尤其是在 节能模式此外,系统无法轻松地批处理这些请求, 从而更高效地利用资源
强烈建议您在遇到以下情况时创建一个不精确闹钟:
如需执行时间更长的工作,请使用以下方式安排时间
WorkManager
或
JobScheduler
(从闹钟所在地点)
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_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 所示。为此,请完成以下步骤:
- 在应用的界面中,向用户解释为什么您的应用需要调度精确的闹钟。
- 调用包含
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_MINUTES
、
INTERVAL_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
继续执行其任务,而无需用户手动重新启动闹钟。
具体步骤如下:
设置
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. } } }
使用符合以下条件的 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)的设备支持 低电耗模式 模式, 有助于延长设备的电池续航时间。设备处于开机状态时,闹钟不会触发 低电耗模式 所有已设定的闹钟都会延迟,直到设备退出低电耗模式。如果您需要 即使设备处于空闲状态,也依然能完成工作。 可用:
设置精确闹钟。
使用 WorkManager API,它可用于执行 后台工作。您可以指示系统应加快您的工作速度 工作尽快完成如需了解详情,请参阅 使用 WorkManager 调度任务
最佳做法
您在设计重复闹钟时所做的每一个选择都会产生后果 应用如何使用(或滥用)系统资源。例如,假设 与服务器同步的热门应用程序。如果同步操作基于时钟 应用的每个实例都会在晚上 11:00 同步 可能导致延迟时间较长,甚至 “拒绝服务攻击”请遵循以下使用闹钟的最佳做法:
向任何需要增加随机性(抖动)的网络请求 会被重复闹钟触发:
在闹钟触发时执行本地的任何操作。“本地工作”是指任何 无需访问服务器或需要来自服务器的数据。
同时,安排包含网络请求的闹钟, 在某个随机的时间段触发
尽可能降低闹钟的触发频率。
请勿在不必要的情况下唤醒设备(此行为由 闹钟类型(如选择闹钟类型中所述)。
请勿将闹钟的触发时间设置得过于精确。
使用
setInexactRepeating()
而非setRepeating()
。 当您使用setInexactRepeating()
时, Android 会同步多个应用的重复闹钟并触发 这些文件。这样可减少系统必须唤醒 从而减少耗电量。从 Android 4.4 开始 (API 级别 19),所有重复闹钟都是不精确闹钟。注意事项 同时setInexactRepeating()
相较于setRepeating()
, 即使应用的每个实例都访问服务器,仍可能会使服务器不堪重负 大约同一时间。因此,对于网络请求, 设置好闹钟尽量避免基于时钟时间设置闹钟。
基于精确触发时间的重复闹钟无法很好地扩展。使用
ELAPSED_REALTIME
(如果 。与众不同的闹钟 类型。