这是Managing Device Awake State的下半篇,上半篇请看:Android官方文章翻译之管理设备苏醒状态(Managing Device Awake State)(一)
在了解接下来的内容之前,有必要先看一下这篇来自 FireOfStar的专栏 的文章:AndroidManifest.xml文件详解(receiver)
2.执行重复报警(Scheduling Repeating Alarms)
Alarms(报警,报警器,警报,闹钟!下面的描述中会有用到这三个中文解释之一或者英文Alarm)(基于 AlarmManager 类)为你提供了一种在你的程序的生命周期之外执行基于时间的操作的方式,例如,你可以使用一个Alarm来启动一个长时间运行的操作,比如每天启动一个服务去下载天气预报信息。
Alarms具备这些特性:
1.使得你可以设定的时间或者一段时间间隔激活/触发一个Intent意图。
2.你可以使用它们联合一个广播接收器去启动服务并执行其他一些列的操作。
3.它们不在你的应用程序生命周期范畴内,因此你可以使用它们触发事件或者行为甚至当你的程序还未运行时,更甚是你的设备处于休眠状态中时。
4.它们帮助你最大限度减少你的程序的资源请求,你可以不依赖时间或者一直在后台运行服务执行操作。
注意:为了确保基于时间的操作在程序的生命周期期间得到执行,可以考虑使用 Handler 类结合 Timer 类和 Thread 类替代。这种方法可以让安卓系统更好的控制系统资源。
明白权衡(Understand the Trade-offs)
一个重复的报警是一个相对简单具有有限的灵活性的机制,它可能不是你的程序的最佳选择,特别是当你需要的是激发网络请求时。一个糟糕/不良的报警设计会引起电池的消耗,放入一个沉重的加载任务到服务中。
一个在你的程序生命周期之外激发一个请求的通用场景是使用一个服务来同步数据,这是你可能尝试使用一个重复报警的一种情况。但是如果你的这个服务持有着你的程序数据,使用 Google Cloud Messaging (GCM) 结合 sync adapter(翻译成同步适配器!?) 会是比使用 AlarmManager 的一个更好的解决方案。一个同步适配器会为你提供与 AlarmManager 一样的执行选项,但是它会显著的更加灵活。例如,一个同步可以基于来自服务或设备的"新数据"消息(查看 Running a Sync Adapter 了解更多详情),用户的活动(或者空闲),一天内的时间等等。请查看页面顶部的链接视频来查看关于如何使用GCM和同步适配器的详细的讨论。
最佳实践(Best practices)
你在设计你的重复报警时做出的每一个选择都会对你的程序如何使用(或者说滥用)系统资源产生影响。例如,假设一个流行的程序同步服务器数据,如果一个同步操作是基于闹钟时间,所有的程序实例都在上午11点时同步,在服务器上的负载可能会导致高延迟甚至是“拒绝服务”。下面是使用Alarms的最佳实践:
1.在一个以任何的网络请求作为激发结果的重复报警中加入随机性(抖动的):
1.1 当报警触发时做一些本地的工作,“本地工作”意思是不与服务器连接或者从服务器请求数据的任何事情。
1.2 在同一时间,在同一段时间内的随机时间激活包含网络请求的报警。
2.保持你的报警的频率最小化。
3.非必要时不要唤醒你的设备(唤醒或不唤醒设备的行为由Alarm的类型决定,在下面的内容( Choose an alarm type)中会讨论到)。
4.不要让你的报警器的激发时间比它本应设定的时间更加精确。(即设定尽量模糊的时间,下面会有说明。)
使用 setInexactRepeating() 方法来替代 setRepeating() 方法。当你使用 setInexactRepeating() 时,Android会在同一个时间同步的执行来自多个程序的重复报警任务。这样可以减少系统必须唤醒设备的总次数,由此减少对电池的消耗。从Android4.4(API版本为19)开始,所有的重复报警器都是inexact的(非精确模式)。注意,虽然 setInexactRepeating() 是对 setRepeating() 的一种改进,如果所有的程序实例都在几乎同一时间激活服务,这仍然会压倒服务。因此,对于网络请求,应该添加随机性到你的报警器中,就像上面讨论过的。
5.尽可能的避免你的报警器基于系统闹钟时间。
一个基于精确的激活时间的重复报警器没有很好的伸缩性,如果可以,使用 ELAPSED_REALTIME。不同的报警器类型在下面的内容中将由更加详细的描述。
设置一个重复报警器(Set a Repeating Alarm)
正如上面所描述的,重复报警器是用来执行具有规律的事件或者数据取回操作的良好选择。一个重复报警器具备如下特性:
1.一个报警器类型,更多详细信息,请看下面的“选择一个报警器类型”(Choose an alarm type)
2.一个触发时间。如果你所指定的时间是过去的,那么报警器将被立即触发。
3.报警器的间隔时间。例如,一天一次,每个小时一次,每五分钟一次等等。
4.一个当报警器触发时的待处理意图。当你用这个相同的待处理意图设置了第二个报警器,它将替换掉原始的报警器。
选择一个报警器类型(Choose an alarm type)
在使用一个重复报警器时第一个要考虑的就是它应该配置哪种类型。
报警器有两种通用的闹钟类型:“elapsed real time(过去的真实时间)”(官方没有给出简写,为了方便,我这里描述成ERT)和“real time clock(基于闹钟的真实时间)”(RTC),ERT引用“从系统启动之后经过的时间”,RTC引用UTC时间(即挂钟),这意味着ERT适合用在设置基于时间的推移的报警器(例如,一个每隔30分钟激活一次的报警器)因为它不会受时区/区域影响。RTC更适合依赖于当前区域的报警器。
这两个类型都有一个“wakeup(唤醒)”版本,表示当屏幕处于关闭状态时会唤醒设备的CPU。这能保证报警器在指定的执行时间被激活。如果你的程序有时间依赖性,这是非常有用的,例如,如果它有一个有限的窗口中执行特定的操作。如果你不使用唤醒版本的报警器类型,那么当你的设备苏醒时,所有的重复报警器将被激活。
如果你只是简单的需要你的报警器在一个特定的时间间隔时激活(例如,每半个小时),那么请使用ERT类型中的一种。通常,这是最好的选择。
如果你需要想要在一天的特定时间激活你的报警器,那么选择基于时钟的RTC类型中的一种。注意,然而,这种方法会有一些缺点--程序可能无法很好的完成与其他区域的时间转换,并且如果用户修改了设备的时间设置,这将会使你的程序引起非预期的行为。使用RTC报警器类型同样不具备很好的伸缩性,正如上面描述的。我们建议如果可能尽量使用ERT类型的报警器。
下面是类型列表:
1.ELAPSED_REALTIME -- 基于从设备启动开始的一定时间激活待处理意图,但是不会唤醒设备。这种类型的时间会把设备处于休眠状态期间经过的时间一并计算在内。
2.ELAPSED_REALTIME_WAKEUP -- 与 的区别就是它会唤醒设备。
3.RTC -- 在特定的时间激活待处理意图,但是不唤醒设备。
4.RTC_WAKEUP -- 与 RTC 的区别就是它会唤醒设备。
ELAPSED_REALTIME_WAKEUP举例
这里有一些使用 ELAPSED_REALTIME_WAKEUP 的示例。
1.30分钟之后唤醒设备激活报警器,在这之后每隔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);
2.1分钟之后唤醒设备激活一次报警器(不重复):
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举例
这里有一些使用 RTC_WAKEUP 的示例。
1.在大约下午2点时唤醒设备激活报警器,此后每天在相同的时间点重复一次。
// 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);
2.在精确的时间点,早上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)
正如上面所描述的,在创建一个报警器的第一步,经常是选择一个报警器类型,进一步区分下来就是决定你的报警器应该有多精确。对于大多数程序,setInexactRepeating() 是正确的选择。当你使用这个方法,Android会同步多个非精确模式下的重复报警器然后在同一时间激发它们。这将减少对电池的消耗。
部分少见的程序对时间有严格的要求--例如,报警器需要在精确的时间,早上8点30分激发,以及此后的每一个小时激活--使用 setRepeating(),但是如果可能应该尽量避免使用这种精确的时间模式。
使用 setInexactRepeating(),你无法像使用 setRepeating() 那样指定一个自定义的间隔时间,你必须使用这些时间间隔变量中的一个,如 INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY 等等。请到 AlarmManager 中查看完整的清单。
取消报警器(Cancel an Alarm)
依据你的程序,你或许想要具备取消报警器的功能,为了取消一个报警器,在AlarmManager中调用 cancel() 方法。传入你不再想要激发的 PendingIntent 对象。例如:
// If the alarm has been set, cancel it. if (alarmMgr!= null) { alarmMgr.cancel(alarmIntent); }
当设备启动时启动报警器(Start an Alarm When the Device Boots)
默认情况下,所有的报警器在设备被关闭时都被取消。为了阻止这种情况发生,你可以设计使得你的程序可以在用户重启设备时自动的重启你的重复报警器。这确保了 AlarmManager 能够继续执行它的工作而无需用户手动的重启它们。
下面是步骤:
1.在你的程序的manifest清单中设置 RECEIVE_BOOT_COMPLETED 权限。这将允许你的程序接收 ACTION_BOOT_COMPLETED 广播,当设备完成启动时,会发出该广播。(前提是在本次开机之前,之前至少有一次用户启动过本程序)。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2.实现一个 类接收广播。
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.向你的manifest清单中注册该广播,并使用 ACTION_BOOT_COMPLETED 类型的过滤类型来添加一个过滤器。
<receiver android:name=".SampleBootReceiver" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
注意这个manifest文件,这个监听设备启动的接收器被设置成了 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);
一旦你通过这种方法启用接收器,它将一直保持启动状态,甚至是用户了重启设备时。换句话说,以编程的方式启用接收器会覆盖在manifest中的设置,即便是重启后。这个接收器将一直保持着开启状态直到你的程序禁用它,你可以按照如果的放啊来禁用一个广播接收器(例如,如果用户取消了报警器):
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
完成...