十七、探索警报管理器

在 Android 中,intent 对象用于启动 UI 活动、后台服务或广播接收器。通常这些意图是由用户动作触发的。在 Android 中,你也可以使用闹钟来触发广播意图,请注意,只是广播意图。然后,被调用的广播接收器可以选择开始一项活动或一项服务。

在本章中,您将了解警报管理器 API。警报管理器 API 用于安排广播意图在特定时间启动。我们将这种在特定时间安排广播意图的过程称为设置警报。

我们还将向您展示如何安排定期重复的闹铃。我们将向您展示如何取消已经设置的警报。

当意图对象被存储以供以后使用时,它被称为待定意图。由于警报管理器一直在使用未决意图,你也将在本章中看到未决意图的用法和复杂性。

设置简单的警报

我们将从在特定时间设置闹钟并让它呼叫广播接收器开始这一章。一旦广播接收器被调用,你可以使用来自第 16 章的信息在广播接收器中执行简单的和长时间运行的操作。

访问警报管理器很简单,如清单 17-1 所示。

清单 17-1 。访问警报管理器

//In filename: SendAlarmOnceTester.java
AlarmManager am =
    (AlarmManager)
         anyContextObject.getSystemService(Context.ALARM_SERVICE);

变量 anyContextObject 指的是一个上下文对象。例如,如果您从活动菜单中调用此代码,上下文变量将是活动。为了设置特定日期和时间的闹钟,您需要一个由 Java 日历对象标识的 time 实例。清单 17-2 显示了一个实用函数,它为当前时间之后的某个特定时刻提供了一个日历对象。

清单 17-2 。一些有用的日历工具

//In filename: Utils.java
public class Utils {
    public static Calendar getTimeAfterInSecs(int secs) {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND,secs);
        return cal;
    }
}

在本章的可下载项目中,您将看到更多基于日历的工具,它们可以通过多种方式到达时间实例。现在,我们需要一个接收器来设置我们计划设置的警报。清单 17-3 中显示了一个简单的接收器。

清单 17-3 。 TestReceiver 用于测试警报广播

//In filename: TestReceiver.java
public class TestReceiver extends BroadcastReceiver  {
    private static final String tag = "TestReceiver";
    @Override
    public void onReceive(Context context, Intent intent)  {
        Log.d (tag, "intent=" + intent);
        String message = intent.getStringExtra("message");
        Log.d(tag, message);
    }
}

您需要使用 < receiver > 标签在清单文件中注册这个接收者,如清单 17-4 所示。接收器在第 16 章中有详细介绍。

清单 17-4 。注册广播接收器

<!-- In filename: AndroidManifest.xml -->
<receiver android:name=".TestReceiver"/>

在 Android 中,警报实际上是一种广播意图,被安排在稍后的时间。这个意图应该调用的接收方组件在意图中明确指定(通过它的类名)。清单 17-5 显示了一个意图,可以用来调用我们在清单 17-3 中的广播接收器。

清单 17-5 。创建一个指向 TestReceiver 的意图

//In filename: SendAlarmOnceTester.java
Intent intent = new Intent(mContext, TestReceiver.class);
intent.putExtra("message", "Single Shot Alarm");

我们也有机会在创建意图时加载“额外内容”。因为警报管理器存储了一个意图供以后使用,我们需要从清单 17-5 的意图中创建一个待定意图。清单 17-6 显示了如何从一个标准意向创建一个待定意向。

清单 17-6 。创建待定意向

//In filename: SendAlarmOnceTester.java
PendingIntent pendingIntent =
    PendingIntent.getBroadcast(
      mContext,    //context, or activity, or service
      1,           //request id, used for disambiguating this intent
      intent,      //intent to be delivered
      0);          //pending intent flags

注意,我们已经要求 pending content 类构造一个显式适用于广播的待定 Intent。创建待定意向的其他变化在清单 17-7 中列出:

清单 17-7 。用于创建待定意向的多个 API

//useful to start an activity
PendingIntent activityPendingIntent = PendingIntent.getActivity(..args..);
//useful to start a service
PendingIntent servicePendingIntent = PendingIntent.getService(..args..);

清单 17-7 中,方法 getActivity() 和 getService() 的参数类似于清单 17-6 中 getBroadcast() 方法的参数。请注意,警报需要广播待定意图,而不是活动待定意图或服务待定意图。

我们将在本章后面更详细地讨论请求 id 参数,我们在清单 17-6 中将它设置为 1。简而言之,它用于分离两个在所有其他方面都等于的意图对象。

待定意向标志对警报管理器影响很小或没有影响。建议不使用任何标志,并使用 0 作为它们的值。这些意向标志通常有助于控制待定意向的生存期。但是,在这种情况下,生命周期由警报管理器维护。例如,要取消一个待定的意向,您要求警报管理器取消它。

一旦我们有了作为日历对象的以毫秒为单位的时间实例和指向接收者的待定意向,我们就可以通过调用警报管理器的 set() 方法来设置一个警报。这显示在清单 17-8 中。

清单 17-8 。使用警报管理器的 set() 方法

//In filename: SendAlarmOnceTester.java
Calendar cal = Utils.getTimeAfterInSecs(30);
//...other code that gets the pendingintent etc
am.set(AlarmManager.RTC_WAKEUP,
        cal.getTimeInMillis(),
        pendingIntent);

set() 方法的第一个参数表示闹铃的唤醒性质,以及我们将用于闹铃的参考时钟。该参数的可能值为 AlarmManager。RTC_WAKEUP ,警报管理器。RTC ,警报管理器。ELAPSED_REALTIME , AlarmManager。ELAPSED_REALTIME_WAKEUP

这些常量中的 elapsed 字指的是自设备最近启动以来的时间,以毫秒为单位。因此,它指的是设备时钟。 RTC 时间是指当你在设备上查看自己的时钟时,你在设备上看到的人类时钟/时间。这些常量中的 WAKEUP 一词指的是警报的性质,比如警报是应该唤醒设备还是只是在设备最终唤醒的第一时间传递。综上所述, RTC_WAKEUP 表示使用实时时钟,器件应该被唤醒。常量 ELAPSED_REALTIME 表示使用设备时钟,不唤醒设备;相反,要在第一时间发出警报。

当调用清单 17-8 中的方法时,警报管理器将调用清单 17-3的测试接收器,在该方法被调用的日历时间之后 30 秒,如果设备处于睡眠状态,警报管理器也会唤醒设备。

反复触发警报

现在让我们考虑如何设置一个重复的闹钟;见清单 17-9 。

清单 17-9 。设置重复闹钟

public void sendRepeatingAlarm() {
    Calendar cal = Utils.getTimeAfterInSecs(30);

    //Get an intent to invoke the receiver
    Intent intent = new Intent(this.mContext, TestReceiver.class);
    intent.putExtra("message", "Repeating Alarm");

    int requestid = 2;
    PendingIntent pi = this.getDistinctPendingIntent(intent, requestid);
    // Schedule the alarm!
    AlarmManager am =
        (AlarmManager)
            this.mContext.getSystemService(Context.ALARM_SERVICE);

    am.setRepeating(AlarmManager.RTC_WAKEUP,
            cal.getTimeInMillis(),
            5*1000, //5 secs repeat
            pi);
}

protected PendingIntent getDistinctPendingIntent(Intent intent, int requestId) {
    PendingIntent pi =
        PendingIntent.getBroadcast(
          mContext,     //context, or activity
          requestId,    //request id
          intent,       //intent to be delivered
          0);
    return pi;
}

清单 17-9 中代码的关键元素被突出显示。通过调用警报管理器对象上的 setRepeating() 方法来设置重复警报。此方法的主要输入是指向接收者的挂起意图。我们使用了在清单 17-5 中创建的相同意图,指向 TestReceiver 的意图。然而,当我们用清单 17-5 中的意图制作一个待定意图时,我们将唯一请求代码的值改为 2。如果我们不这样做,我们会看到一点奇怪的行为,我们现在解释。假设我们打算通过两个不同的警报调用同一个接收器:一个警报只响一次,另一个警报重复响。因为两个警报都指向同一个接收者,所以它们需要使用指向同一个接收者的意图。指向同一个接收者的两个意图之间没有任何其他区别,被认为是相同的意图。因此,当我们告诉警报管理器将 intent 1 上的警报设置为一次性警报,然后将 intent 2 上的警报设置为重复警报时,我们可能会认为它们是两个不同的警报。但是,在内部,两个警报指向相同的 intent 值,因为 intent 1 和 intent 2 的值相同。这就是为什么警报实际上与其设置目的相同(特别是通过值)。因此,如果意图相同,后一个警报会覆盖第一个警报。

同样,如果两个意图具有相同的动作、类型、数据、类别或类,则它们被认为是相同的。在计算意图的唯一性时,额外的因素不包括在内。此外,如果两个待定意图的底层意图相同并且请求 id 匹配,则它们被认为是相同的。因为我们可以使用请求 ID 来区分两个待定的意图,所以清单 17-8 中的代码通过使用请求 id 参数克服了源意图的相似性。这个请求 id 到待定内容 API 的参数将在所有其他内容匹配时将一个待定意图与另一个待定意图分开。

如果您将未决意图(通过值而不是通过其 Java 对象引用)本身视为您设置不同时间的警报,这一切都应该是有意义的。

取消警报

清单 17-10 中的代码用于取消警报。

清单 17-10 。取消重复警报

public void cancelRepeatingAlarm() {
    //Get an intent that was originally
    //used to invoke TestReceiver class
    Intent intent = new Intent(this.mContext, TestReceiver.class);

    //To cancel, extra is not necessary to be filled in
    //intent.putExtra("message", "Repeating Alarm");

    PendingIntent pi = this.getDistinctPendingIntent(intent, 2);

    // Cancel the alarm!
    AlarmManager am =
        (AlarmManager)
           this.mContext.getSystemService(Context.ALARM_SERVICE);
    am.cancel(pi);
}

要取消一个警报,我们必须首先构造一个挂起的意图,然后将其作为参数传递给警报管理器的 cancel() 方法。但是,您必须注意确保待定内容在设置警报时以完全相同的方式构建,包括请求代码和目标接收器。

在构建取消意图时,可以忽略原始意图中的额外意图(清单 17-10 ),因为额外意图在意图的唯一性中不起作用,因此取消该意图。

了解警报的准确性

在 API 19 之前,Android 会在尽可能接近指定时间的时候触发警报。从 API 19 开始,为了电池寿命,彼此靠近的警报被捆绑。如果你需要更老的行为,有一个版本的 set() 方法叫做 setExact() 。还有一种叫做 setWindow() 的方法,它允许有效率的空间,也允许有保证的窗口。类似地,方法 setRepeating() 现在是不精确的。与 setExact() 方法不同, setRepeating() 没有确切的版本。如果你有这样的需求,你就得用 setExact() 自己重复多次。

了解警报的持续性

关于警报的另一个注意事项是,它们不会在设备重启后保存。这意味着您需要将警报设置和待定意图保存在持久性存储中,并根据设备重启广播动作和可能的时变广播动作(例如,意图)重新注册它们。动作 _ 启动 _ 完成,意图。ACTION_TIME_CHANGED ,意图。动作 _ 时区 _ 改变。

参考

以下参考资料将帮助您了解本章中讨论的主题的更多信息:

摘要

本章探讨了用于设置和取消警报的警报管理器 API。本章向您展示了如何将警报连接到广播服务。本章还向您展示了警报是如何与意图紧密相关的。