警报(基于AlarmManager类)为您提供了一种在应用程序生命周期之外执行基于时间的操作的方法。例如,您可以使用闹钟启动长时间运行的操作,例如每天启动一次服务以下载天气预报。

警报具有以下特征:

  • 他们让你在设定的时间和/或时间间隔触发意图。
  • 您可以将它们与广播接收器一起使用以启动服务并执行其他操作。
  • 它们在应用程序之外运行,所以即使应用程序未运行,即使设备本身处于睡眠状态,也可以使用它们触发事件或操作。
  • 它们可以帮助您最大限度地减少应用程序的资源需求 您可以安排操作而不依赖定时器或连续运行后台服务。

注意:对于在应用程序生命周期中保证发生的定时操作,请考虑将此类 Handler与Timerand 结合使用 Thread。这种方法可以让Android更好地控制系统资源

了解权衡


重复报警是一种相对简单的机制,灵活性有限。它可能不是您的应用程序的最佳选择,特别是如果您需要触发网络操作。设计不当的警报可能会导致电池耗尽并给服务器带来重大负担。

触发应用程序生命周期之外的操作的常见方案是将数据与服务器同步。这种情况下,您可能会试图使用重复闹铃。但是,如果您拥有托管应用数据的服务器,则将Google云消息传递(GCM)与同步适配器一起使用 是一种更好的解决方案AlarmManager。同步适配器为您AlarmManager提供与之相同的计划选项,但它为您提供了更大的灵活性。例如,同步可能基于来自服务器/设备的“新数据”消息(请参阅 运行同步适配器有关详细信息),用户的活动(或不活动),一天中的时间等。有关何时以及如何使用GCM和同步适配器的详细信息,请参阅本页顶部的链接视频。

设备处于打盹模式下时,警报不会触发 。任何计划的警报将被推迟到设备退出打盹。如果您需要确保您的工作即使在设备空闲时也能完成,有几个选项可用。您可以使用 setAndAllowWhileIdle()或 setExactAndAllowWhileIdle()保证报警将执行。另一种选择是使用新的WorkManager API,该API可以一次或定期执行后台工作。有关更多信息,请参阅 使用WorkManager安排任务。

最佳实践


您在设计重复警报时所做的每一项选择都会对您的应用程序使用(或滥用)系统资源的方式产生影响。例如,想象一个与服务器同步的流行应用程序。如果同步操作基于时钟时间,并且每个应用程序实例在晚上11:00同步,则服务器上的负载可能导致高延迟甚至“拒绝服务”。遵循以下使用警报的最佳做法:

将随机性(抖动)添加到由于重复警报而触发的任何网络请求中:
当警报触发时做任何本地工作。“本地工作”是指任何不会触及服务器或从服务器请求数据的东西。
同时,安排包含网络请求的警报在某个随机时间段内触发。
保持警报频率最低。
请勿不必要地唤醒设备(此行为由报警类型决定,如选择报警类型中所述)。
不要让闹钟的触发时间更加准确。
使用,setInexactRepeating()而不是setRepeating()。当您使用时setInexactRepeating(),Android会同步来自多个应用程序的重复警报并同时触发它们。这样可以减少系统必须唤醒设备的总次数,从而减少电池消耗。从Android 4.4(API Level 19)开始,所有重复警报都是不精确的。请注意,尽管 setInexactRepeating()有所改进setRepeating(),但如果应用程序的每个实例都在同一时间点击服务器,它仍然可能会淹没服务器。因此,对于网络请求,添加一些随机性到您的警报,如上所述。

尽可能避免在闹钟时间内使用闹钟。
重复基于精确触发时间的警报不能很好地扩展。ELAPSED_REALTIME如果可以,请使用。下一节将详细介绍不同的报警类型。

设置重复闹钟


如上所述,重复报警对于安排常规事件或数据查找是一个很好的选择。重复报警具有以下特征:

  • 警报类型。有关更多讨论,请参阅选择报警类型。
  • 触发时间。如果您指定的触发时间过去,则会立即触发警报。
  • 警报的间隔。例如,每天一次,每小时一次,每五分钟一次等等。
  • 触发警报时触发的未决意图。当您设置使用相同待定意图的第二个警报时,它会替换原始警报。

选择一种报警类型

使用重复报警的首要考虑因素之一是其类型应该是什么。

有两种通用的闹钟类型:“实时实时”和“实时时钟”(RTC)。经过实时使用“系统启动后的时间”作为参考,实时时钟使用UTC(挂钟)时间。这意味着已过去的实时时间适合于根据时间的推移设置闹钟(例如,每30秒触发一次闹钟),因为它不受时区/区域设置的影响。实时时钟类型更适合依赖当前语言环境的警报。

这两种类型都有一个“唤醒”版本,即在屏幕关闭时唤醒设备的CPU。这确保了警报将在预定的时间触发。如果您的应用程序具有时间依赖性,例如,如果它具有执行特定操作的有限窗口,这非常有用。如果您不使用闹铃类型的唤醒版本,则当您的设备下次醒来时,所有重复闹铃都会触发。

如果您只需要在特定时间间隔(例如,每半小时)触发一次闹钟,请使用其中一种实时类型。一般来说,这是更好的选择。

如果您需要在一天中的特定时间触发闹钟,请选择其中一种基于时钟的实时时钟类型。但请注意,此方法可能有一些缺点 - 应用程序可能无法很好地转换到其他语言环境,并且如果用户更改设备的时间设置,则可能会导致应用程序出现意外行为。如上所述,使用实时时钟报警类型也不能很好地扩展。如果可以,我们建议您使用“经过实时”警报。

这里是类型列表:

  • ELAPSED_REALTIME - 根据自设备启动以来的时间量引发待定意图,但不唤醒设备。经过的时间包括设备睡着的任何时间。
  • ELAPSED_REALTIME_WAKEUP - 设备启动后经过指定的时间长度后,唤醒设备并触发挂起的意图。
  • RTC - 在指定的时间为待定意图启动,但不会唤醒设备。
  • RTC_WAKEUP - 唤醒设备在指定的时间触发挂起的意图。

已用实时警报的示例

这里有一些使用的例子ELAPSED_REALTIME_WAKEUP。

在30分钟内将设备唤醒,然后每30分钟发出一次:

// 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);

唤醒设备以在一分钟内触发一次性(非重复)警报:

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唤醒设备以发出警报,并且每天重复一次:

// 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分钟启动闹钟:

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会同步多个不精确的重复警报并同时触发它们。这可以减少电池的消耗。

对于具有严格时间要求的罕见应用程序,例如,警报需要在上午8:30准确着火,然后每小时使用一次setRepeating()。但是如果可能的话,你应该避免使用精确的警报

有了setInexactRepeating(),你不能像你一样指定一个自定义时间间隔 setRepeating()。你必须使用间隔常量之一,比如INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY等。请参阅AlarmManager 完整列表。

取消闹钟


根据您的应用程序,您可能希望包含取消闹钟的功能。要取消闹钟,请cancel()打开闹钟管理器,传递PendingIntent您不再想要的闹钟。例如:

// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
    alarmMgr.cancel(alarmIntent);
}

设备重新启动时发出警报


默认情况下,当设备关闭时,所有报警都会被取消。为防止这种情况发生,您可以设计应用程序,以便在用户重新启动设备时自动重新启动重复警报。这确保了AlarmManager将继续执行其任务,而用户不需要手动重启警报。

这里是步骤:
在应用程序的清单中设置权限。这允许您的应用程序在系统完成引导后接收 广播的广播(这只有在应用程序已经由用户至少启动一次时才可用): RECEIVE_BOOT_COMPLETEDACTION_BOOT_COMPLETED

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

实现一个BroadcastReceiver接收广播:

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"。这意味着除非应用程序明确启用接收器,否则不会调用接收器。这可以防止引导接收器被不必要地调用。您可以启用接收器(例如,如果用户设置了警报),如下所示:

ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);

一旦以这种方式启用接收器,即使用户重新启动设备,它也将保持启用状态。换句话说,即使在重新启动时,以编程方式启用接收器也会覆盖清单设置。接收器将保持启用状态,直到您的应用停用。您可以禁用接收器(例如,如果用户取消报警),如下所示:

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)中引入了Doze和App Standby,以延长设备电池寿命。当设备处于打盹模式时,任何标准报警将被推迟,直到设备退出打盹模式或打开维护窗口。如果即使在打盹模式下也必须有警报火警,您可以使用 或。闲置时,您的应用将进入App Standby模式,这意味着用户在一段时间内没有使用它,并且该应用没有前台进程。当应用程序处于应用程序待机状态时,警报延迟就像打盹模式一样。当应用程序不再闲置或设备插入电源时,此限制即被解除。有关您的应用如何受到这些模式影响的更多信息,请阅读 Optimize for Doze和App Standby。 setAndAllowWhileIdle() setExactAndAllowWhileIdle()

示例应用


要尝试本指南中的概念,请下载示例应用程序

Lastest Update:2018.05.24