首页 > 代码库 > android--Scheduling Repeating Alarms学习

android--Scheduling Repeating Alarms学习

原文地址:http://developer.android.com/training/scheduling/alarms.html

Alarms (based on the AlarmManager class) give you a way to perform time-based operations outside the lifetime of your application. For example, you could use an alarm to initiate a long-running operation, such as starting a service once a day to download a weather forecast.。

在你的应用生命期之外,Alarms(基于 AlarmManager类)给你提供了一个方法去执行一些基本操作。例如:你可以使用一个alarm 发起一个长时间运行的操作,比如每天一次开启服务下载天气预报。

Alarms have these characteristics:(Alarms 有以下特征:)

  • They let you fire Intents at set times and/or intervals.允许你定时或者间隔一段时间发起Intent
  • You can use them in conjunction with broadcast receivers to start services and perform other operations.能和广播接收器结合起来使用,开启服务或者执行其他操作。
  • They operate outside of your application, so you can use them to trigger events or actions even when your app is not running, and even if the device itself is asleep.这个操作在你应用程序之外,所以当你的app没有运行时你能使用他们触发一些事件或者操作,甚至是设备休眠时。
  • They help you to minimize your app‘s resource requirements. You can schedule operations without relying on timers or continuously running background services.帮你尽量减少app的资源要求。你可以有计划的操作,而不需要依赖定时器或者持续运行的后台服务。

Note: For timing operations that are guaranteed to occur during the lifetime of your application, instead consider using the Handler class in conjunction with Timer and Thread. This approach gives Android better control over system resources在你的应用程序运行期间也能保证定时操作发生,而不需考虑使用Handler和计时器线程一起使用。这种方法使Android更好地控制系统资源。

Understand the Trade-offs


A repeating alarm is a relatively simple mechanism with limited flexibility. It may not be the best choice for your app, particularly if you need to trigger network operations. A poorly designed alarm can cause battery drain and put a significant load on servers.重复alarm是一个灵活有限且相对简单的机制。如果你需要触发网络操作,它对你的app而言可能不是最好的选择。设计不良的alarm可以导致电池电量的消耗和加大服务器上的负载

A common scenario for triggering an operation outside the lifetime of your app is syncing data with a server. This is a case where you might be tempted to use a repeating alarm. But if you own the server that is hosting your app‘s data, using Google Cloud Messaging (GCM) in conjunction with sync adapter is a better solution than AlarmManager. A sync adapter gives you all the same scheduling options as AlarmManager, but it offers you significantly more flexibility. For example, a sync could be based on a "new data" message from the server/device (see Running a Sync Adapter for details), the user‘s activity (or inactivity), the time of day, and so on. See the linked videos at the top of this page for a detailed discussion of when and how to use GCM and sync adapter.

Best practices

Every choice you make in designing your repeating alarm can have consequences in how your app uses (or abuses) system resources. For example, imagine a popular app that syncs with a server. If the sync operation is based on clock time and every instance of the app syncs at 11:00 p.m., the load on the server could result in high latency or even "denial of service." Follow these best practices in using alarms:在设计重复闹钟时你做的每个选择,都会有一个结果:在你的应用程序使用(或滥用)系统资源。例如:设想大量app使用一个服务器同步。如果这个同步操作是基于时钟时间并且每个app都在下午11点同步,这会导致服务高延时甚者是拒载服务。在使用alams时遵循以下最佳实践:

  • Add randomness (jitter) to any network requests that trigger as a result of a repeating alarm:重复闹钟添加随机性,结果是随机触发网络请求
    • Do any local work when the alarm triggers. "Local work" means anything that doesn‘t hit a server or require the data from the server.alarm触发时仅在本地起作用(Do any local work ),“Local work”指不需要握手服务器或者请求服务器数据。
    • At the same time, schedule the alarm that contains the network requests to fire at some random period of time.与此同时, 在随机的一段时间安排包含发起网络请求的alarm。
  • Keep your alarm frequency to a minimum.保持alarm频率最低。
  • Don‘t wake up the device unnecessarily (this behavior is determined by the alarm type, as described in Choose an alarm type).不需要唤醒设备(这个行为依alarm的类型决定, Choose an alarm type有描述)
  • Don‘t make your alarm‘s trigger time any more precise than it has to be.不要让你的闹钟触发时间很精确。

    Use setInexactRepeating() instead of setRepeating(). When you use setInexactRepeating(), Android synchronizes repeating alarms from multiple apps and fires them at the same time. This reduces the total number of times the system must wake the device, thus reducing drain on the battery. As of Android 4.4 (API Level 19), all repeating alarms are inexact. Note that while setInexactRepeating() is an improvement over setRepeating(), it can still overwhelm a server if every instance of an app hits the server around the same time. Therefore, for network requests, add some randomness to your alarms, as discussed above.使用 setInexactRepeating() 取代setRepeating()方法当你使用setInexactRepeating(),从Android的多个应用程序同步重复alarm,并在同一时间触发他们。这能减少系统强制唤醒设备的总次数,因此能降低电量的消耗。在android4.4,所有重复的alarm都是不精确的。注意:尽管setInexactRepeating()是一个改进setRepeating(),如果一个应用程序的每个实例都在同一时间握手服务器,它仍然可以压倒一个服务器。因此,正如上面所讨论的,对于网络的请求,让alarm增加一些随机性。

  • Avoid basing your alarm on clock time if possible. 如果可能的话避免基于时钟时间的闹钟(alarm) 。 

    Repeating alarms that are based on a precise trigger time don‘t scale well. Use ELAPSED_REALTIME if you can. The different alarm types are described in more detail in the following section。基于精确触发时间的重复alarms没有很好地扩展。你可以使用ELAPSED_REALTIME。在下面的部分中对不同的告警类型进行了更详细的描述。


Set a Repeating Alarm


As described above, repeating alarms are a good choice for scheduling regular events or data lookups. A repeating alarm has the following characteristics:如上所叙,对于定期调度的事件或数据查询重复闹钟是一个非常好的选择。重复闹钟有以下特征:

  • A alarm type. For more discussion, see Choose an alarm type.一个闹钟类型,更多讨论看 Choose an alarm type.
  • A trigger time. If the trigger time you specify is in the past, the alarm triggers immediately.一个触发时间,如果你指定的触发时间是在过去,这个闹钟立即触发。
  • The alarm‘s interval. For example, once a day, every hour, every 5 seconds, and so on.闹钟间隔。例如:一天一次,一小时一次,五分钟一次等等
  • A pending intent that fires when the alarm is triggered. When you set a second alarm that uses the same pending intent, it replaces the original alarm.当闹钟被触发时将启动待处理的意图。当您使用相同的意图设置第二次闹钟时,它取代了原来的闹钟

Choose an alarm type

One of the first considerations in using a repeating alarm is what its type should be.在使用重复闹钟时你首先考虑的问题是你应该用什么类型

There are two general clock types for alarms: "elapsed real time" and "real time clock" (RTC). Elapsed real time uses the "time since system boot" as a reference, and real time clock uses UTC (wall clock) time. This means that elapsed real time is suited to setting an alarm based on the passage of time (for example, an alarm that fires every 30 seconds) since it isn‘t affected by time zone/locale. The real time clock type is better suited for alarms that are dependent on current locale.有两个通用的闹钟类型: "elapsed real time" 和"real time clock" (RTC).Elapsed real time使用“自系统启动时间”作为一个参考,和real time clock使用UTC(挂钟)时间。这意味着,elapsed real time 适合于设置基于时间流逝的闹钟(例如,警报将触发每30秒),因为它不会受时区/地区。real time clock 类型更适合依赖于当前语言环境的报警。

Both types have a "wakeup" version, which says to wake up the device‘s CPU if the screen is off. This ensures that the alarm will fire at the scheduled time. This is useful if your app has a time dependency—for example, if it has a limited window to perform a particular operation. If you don‘t use the wakeup version of your alarm type, then all the repeating alarms will fire when your device is next awake.两种类型都有一个唤醒版本,即如果屏幕关闭唤醒设备的CPU。这能确保闹钟在计划的时间里启动。如果你的App有时间依赖性是非常有用的。例如:有一个有限的窗口来执行特定的操作。如果你没有使用闹钟类型的唤醒版本,那么只有当你的设备下次唤醒时所有的重复闹钟才触发。

If you simply need your alarm to fire at a particular interval (for example, every half hour), use one of the elapsed real time types. In general, this is the better choice.如果你只需要在特定的间隔下(例如:每小时)启动闹钟,用elapsed real time的一种就行。通常,这是一个好的选择。

If you need your alarm to fire at a particular time of day, then choose one of the clock-based real time clock types. Note, however, that this approach can have some drawbacks—the app may not translate well to other locales, and if the user changes the device‘s time setting, it could cause unexpected behavior in your app. Using a real time clock alarm type also does not scale well, as discussed above. We recommend that you use a "elapsed real time" alarm if you can.如果你需要你的闹钟在一天的特定时间启动,就选择基于时钟类型的real time clock一种,但请注意,这种方法可以有一些缺点,应用程序可能无法很好的转化为其他语言环境,并且如果用户更改设备的时间设置,可能会导致您的应用程序出现意外行为。如上所述,使用real time clock闹钟类型也不能很好地扩展。如果可能我们推荐你使用一个“"elapsed real time”闹钟。

Here is the list of types:这是类型列表:

  • ELAPSED_REALTIME—Fires the pending intent based on the amount of time since the device was booted, but doesn‘t wake up the device. The elapsed time includes any time during which the device was asleep.基于设备启动之后的时间启动意图,但不唤醒设备,The elapsed time 包括设备休眠期间的时间
  • ELAPSED_REALTIME_WAKEUP—Wakes up the device and fires the pending intent after the specified length of time has elapsed since device boot.在设备启动之后的指定时间长度唤醒设备并启动意图,
  • RTC—Fires the pending intent at the specified time but does not wake up the device.在一个具体的时间去启动意图,但是不会唤醒设备
  • RTC_WAKEUP—Wakes up the device to fire the pending intent at the specified time.在一个具体的时间唤醒设备启动意图

ELAPSED_REALTIME_WAKEUP examples

Here are some examples of using ELAPSED_REALTIME_WAKEUP.使用ELAPSED_REALTIME_WAKEUP.的例子

Wake up the device to fire the alarm in 30 minutes, and every 30 minutes after that:在30分钟内唤醒设备并启动闹钟,之后每三十分钟一次

// Hopefully your alarm will have a lower frequency than this!
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        AlarmManager.INTERVAL_HALF_HOUR,
        AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);

Wake up the device to fire a one-time (non-repeating) alarm in one minute:唤醒设备并在一分钟之后启动闹钟,仅一次

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 examples

Here are some examples of using RTC_WAKEUP.使用RTC_WAKEUP.的例子

Wake up the device to fire the alarm at approximately 2:00 p.m., and repeat once a day at the same time:在下午两点左右,唤醒设备并启动闹钟,在相同的时间每天重复一次

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

Wake up the device to fire the alarm at precisely 8:30 a.m., and every 20 minutes thereafter:在精确的上午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);

Decide how precise your alarm needs to be 决定你的闹钟需要精确到多少

As described above, choosing the alarm type is often the first step in creating an alarm. A further distinction is how precise you need your alarm to be. For most apps, setInexactRepeating() is the right choice. When you use this method, Android synchronizes multiple inexact repeating alarms and fires them at the same time. This reduces the drain on the battery.如上所述,选择闹钟类型是创建闹钟的第一步。另一个区别是你的闹钟需要精确多少。对于大多数应用程序,setInexactRepeating()是正确的选择。当您使用此方法,Android同步多个不精确的重复闹钟,并在同一时间触发他们。这不仅降低了电池电量的消耗。

For the rare app that has rigid time requirements—for example, the alarm needs to fire precisely at 8:30 a.m., and every hour on the hour thereafter—use setRepeating(). But you should avoid using exact alarms if possible.对于有刚性时间要求的罕见应用程序,例如,闹钟需要精确到上午8:30,并每隔一小时以后用setRepeating()。但是如果可能的话,你应该避免使用精确的闹钟。

With setInexactRepeating(), you can‘t specify a custom interval the way you can with setRepeating().You have to use one of the interval constants, such as INTERVAL_FIFTEEN_MINUTESINTERVAL_DAY, and so on. See AlarmManager for the complete list.使用setInexactRepeating()方法时你不能指定一个自定义的时间间隔,使用setRepeating()就可以指定。你必须使用的间隔常量中的一个,如INTERVAL_FIFTEEN_MINUTES,INTERVAL_DAY等等。见AlarmManager的完整列表。

Cancel an Alarm


Depending on your app, you may want to include the ability to cancel the alarm. To cancel an alarm, callcancel() on the Alarm Manager, passing in the PendingIntent you no longer want to fire. For example:取决于你的app,你可能想有取消闹钟的能力。取消一个闹钟,在闹钟Manager中呼叫cancel()方法,传入你不想再启动的PendingIntent对象。例如:

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

Start an Alarm When the Device Boots设备启动时启动一个闹钟


By default, all alarms are canceled when a device shuts down. To prevent this from happening, you can design your application to automatically restart a repeating alarm if the user reboots the device. This ensures that theAlarmManager will continue doing its task without the user needing to manually restart the alarm.默认情况下,当设备关闭时所有闹钟将被取消。为了阻止这个情况发生,当用户重启设备时,你可以设计你的程序自动重启一个重复闹钟。这能保证闹钟管理器继续做它的任务而不需要用户手动重启闹钟。

Here are the steps:以下是步骤:

  1. Set the RECEIVE_BOOT_COMPLETED permission in your application‘s manifest. This allows your app to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting (this only works if the app has already been launched by the user at least once)。在你的应用程序配置文件manifest中,添加RECEIVE_BOOT_COMPLETED权限。这允许你的app能接收ACTION_BOOT_COMPLETED 事件广播,该广播是系统启动完成后发送(只在你的app至少被用户启动一次后有作用)。
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Implement a BroadcastReceiver to receive the broadcast:实现一个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.
            }
        }
    }
  3. Add the receiver to your app‘s manifest file with an intent filter that filters on the ACTION_BOOT_COMPLETEDaction:添加这个接收器到你的应用配置文件maiifest里,并用intent filter过滤 ACTION_BOOT_COMPLETED事件
    <receiver android:name=".SampleBootReceiver"
            android:enabled="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"></action>
        </intent-filter>
    </receiver>

    Notice that in the manifest, the boot receiver is set to android:enabled="false". This means that the receiver will not be called unless the application explicitly enables it. This prevents the boot receiver from being called unnecessarily. You can enable a receiver (for example, if the user sets an alarm) as follows:注意在配置文件里,启动接收器需要设置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);

    Once you enable the receiver this way, it will stay enabled, even if the user reboots the device. In other words, programmatically enabling the receiver overrides the manifest setting, even across reboots. The receiver will stay enabled until your app disables it. You can disable a receiver (for example, if the user cancels an alarm) as follows:一旦启用了接收这种方式,它会保持启用,即使用户重新启动设备。换句话说,通过编程使接收器覆盖舱单,甚至在重新启动。接收器将保持启用,直到您的应用程序禁用它。可以禁用接收器(例如,如果用户取消闹钟),如下所示:

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

翻译不好的地方,请指正,我会非常感谢的并且及时修改。