安卓意图学习手册(三)
原文:
zh.annas-archive.org/md5/0C85816AE60A0FB2F72BC5A43007FCC3译者:飞龙
第八章:广播意图
在上一章中,我们了解了意图过滤器以及这些过滤器如何向安卓操作系统提供有关不同活动、服务等的信息。我们还讨论了意图过滤器的工作原理以及它们如何将到来的意图对象与属性相匹配。本章还提供了关于动作、数据和类别测试的信息。
意图是安卓操作系统不同组件之间异步发送消息的方式。到目前为止,我们只学会了如何从一个组件向另一个组件发送和接收这些消息,即意图。但在我们讨论的每个示例中,我们都有关于意图接收者的信息,比如哪个活动或服务将接收意图并使用嵌入在意图中的数据。
在本章中,我们将扩展有关向多个广播接收器发送意图的知识。我们将学习安卓操作系统如何广播意图,以及这些广播意图是如何被接收的。
本章节包括以下主题:
-
在安卓操作系统中的广播
-
安卓操作系统中的广播意图
-
安卓操作系统中的系统广播
-
使用安卓操作系统的不同系统广播
-
检测电池电量低广播
-
检测屏幕
开启/关闭广播 -
检测手机重启完成广播
-
发送/接收自定义广播意图
提示
要理解本章及后续章节,需要掌握第二章安卓意图简介和第三章意图及其分类中讨论的意图概念和意图结构。如果你对这些基础概念不熟悉,我们建议你阅读第三章意图及其分类和第四章移动组件的意图,以便继续学习。
在安卓操作系统中的广播
任何运行 Android 操作系统的智能手机在特定时间都有很多服务和动作在执行。这些服务和动作可能在前景或后台运行。那么这里我们可能会想,这些服务和动作实际在做什么呢?答案非常简单。这些服务和动作在等待某些事件的发生,或在后台执行一些长时间的操作,或与 Android 操作系统的其他组件进行通信等等。你可能会想知道这些组件如何在事件发生时进行监听,或者它们如何在后台与其他组件进行通信,尤其是当用户不能直接与应用程序交互时。在 Android 操作系统中,这类任务是通过广播来完成的。Android 操作系统不断地广播有关不同动作的信息,例如是否连接了电源和是否开启了 Wi-Fi。我们作为开发者,在我们的应用程序中使用这些广播信息,使我们的应用程序更具互动性和智能。在下一节中,我们将了解 Android 操作系统如何广播不同的信息。
广播意图
广播意图是通过调用任何Activity类的sendBroadcast()、sendStickyBroadcast()或sendOrderedBroadcast()方法来广播的Intent对象。这些广播意图为不同的应用程序组件之间提供了一个消息和事件系统。此外,Android 操作系统还使用这些意图来通知感兴趣的应用程序关于系统事件,比如电量低或者是否插入了耳机。要创建广播意图的实例,我们必须在其中包含一个动作字符串。动作字符串用于标识广播意图,它是唯一的。这个动作字符串通常使用 Java 包名格式。
在下面的代码片段中,我们将创建一个广播意图的实例并将其广播出去:
在前面的代码中可以看到,并没有一个名为BroadcastIntent的特殊类。它只是一个普通的Intent对象。我们曾在startActivity()或startService()等方法中使用过这些Intent对象。这次我们将这些Intent对象传递给了Activity类的sendBroadcast()方法。我们通过调用setAction()方法来设置其动作字符串。如前所述,在setAction()方法中我们使用了包名格式。为了广播任何意图,我们使用了sendBroadcast()方法。这个方法可以广播任何给定的意图。记住,这个方法调用是异步的,会立即返回。你不能从任何接收器中得到任何结果,接收器也不能中止任何广播意图。感兴趣的接收器会将意图的动作字符串与它们自己的动作字符串匹配,如果匹配,这些接收器将被执行。
注意
从现在开始,在本章中我们将使用关键词广播或广播们,而不是广播意图。
Android 系统内置的广播
Android OS 包含不同类型的广播。Android OS 不断广播这些意图,以通知其他应用程序系统中的各种变化。例如,当设备电量低时,Android OS 会广播包含低电量信息的意图;对这一信息感兴趣的应用程序和服务在接收到后会执行相应的操作。这些广播在 Android OS 中是预定义的,我们可以在应用程序中监听这些意图,使我们的应用更具交互性和响应性。
提示
你可以在名为broadcast_actions.txt的文本文件中找到所有可能的广播列表。该文件存储在Android文件夹下的SDK文件夹中。
<ANDROID_SDK_HOME>/platforms/android-<PLATFORM_VERSION>/data/broadcast_actions.txt
下表展示了一些 Android OS 广播及其行为描述的列表:
| 广播意图动作 | 描述 |
|---|---|
android.intent.action.ACTION_POWER_CONNECTED | 当手机连接到电源时,会广播此意图。 |
android.intent.action.ACTION_POWER_DISCONNECTED | 当手机从任何电源断开时,会广播此意图。 |
android.intent.action.BATTERY_LOW | 当手机电量低时,会广播此意图。 |
android.intent.action.BOOT_COMPLETED | 当手机启动完成时,会广播此意图。 |
android.intent.action.DEVICE_STORAGE_LOW | 当手机设备存储空间不足时,会广播此意图。 |
android.intent.action.NEW_OUTGOING_CALL | 当新的外拨电话开始时,会广播此意图。 |
android.intent.action.SCREEN_OFF | 当手机屏幕关闭时,会广播此意图。 |
android.intent.action.SCREEN_ON | 当手机屏幕打开时,会广播此意图。 |
android.net.wifi.WIFI_STATE_CHANGED | 当手机的 WIFI 状态改变时,会广播此意图。 |
android.media.VIBRATE_SETTING_CHANGED | 当手机的振动设置改变时,会广播此意图。 |
android.provider.Telephony.SMS_RECEIVED | 当手机收到短信时,会广播此意图。 |
正如我们前面的表格中所看到的,Android OS 通过发送广播,不断通知不同的应用程序关于设备状态的多种变化。我们可以监听这些变化或广播,并执行自定义操作,使我们的应用更具响应性。
注意
你可能已经注意到,一些前面的意图,如android.provider.Telephony.SMS_RECEIVED,并不包含在SDK文件夹中的列表中。这些意图在 Android 中不受支持,并可能在任何未来的平台版本中有所变动。开发者在他们的应用中使用这些不受支持的隐藏功能时应谨慎。
到目前为止,我们只讨论了广播,但还没有在实际例子中使用它们。在下一节中,我们将开发一些示例,在这些示例中,我们将监听一些 Android 操作系统的预定义广播,并根据情况进行操作。
检测设备的低电量状态
在本节中,我们将实现一个当手机电量低时会显示警报消息的小应用程序。现在,让我们开始第一个示例的开发。但是,为了开始这个示例,你需要构建一个 Android 项目。你可以使用 Android Studio 或 Eclipse IDE(根据你的方便),但如果是 Eclipse,请确保你已经正确安装了 JDK、ADT 和 Android SDK 及其兼容性。如果你不知道这些 IDE 之间的区别,可以参考本书的第一章《理解 Android》。按照这些步骤将帮助你创建一个带有一些预定义文件和文件夹的完整 Android 项目。
创建一个空的 Android 项目后,我们需要修改两个文件:一个主活动文件和一个清单文件。同时我们也添加了一个接收器文件。现在,让我们详细看看这些文件。
BatteryLowReceiver.java 文件
由于我们正在开发一个低电量警报应用,我们首先要做的是检测低电量。为此,我们需要创建一个BroadcastLowReceiver类,它将监听低电量广播。以下代码显示了接收器文件的实现:
如前述代码所示,我们从BroadcastReceiver类扩展了我们的类,并覆盖了onReceive()方法。当接收到任何广播时,将调用此方法。我们首先要做的是检查这个意图是否是低电量意图或其他广播。为此,我们检查意图的动作字符串是否与标准的 Android 动作Intent.ACTION_BATTERY_LOW相匹配。如果结果为true,则意味着设备的电量低,我们需要执行自定义操作。
接下来,我们创建一个线程,在其中传递一个匿名Runnable对象。我们覆盖了run()方法,在这个方法中,我们使用AlertDialog.Builder接口创建一个AlertDialog实例。我们设置详细信息,比如警报的标题和消息,然后显示它。
你可能会好奇为什么我们要创建一个线程来显示警报。我们本可以在不使用线程的情况下显示警报。需要注意的是,广播接收器运行的时间非常短,大约只有 4 毫秒。开发者在在接收器中执行操作时应该非常小心。从广播接收器中创建线程来执行创建警报、启动活动和服务的操作是一种良好的实践。
现在,我们的 BatteryLowReceiver 类已经准备好了。但是,这个接收器是如何被触发的,这个类如何从 Android 操作系统中接收低电量广播呢?这些问题将在下一节中解释。现在让我们详细看看我们的活动文件。
BatteryLowActivity.java 类
这个类代表了应用程序的主要活动,这意味着每当应用程序启动时,这个活动将首先被启动。以下代码展示了我们活动文件的实现:
与往常一样,我们从 Activity 类扩展了我们的类。然后,我们覆盖了活动的 onCreate() 方法。我们创建了一个 IntentFilter 实例,并在其构造函数中传递了 Intent.ACTION_BATTERY_LOW 动作字符串。你可以在第七章,意图过滤器中了解更多关于意图过滤器的信息。之后,我们创建了 BatteryLowReceiver 类的一个实例。最后,我们调用 registerReceiver() 方法,并在其中传递我们的接收器和过滤器对象。这个方法告诉 Android 操作系统,我们的应用程序对低电量广播感兴趣。这样我们就可以监听低电量广播。这里需要注意的是,当调用 registerReceiver() 方法时,开发人员有责任在应用程序不再对低电量广播感兴趣时调用 unregisterReceiver() 方法。如果开发人员没有注销它,无论应用程序是打开还是关闭,它都会监听低电量广播并相应地采取行动。
这对内存和应用程序的优化可能是有害的。我们可以在 Activity 类的 onDestroy()、onPause() 或 onStop() 回调中调用 unregisterReceiver() 方法,如下面的代码片段所示:
AndroidManifest.xml 文件
开发人员也可以在 AndroidManifest.xml 文件中注册接收器。在清单文件中注册接收器的优点是,开发人员无需通过调用 unregisterReceiver() 方法手动注销它们。Android 操作系统会自行处理这些接收器,开发人员无需再为此担心。下面是我们 AndroidManifest.xml 文件的代码实现,其中注册了我们的低电量接收器:
在前面的代码中,您可以看到我们在<application>标签内使用了<receiver>标签来注册我们的广播接收器。我们在<receiver>标签的android:name属性中插入了BatteryLowReceiver的完整包名,作为接收器的名称。正如我们在活动文件中通过创建IntentFilter类实例来设置意图过滤器动作一样,我们在<intent-filter>标签内嵌入了动作名为android.intent.action.BATTERY_LOW的意图过滤器。这个意图过滤器将告诉 Android 操作系统,接收器对设备的低电量状态信息感兴趣。
注意
开发者应注意,注册接收器应该只采用一种方法;要么从活动中调用registerReceiver()方法,要么从他们的AndroidManifest.xml文件中注册。使用AndroidManifest.xml文件来注册应用程序的BroadcastReceiver是一个好习惯。
当我们运行应用程序时,我们会看到一个空白屏幕,因为我们没有为活动设置任何布局。但是当手机电量低时,手机上会显示一个警告框。以下截图展示了来自我们BatteryLowReceiver类的警告框:
检测手机的屏幕开启/关闭状态
几乎在所有的 Android 手机中,我们在接听电话时都注意到了一个非常有趣的功能;我们可以看到屏幕会开启或关闭。此外,您可能已经观察到,当您将手机靠近耳朵时,屏幕会关闭,而当你从耳朵旁移开并手持手机时,屏幕会自动开启。这是智能手机的有趣行为。假设我们想要开发一个应用程序,每当屏幕开启时,我们都想开启扬声器模式,以便与我们在一起的其他人可以听到并参与电话对话。而当我们再次将其放在耳朵上,屏幕关闭时,我们想要关闭扬声器模式。下图展示了这个应用程序的概念:
现在,让我们在以下示例中开发这样的应用程序。首先从您喜欢的 IDE 中创建一个 Android 项目。然后,我们首先需要检测屏幕是否已开启或关闭。为了检测这一点,我们将实现我们自定义的BroadcastReceiver类。让我们在下一节中实现我们的广播接收器类。
ScreenOnOffReceiver.java文件
ScreenOnOffReceiver.java文件表示我们用于检测手机屏幕开启/关闭状态的定制广播接收器。以下代码实现展示了我们的屏幕开启/关闭检测接收器:
与之前的示例一样,我们的ScreenOnOffReceiver类从BroadcastReceiver类扩展而来,并覆盖了onReceive()方法。当我们的应用程序接收到任何广播意图时,将调用此方法。我们的应用程序首先通过将意图动作与Intent.ACTION_SCREEN_ON或Intent.ACTION_SCREEN_OFF常量进行比较,来检查它是否是屏幕开启/关闭的意图。记住,在之前的示例中,我们只监听一个广播意图。然而在本例中,我们监听两个广播意图:一个用于屏幕开启,另一个用于屏幕关闭。
在 Android 手机中,屏幕不仅在通话期间会开启/关闭。当手机锁定/解锁时,屏幕也会开启/关闭。因此,在设置我们的扬声器开启/关闭之前,我们必须检查当前是否正在通话。我们可以通过检查AudioManager的模式来检测它。如果模式是AudioManager.MODE_IN_CALL,那就意味着我们当前正处于来电或去电的通话中。一旦我们确认了通话模式状态,那么我们就可以设置扬声器的开启/关闭。我们使用AudioManager.setSpeakerphoneOn(boolean)方法来实现这一目的。
到目前为止,我们已经实现了我们的接收器。但我们还没有注册这些接收器。记得在前一个示例中,我们使用了两种方法来注册我们的自定义广播接收器:一种是从活动类中使用registerReceiver()方法,另一种是从AndroidManifest.xml文件中。让我们选择后者,即使用AndroidManifest.xml文件来注册我们的接收器。
AndroidManifest.xml 文件
与前面的示例一样,我们将在该清单文件中注册我们的ScreenOnOffReceiver广播接收器。需要注意的是,在之前电池电量低的应用示例中,我们只为一个过滤器注册了接收器,即手机的低电量状态。然而,在本例中,我们监听两个状态过滤器:屏幕开启和屏幕关闭。但我们只实现了一个广播接收器。下面让我们看看如何在以下AndroidManifest.xml文件的代码实现中,用两个意图过滤器注册一个接收器:
在前面的代码中,我们可以看到,我们在<application>标签内放置了<receiver>标签,以注册我们的接收器。同时,需要注意的是,这次我们两次使用了<intent-filter>标签,其中嵌入了两种不同的动作:一个是android.intent.action.SCREEN_ON,另一个是android.intent.action.SCREEN_OFF。你可以在第七章《意图过滤器》中了解更多关于多个意图过滤器的内容。这两个意图过滤器连同我们在AndroidManifest.xml文件中嵌入的接收器一起,将我们的ScreenOnOffReceiver广播接收器注册到 Android 操作系统中,监听手机屏幕开启和关闭状态的变化。
检测手机的重启完成状态
许多安卓应用程序在执行多项任务和操作时会在后台运行服务。例如,一个天气应用会通过后台服务在固定时间间隔后持续检查天气情况。但你有没有想过,当你重启手机或电池耗尽导致手机重启后,这些服务是如何在重启后再次开始运行的?在本节中,我们将了解如何实现这一点。
当一部安卓手机成功重启后,安卓操作系统会广播一个意图,通知其他应用程序重启已完成。然后这些应用程序会再次启动它们的后台服务。在本节中,我们将创建一个监听重启完成广播的应用程序,并从中启动我们的测试服务。
让我们在任何 IDE(如 Eclipse 或 Android Studio)中创建一个空的安卓项目。像往常一样,我们首先实现我们的广播接收器类。
PhoneRebootCompletedReceiver.java 文件
PhoneRebootCompletedReceiver.java 类表示我们的重启完成广播接收器文件。以下代码展示了该文件的实现:
你可以在前面的代码中看到,我们没有做任何新的操作。我们的类是从BroadcastReceiver类扩展而来的。然后,我们检查意图的Intent.ACTION_BOOT_COMPLETED动作是否为真。如果是,我们通过调用Context.startService()方法来启动我们的临时服务。现在,让我们在下一节中看看TempService类的作用。
TempService.java 文件
TempService.java 类表示我们将在安卓系统启动完成后开始运行的服务。
注意
在 Android 3.0 中,用户至少需要启动一次应用程序,之后应用程序才能接收到android.intent.action.BOOT_COMPLETED的广播。
以下代码展示了我们TempService类的实现:
与任何常规服务类一样,我们的类是从Service扩展而来的。我们重写了两个方法:onBind()和onStartCommand()。在onStartCommand()方法中,我们将通过调用Toast.makeText()方法并传入"Service started"文本来显示一个提示框。当我们的手机启动完成后,将显示这个提示框。我们可以在该方法中实现我们自己的自定义操作。
现在,我们还需要通知安卓操作系统,我们的应用程序想要监听 Boot Completed 广播。与之前的程序一样,我们将在AndroidManifest.xml文件中注册我们的接收器。下一节我们将看到这一点。
AndroidManifest.xml 文件
AndroidManifest.xml 文件通知安卓操作系统我们的应用程序想要监听 Boot Completed 广播。以下代码展示了该清单文件的实现:
与之前的示例应用几乎相同。我们使用 <application> 标签内的 <receiver> 标签注册了我们的接收器,并带有 android.intent.action.BOOT_COMPLETED 动作的意图过滤器。我们还通过使用 <application> 标签内的 <service> 标签注册了 TempService。必须注意,Boot Completed 广播需要用户授予 android.permission.RECEIVE_BOOT_COMPLETED 权限。我们可以通过添加 android:name 属性设置为 android.permission.RECEIVE_BOOT_COMPLETED 的 <uses-permission> 标签,请求用户授予此权限。这样,当手机重启时,我们就可以启动自定义服务了。
发送和接收自定义广播
到目前为止,我们只接收广播。我们实验过的所有意图都是 Android 系统广播。在本节中,我们将讨论自定义广播。我们将了解如何向其他应用程序发送我们自己的自定义广播,以及其他应用程序如何监听我们的自定义广播意图。
在下一节中,我们将创建一个示例,该示例将向其他应用程序发送自定义广播。现在让我们为应用程序创建活动和布局文件。
activity_main.xml 布局文件
activity_main.xml 文件表示我们活动的布局文件。以下代码展示了清单文件的实现:
如您在布局文件中所见,我们放置了一个带有 ID btnSendBroadcastIntent 的按钮。我们将在活动文件中使用此按钮向其他应用程序发送广播。现在让我们看看活动文件。
MainActivity.java 文件
MainActivity.java 文件是我们应用程序的主要启动点。此活动将使用 activity_main.xml 布局文件作为其视觉部分。以下代码展示了文件的实现:
你可以从前面的代码中看到,我们通过调用findViewById()方法从我们的布局文件中获取了Button对象。然后我们设置了它的OnClickListener()方法,并在重写的onClick()方法中执行了我们的主要操作,即向其他应用程序发送广播。我们创建了一个Intent对象,并通过调用Intent.setAction()方法设置其动作字符串。需要注意的是,这次我们定义了自己的自定义动作值,即com.packt.CustomBroadcast字符串。创建自己的自定义广播接收器时,我们应该遵循包命名约定。最后,我们使用该意图进行广播,通过调用Activity类的sendBroadcast()方法。这就是我们的自定义广播意图发送到 Android 操作系统和其他应用程序的方式。现在,所有监听这种类型广播的应用程序和接收器都将接收到它,因此可以执行它们的自定义操作。在下一节中,我们将实现我们的自定义广播接收器类,它将接收这种类型的意图,并通过显示提示来通知用户。
CustomReceiver.java 文件
CustomReceiver.java 文件代表我们的自定义广播接收器类,它将接收我们的自定义广播。这个类可以在这个应用程序中,也可以在任何其他想要监听这种自定义类型广播的应用程序中。与之前的所有示例一样,这个类将相同,并且从BroadcastReceiver类扩展而来。与之前示例的唯一区别在于,我们之前使用的是 Android 操作系统的标准预定义常量动作字符串来检测系统广播,但在这个示例中,我们正在监听具有自定义动作字符串设置的自定义广播。以下是文件实现的代码:
public class OurCustomReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
if (intent.getAction() == "com.packt.CustomBroadcast") {
Toast.makeText(context, "Broadcast Intent Detected.",
Toast.LENGTH_LONG).show();
}
}
}
从前面的代码中可以看出,我们并没有做任何你还不熟悉的新操作。我们的类是从BroadcastReceiver派生出来的,并重写了onReceive()方法。然后我们将意图的 action 字符串与我们的自定义字符串com.packt.CustomBroadcast动作进行比较。如果为true,我们将显示一个提示“检测到广播意图”。我们可以在该方法中执行自定义操作。最后,我们必须注册这个接收器,以便 Android 操作系统可以通知我们的应用程序关于广播的信息。
AndroidManifest.xml 文件
与往常一样,AndroidManifest.xml 告诉 Android 操作系统我们的应用程序正在监听自定义广播。以下是文件实现的代码:
你可以看到,我们以与注册 Android 系统广播接收器相同的方式注册了我们的自定义广播接收器。现在,当我们运行这个应用程序时,我们会看到一个名为发送广播意图的按钮。当我们点击这个按钮时,我们的自定义广播将在 Android 操作系统中广播。由于我们也为此自定义意图创建了一个接收器,因此我们也将接收到这个意图。在接收到意图时,我们的自定义接收器将显示一个提示消息。以下屏幕截图展示了这个应用程序的执行情况:
总结
在本章中,我们讨论了广播。我们还了解了 Android 操作系统的不同系统广播意图,例如电量低、电源连接和开机完成。我们也学习了如何通过注册自定义接收器来接收这些广播,以及如何在那些接收器中执行我们自己的自定义操作。最后,我们学习了如何发送我们自己的自定义广播以及接收这些自定义意图。
在下一章中,我们将探索两种特殊的意图类型:IntentService和PendingIntent。我们还将学习如何使用这些意图以及通过这些意图可以实现什么功能。
第九章:意图服务与待定意图
从本书一开始,我们就一直在研究意图可以执行的不同任务,以促进 Android 及其类型。我们已经看到意图可以帮助在活动之间导航。它们还用于在它们之间传输数据。我们了解到如何设置过滤器以验证传入的意图是否可以通过组件测试,最后我们学习了意图在广播接收器中的作用。在本章中,我们将深入了解如何使用意图服务和待定意图进行便捷操作。
在本章中,我们将探讨以下主题:
-
意图服务是什么?
-
意图服务的使用和实现
-
待定意图是什么?
-
待定意图的使用和实现
-
概括
意图服务
意图服务是一个简单的服务类型,用于处理与主线程无关的异步工作。如果客户端通过startService(Intent intent)方法发送请求,就可以这样做。这个新任务将由工作线程处理,并在没有工作可做时停止。
注意
意图服务继承了 Android API 中的Service类
意图服务用于卸载工作线程,使其不会成为瓶颈。它有助于使事情与主应用程序线程分开进行。需要注意的是,尽管它独立于主线程工作,但一次只能处理一个请求。
意图服务是从应用程序的 UI 线程卸载工作到工作队列的最佳方式。无需为每个处理创建异步任务并管理它们。相反,你定义一个意图服务,使其能够处理你想要发送进行处理的数据,然后简单地启动服务。最后,你可以通过在意图对象中广播数据并将其从广播接收器捕获来将数据发送回应用程序,以便在应用程序中使用。
四个基础组件的比较
本节展示了 Android 开发中四个最重要的元素(服务、线程、意图服务和异步任务)之间的基本区别,包括意图服务。
最佳使用情况
服务、线程、意图服务和异步任务的最佳情况场景如下表所示:
| 最佳情况场景 | |
|---|---|
| 服务 | 当任务不是很长,并且与主线程无关时 |
| 线程 | 当有一个长任务需要执行,并且需要并行完成多个任务时 |
| 意图服务 | 当有不需要主线程干预的长任务,并且需要回调时 |
| 异步任务 | 当有需要与主线程通信的长任务,并且需要并行工作 |
注意
如果 Intent Service 需要与主线程通信,我们需要使用 Handler 或广播 Intent。
触发条件
下表讨论了 Service、Thread、Intent Service 和 Async Task 之间的触发条件差异:
| Service | Thread | Intent Service | Async Task | |
|---|---|---|---|---|
| 触发方式 | 使用onStartService()方法 | 使用start()方法 | 使用 Intent | 使用execute()方法 |
| 触发原因 | 可以从任何线程调用 | 可以被任何其他线程调用和运行 | 只能从主线程调用 | 只能从主线程调用 |
| 运行环境 | 可以从主线程调用 | 在自己的线程上运行 | 在独立的工作线程上运行 | 在独立的工作线程上运行,尽管主线程的方法可能会在中间运行 |
| 限制条件 | 在某些场景下可能会阻塞主线程 | 需要手动处理,代码可能不易理解 | 不能同时处理多个任务,所有任务都在同一个工作线程上运行 | 只能有一个任务实例,不能在循环中运行 |
Intent Service 的使用和实现
从本章的前几部分,我们清楚地了解了 Intent Service 的定义以及它与 Thread、Async Task 和 Service 之间的基本区别。现在是开始实现和使用 Intent Services 的时候了。为此,我们将从一个示例开始,这将帮助我们学习如何从 Intent Service 生成假通知。
从 Intent Service 生成假通知
在这个示例中,我们将学习 Intent Service 在通知栏上生成通知的使用方法。该示例还将解释onHandleIntent()方法的使用,该方法用于实现 Intent Service 的所有功能,包括发送广播和通知到通知栏。
此外,在本节末尾,你将了解它与 Thread 或其他之前提到的 Android 定义方法之间的区别。完成这段代码后,启动活动,你将看到以下屏幕:
启动 Activity 将显示“Hello World”屏幕
提示
注意:请记住,在这个示例中,我们将不会介绍项目中使用的完整文件集。由于这是本书的最后一章,我们假设您已经掌握了关于 XML 文件、资源和布局的 Android 开发基础知识。
显示进度通知的通知面板
代码概览
该示例指的是在需要向通知栏发送关于特定事件进展或信号的情景下使用 Intent Service。
package com.app.intentservice;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
public class CustomIntentService extends IntentService {
private static final int NOTIFICATION_ID=1;
NotificationManager notificationManager;
Notification notification;
public static final String ACTION_CUSTOM_INTENT_SERVICE = "com.app.intentservice.RESPONSE";
public static final String ACTION_MY_UPDATE ="com.app.intentservice.UPDATE";
public static final String EXTRA_KEY_IN = "EXTRA_IN";
public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
public static final String EXTRA_KEY_UPDATE = "EXTRA_UPDATE";
String activityMessage;
String extraOut;
public CustomIntentService() {
super("com.app.intentservice.CustomIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
//get input
activityMessage = intent.getStringExtra(EXTRA_KEY_IN);
extraOut = "Hello: " + activityMessage;
for(int i = 0; i <=10; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//send update
Intent intentUpdate = new Intent();
intentUpdate.setAction(ACTION_MY_UPDATE);
intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
intentUpdate.putExtra(EXTRA_KEY_UPDATE, i);
sendBroadcast(intentUpdate);
//generate notification
String notificationText = String.valueOf((int)(100 * i / 10)) + " %";
notification = new NotificationCompat.Builder(getApplicationContext())
.setContentTitle("Progress")
.setContentText(notificationText)
.setTicker("Notification!")
.setWhen(System.currentTimeMillis())
.setDefaults(Notification.DEFAULT_SOUND)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
//return result
Intent intentResponse = new Intent();
intentResponse.setAction(ACTION_CUSTOM_INTENT_SERVICE);
intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
sendBroadcast(intentResponse);
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
}
}
深入理解
创建一个新项目并打开src文件夹。创建一个名为CustomIntentService.java的新的类文件,它是IntentService的子类。扩展IntentService类并覆盖onHandleIntent(Intent intent)方法。
在这一点上,你已经准备好实现自己的 Intent Service,负责将消息发送到通知栏,并以进度百分比条的形式更新它。现在,让我们通过以下步骤开始理解代码:
-
第一步是在
onHandleIntent()方法中使用变量notificationManager和notification进行声明。还有一些其他的静态最终变量将在本项目中使用。它们是NOTIFICATION_ID、ACTION_CustomIntentService、ACTION_MyUpdate、EXTRA_KEY_IN、EXTRA_KEY_OUT和EXTRA_KEY_UPDATE。还需要两个新的字符串变量以处理通知字符串,分别表示为activityMessage和extraOut。 -
这个
IntentService的主要实现将在onHandleIntent()方法中进行,我们将在这里定义包括消息到通知栏和广播消息的工作。 -
在
onHandleIntent()方法开始时,通过intent.getStringExtra()方法获取额外的数据,并将其保存在msgFromActivity变量中,稍后该变量将发送到广播。 -
我们的主要目标是发送一个通知,显示从 0 到 100%的进度(一个假的计数器),并在通知栏中更新。为此,我们初始化一个
for循环,该循环将从 0 进行到 10。在开始时,我们将调用Thread.sleep(1000),这将使线程休眠,在 1000 毫秒内不工作。 -
一旦线程休眠了一定时间,我们的假进度更新的第一个计数器就完成了。下一步是发送一个广播,其主要目的是提供更新。为了看到这一点,我们使用以下代码行:
//send update Intent intentUpdate = new Intent(); intentUpdate.setAction(ACTION_MyUpdate); intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); intentUpdate.putExtra(EXTRA_KEY_UPDATE, i); sendBroadcast(intentUpdate);快速了解我们如何发送广播:创建一个新的意图对象,并为其提供一个名称和
intentUpdate的动作;由于这是一个自定义动作,所以给它一个ACTION_MyUpdate的名称,你可以在代码中看到;定义它的类别,这也是一个自定义类别;放入计数器信息(显示循环当前计数器的变量)并发送此意图的广播。 -
下一步是将通知发送到通知栏。以下代码可以在上一个示例中看到:
//generate notification String notificationText = String.valueOf((int)(100 * i / 10)) + " %"; myNotification = new NotificationCompat.Builder(getApplicationContext()) .setContentTitle("Progress") .setContentText(notificationText) .setTicker("Notification!") .setWhen(System.currentTimeMillis()) .setDefaults(Notification.DEFAULT_SOUND) .setAutoCancel(true) .setSmallIcon(R.drawable.ic_launcher) .build(); notificationManager.notify(MY_NOTIFICATION_ID, myNotification);这段代码将
notificationText的值设置为循环的当前计数器,并将其转换为百分比;通过调用NotificationCompat.Builder()(这基本上是 Android SDK 中描述的构建器模式)创建一个新的通知,并为其提供应用程序上下文,设置其标题内容、文本、提醒、出现时间以及其他一些属性。最后,你需要调用notificationManager.notify()以在通知栏中显示它。 -
最后一步是发送另一个广播作为确认,它的流程与之前的广播相同,你可以在以下代码中看到:
//return result Intent intentResponse = new Intent(); intentResponse.setAction(ACTION_MyIntentService); intentResponse.addCategory(Intent.CATEGORY_DEFAULT); intentResponse.putExtra(EXTRA_KEY_OUT, extraOut); sendBroadcast(intentResponse); -
代码中显示的最后一步是覆盖
onCreate()方法。你可能已经注意到我们没有创建一个新的通知管理器对象,这肯定会出错。因此,为了创建新对象,我们将通过使用通知管理器getSystemService (Context.NOTIFICATION_SERVICE)获取 Android 的系统服务。
注意
这个示例还需要一个广播接收器。如果你还没有关于它的想法,可以参考前面的章节。
再举一个例子。
上一个示例主要处理了在 Android 通知栏中实现通知的问题。它涵盖了 Intent 服务的实现,但没有涉及广播接收器的制作及其注册。在这个例子中,我们将学习如何使用 Intent 服务并将所有输入数据转换为大写,并将其广播回广播接收器。广播接收器的实现也是这个示例的一部分。
从示例开始,在你的开发环境中使用以下代码来实现它:
public class IntentServiceExampleTwo extends IntentService {
private static final String TAG =IntentServiceExampleTwo.class.getSimpleName();
public static final String INPUT_TEXT_STRING="INPUT_TEXT_STRING";
public static final String OUTPUT_TEXT="OUTPUT_TEXT";
/**
* initiate service in background thread with service name
*/
public IntentServiceExampleTwo() {
super(IntentServiceExampleTwo.class.getSimpleName());
}
@Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG,"onHandleIntent()");
String data =intent.getStringExtra(INPUT_TEXT_STRING);
Log.d(TAG,data);
data=data.toUpperCase();
SystemClock.sleep(4*1000);
Intent stringBroadCastIntent =new Intent();
stringBroadCastIntent.setAction(TextCapitalizeResultReceiver.ACTION_TEXT_CAPITALIZED);
stringBroadCastIntent.addCategory(Intent.CATEGORY_DEFAULT);
stringBroadCastIntent.putExtra(OUTPUT_TEXT, data);
sendBroadcast(stringBroadCastIntent);
}
}
这几乎与之前的第一个示例中的实现相同。在这个例子中,展示了onHandleIntent()方法的工作原理,其中进行以下步骤:
-
在
onHandleIntent()方法中,你可以看到的第一步是从传入的意图中获取数据并将其保存到一个变量中。变量data包含我们将转换为大写的传入数据。 -
第二步是将数据记录到 LogCat 中,这是通过使用
Log.d(String, String)方法获得的。第一个参数是TAG,通常是全局声明的类名,以便任何方法都可以使用它。这个类名对于将你的消息与其他消息区分开来很重要(使其易于阅读)。第二个参数是消息字符串,用于显示过程中的任何数据,以便开发者在运行时可以看到其值。 -
第三步是将这些数据转换成大写。这将有助于在广播的意图中反映更改。将其重新保存回数据变量中。
-
其余步骤与之前的示例相同,其中创建意图对象,定义类别和动作,将数据作为额外内容放入并发送给接收器进行广播。
下一步是设置将从sendBroadcast()方法接收的接收器。为此,请查看以下代码:
public class UpperCaseReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
TextView textViewResult = (TextView)findViewById(R.id.receiving_text_view);
String result =intent.getStringExtra(ExampleIntentService.OUTPUT_TEXT);
textViewResult.setText(result);
}
};
之前的代码是示例中关于如何创建广播接收器的部分,这将接收广播并设置 textView。你可以在代码中看到,覆盖了 onReceive() 方法,该类正在扩展广播接收器。在 onReceive() 方法内部,通过 intent.getStringExtra() 方法获取字符串并将其保存在结果字符串中。这个字符串将被用来设置 textView 的文本,以便你可以看到它们在 textView 中的反映。
接下来,下一步是使用 Intent Service 注册这个接收器。这将在与 Intent Filter 相关联的活动内完成,以便它可以产生效果。以下代码段展示了这一过程:
private void registerReceiver() {
IntentFilter intentFilter =new IntentFilter(UpperCaseResultReceiver.ACTION_TEXT_CAPITALIZED);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
capitalCaseReceiver=new UpperCaseResultReceiver();
registerReceiver(capitalCaseReceiver, intentFilter);
}
registerReceiver() 方法在你的活动中声明,它将从 onCreate() 或 onResume() 方法中被调用,以便在启动或恢复活动时注册广播接收器。
-
Intent Filter 被声明并使用名为
intentFilter的对象初始化。 -
intentFilter对象被设置为默认。 -
广播接收器的对象被初始化并通过调用
registerReceiver(Receiver, IntentFilter)方法与 Intent Filter 注册。
在使用 Intent Filter 注册接收器之后,下一步是在你的活动中使用它。为此,请查看以下代码。这段代码可以位于任何事件内:
Intent textUpperCaseIntent = new Intent(MainActivity.this, ExampleIntentService.class);
textUpperCaseIntent.putExtra(ExampleIntentService.INPUT_TEXT, inputText);
startService(textUpperCaseIntent);
以传统方式初始化一个意图,给出你刚刚创建的 IntentService 类,并放入你想要转换为大写的输入文本。通过 Intent.putExtra(String, String) 方法给这个意图提供额外的数据。最后一步是使用这个意图启动服务。我们将使用 startService() 方法,而不是典型的 startActivity() 方法,因为在 Android 中我们使用 startActivity 来通过意图启动活动。
Pending Intents
Pending Intent 是一种允许其他应用(或称为外部应用)访问你的意图权限以运行预定义代码段的意图。这样,许多其他应用(如闹钟管理和日历)可以利用你的应用来执行它们的任务。
Pending Intents 不会立即运行;而是在其他活动需要它运行时才执行。Pending Intent 是系统维护的一个引用,以便在稍后的阶段使用。这意味着即使包含 Pending Intent 的应用结束了,另一个应用仍然可以使用该上下文,直到对该意图调用 cancel()。
注意
要通过 Pending Intent 执行广播,请使用 PendingIntent.getBroadcast() 获取 Pending Intent。
Pending Intents 可以通过三种方法启动,分别是 getActivity(Context context, int requestCode, Intent intent, int flags)、getBroadcast(Context context, int requestCode, Intent intent, int flags) 和 getService(Context context, int requestCode, Intent intent, int flags)。为了了解 Pending Intents 在 Android 应用中的制作和使用方式,您可以继续阅读下一节,其中将介绍实现过程。
如何让 Pending Intents 工作?
本节介绍 Pending Intents 的工作原理及解释。为了更好地理解这一点,我们建议您阅读之前提到的定义,以便您能更好地理解。
在这个例子中,我们将向您展示如何制作一个应用程序,用户可以在 editText 字段(以秒为单位)中输入时间,之后闹钟将会响起,Android 的闹钟管理器将根据相应地播放闹钟。
为了进一步了解,请查看以下代码:
EditText text = (EditText) findViewById(R.id.editText1);
int i = Integer.parseInt(text.getText().toString());
Intent intent = new Intent(MainActivity.this, MyBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast( MainActivity.this, 234324243, intent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (i * 1000), pendingIntent);
Toast.makeText(MainActivity.this, "Alarm set in " + i + " seconds",
Toast.LENGTH_SHORT).show();
之前编写的代码片段可以插入到任何事件中,比如一个按钮,用于获取 EditText 字段中的输入值,并通过 Pending Intents 处理它。
-
从布局文件中获取编辑文本,并创建一个名为
text的对象,该对象保存该小部件的当前状态。 -
整数变量
i将保存编辑文本中的输入值,通过text.getText().toString()获取。 -
创建一个显式意图,将
BroadcastReceiver类作为意图的目标类(我们将在完成此部分后创建)。 -
为了启动 Pending Intent,我们使用
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags, int, Intent, int)方法。关于这个方法的更多描述可以在developer.android.com/reference/android/app/PendingIntent.html找到。 -
通过在
getSystemService()方法中传入ALARM_SERVICE来获取闹钟的系统服务,并将其指向一个AlarmManager对象。 -
使用存储在
i中的值设置闹钟管理器的值,并给它一个 Pending Intent,这将帮助它启动(因为闹钟管理器是 Android 的一个服务)。 -
alarmManager.set()方法包含参数int类型的类型、long triggerMilliSec(在其中您取当前系统时间并将变量i转换为毫秒后相加)以及 Pending Intent。 -
制作一个吐司通知,以显示闹钟管理的成功完成。
下一步是创建您选择的广播接收器并实现该接收器。为此,创建一个广播接收器并重写 onReceive() 方法。查看以下代码:
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Alarm is ringing...",
Toast.LENGTH_LONG).show();
Vibrator vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(2000);
}
这个接收器有一个吐司提示,它会指示其报警状态。下一步是声明一个振动器对象,可以通过调用 context.getSystemService(Context.VIBRATOR_SERVICE) 方法来初始化。这个方法负责返回直接影响手机物理振动器的对象。最后一步是调用 vibrator.vibrate(int) 方法来启动振动。
注意
要使用振动器,你需要在清单文件中添加权限。你可以使用以下代码片段来完成这个操作:
<uses-permission android:name="android.permission.VIBRATE" />
最后,我们必须在 AndroidManifest.xml 文件中声明这个接收器,可以通过使用以下代码简单地完成:
<receiver android:name="MyBroadcastReceiver" >
</receiver>
上一个示例描述了待定意图(pending Intent)和广播接收器(Broadcast Receivers)一起使用的情况。
概括
本书的最后一章的概要可以作为本书的结论。在本章中,我们学习了如何实现 intentService 和 PendingIntents 以及它们最佳的应用场景。IntentService 的特性及其与 Android 中最常使用的三种功能——线程(Thread)、服务(Services)和异步任务(Async Tasks)的比较。此外,在本章中,实现了待定意图的示例,并解释了每一步。可以将本章视为意图在 Android 中高级版本或高级用法的介绍。需要记住的是,这些功能的使用可能并不常见,但在某些情况下,你必须让它们工作,因为没有其他解决方案。