五、广播

Android 广播是遵循发布-订阅模式的消息。它们通过 Android 操作系统发送,内部被 Android 操作系统隐藏,因此发布者和订阅者只能看到一个简单的异步接口来发送和接收消息。广播可以由 Android 操作系统本身、标准应用以及系统上安装的任何其他应用发布。同样,任何应用都可以配置或编程为接收他们感兴趣的广播消息。像活动一样,广播可以显式或隐式路由,这是广播发送方决定的责任。

广播接收器要么在AndroidManifest.xml文件中声明,要么以编程方式声明。从 Android 8.0 (API level 26)开始,Android 的开发人员已经放弃了 XML 和广播接收器的编程声明之间通常的对称性,转而使用隐含的意图。原因是对在后台模式下运行的进程(尤其是与广播相关的进程)施加限制的总体想法导致了 Android 操作系统上的高负载,大大降低了设备的速度,并导致了糟糕的用户体验。出于这个原因,AndroidManifest.xml内部广播接收器的声明现在被限制在一个更小的用例集合中。

注意

你会想要编写可以在 Android 8.0 和更新版本中运行的现代应用。出于这个原因,认真对待这个隐含意图的广播限制,并使你的应用在这个限制内运行。

明确的广播

显式广播是以这样一种方式发布的广播,即只有一个接收方被其寻址。这通常只有在广播发布者和订阅者都是同一个应用的一部分时才有意义,或者如果它们之间有很强的功能依赖性,则是同一个应用集合的一部分。

本地广播和远程广播是有区别的:本地广播接收者必须驻留在同一个 app 中,它们运行速度很快,接收者不能在AndroidManifest.xml内部声明。相反,本地广播接收机必须使用程序化注册方法。此外,您必须使用以下内容来发送本地广播消息:

// send local broadcast
LocalBroadcastManager.getInstance(Context).
      sendBroadcast(...)

远程广播接收机,另一方面,可以驻留在同一个 app 中,它们比较慢,可以用AndroidManifest.xml来声明。要发送远程广播,请编写以下内容:

// send remote broadcast (this App or other Apps)
sendBroadcast(...)

注意

出于性能原因,本地广播应优先于远程广播。不能使用AndroidManifest.xml来声明本地接收器的明显缺点并没有太大关系,因为从 Android 8.0 (API level 26)开始,在清单文件中声明广播接收器的用例无论如何都是有限的。

明确的本地广播

要在同一个应用中向本地广播接收器发送本地广播消息,您需要编写以下代码:

val intent = Intent(this, MyReceiver::class.java)
intent.action = "de.pspaeth.simplebroadcast.DO_STH"
intent.putExtra("myExtra", "myExtraVal")
Log.e("LOG", "Sending broadcast")
LocalBroadcastManager.getInstance(this).
      sendBroadcast(intent)
Log.e("LOG", "Broadcast sent")

这里,MyReceiver是接收器类。

class MyReceiver : BroadcastReceiver() {
  override
  fun onReceive(context: Context?, intent: Intent?) {
    Toast.makeText(context, "Intent Detected.",
                   Toast.LENGTH_LONG).show()
    Log.e("LOG", "Received broadcast")
    Thread.sleep(3000)
    // or real work of course...
    Log.e("LOG", "Broadcast done")
  }
}

对于本地广播,接收者必须在代码中声明。为了避免资源泄漏,我们在onCreate()中创建和注册了接收者,并在onDestroy()中取消注册。

class MainActivity : AppCompatActivity() {
    private var bcReceiver:BroadcastReceiver? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...

        bcReceiver = MyReceiver()
        val ifi:IntentFilter =
              IntentFilter("de.pspaeth.myapp.DO_STH")
        LocalBroadcastManager.getInstance(this).
              registerReceiver(bcReceiver, ifi)
    }

    override fun onDestroy() {
        super.onDestroy()
        // ...

        LocalBroadcastManager.getInstance(this).
            unregisterReceiver(bcReceiver)
    }
}

显式远程广播

我们已经指出,我们可以将远程类型的广播消息发送到其他应用或接收者所在的同一应用。区别在于数据是如何发送的。对于远程消息,数据通过 IPC 通道传输。现在,要向同一个应用发送这样的远程广播消息,您需要编写以下代码:

val intent = Intent(this, MyReceiver::class.java)
intent.action = "de.pspaeth.myapp.DO_STH"
intent.putExtra("myExtra", "myExtraVal")
sendBroadcast(intent)

在接收端,对于远程消息,必须在清单文件中声明接收者

<application ...>
  ...
  <receiver android:name=".MyReceiver">
    <intent-filter>
      <action android:name=
            "de.pspaeth.myapp.DO_STH">
      </action>
    </intent-filter>
  </receiver>
</application>

了解本地和远程广播之间的差异,记住以下几点很有帮助:

  • 本地显式广播:

    发送方使用显式的接收方类,接收方必须以编程方式声明,发送方和接收方都使用LocalBroadcastManager来发送消息和注册接收方。

  • 远程显式广播:

    发送方使用显式的接收方类,接收方必须在AndroidManifest.xml中声明。

对于负责处理接收到的广播的类,与显式本地广播相比没有区别。

class MyReceiver : BroadcastReceiver() {
  override
  fun onReceive(context: Context?, intent: Intent?) {
    // handle incoming broadcasts...
  }
}

发送到其他应用的明确广播

显性广播的发送者和接收者可以生活在不同的应用中。为此,您不能再使用我们之前使用的 intent 构造函数。

val intent = Intent(this, MyReceiver::class.java)
intent.action = "de.pspaeth.myapp.DO_STH"
// add other coords...
sendBroadcast(intent)

这是因为接收类(这里是MyReceiver)不是类路径的一部分。然而,我们可以使用另一种结构来代替。

val intent = Intent()
intent.component = ComponentName("de.pspaeth.xyz",
      "de.pspaeth.xyz.MyReceiver")
intent.action = "de.pspaeth.simplebroadcast.DO_STH"
// add other coords...
sendBroadcast(intent)

这里,ComponentName的第一个参数是接收包的包字符串,第二个参数是类名。

警告

除非你正在向自己构建的应用广播,否则这种发送明确广播的方式只有有限的用途。另一个应用的开发人员可能很容易决定更改类名,然后您使用广播与另一个应用的通信将会中断。

隐式广播

隐式广播是具有不确定数量的可能接收者的广播。对于显式广播,您了解到我们必须通过使用指向接收方组件的构造函数来构建相应的意图:val intent = Intent(this, TheReceiverClass::class.java)。与此相反,对于隐式广播,我们不再指定接收者,而是提示哪些组件可能对接收它感兴趣。这里有一个例子:

val intent = Intent()
intent.action = "de.pspaeth.myapp.DO_STH"
sendBroadcast(intent)

在这里,我们实际上表达如下:“向所有对动作de.pspaeth.myapp.DO_STH感兴趣的接收者发送广播消息。”Android OS 确定哪些组件有资格接收这样的广播消息;这可能导致零个、一个或多个实际收件人。

在开始编程隐式广播之前,您必须做出三个决定。

  • 我们想听系统广播吗?

    Android 存在大量预定义的广播消息类型。在你用 Android Studio 安装的 Android SDK 里面,在SDK_INST_DIR/platforms/VERSION/data/broadcast_actions.txt,你可以找到一个系统广播动作的列表。如果我们想要收听这样的消息,我们只需要按照本章后面的描述对适当的广播接收机进行编程。在在线文本指南的“系统广播”部分,您会找到系统广播的完整列表。

  • 我们如何对广播消息类型进行分类?

    广播发送者和广播接收者通过意图过滤器匹配来连接,就像活动一样。正如在第 3 章中所讨论的,当描述活动的意图过滤器时,广播的分类是三重的:首先是一个强制的动作说明符,其次是一个类别,第三是一个可以用来定义过滤器的数据和类型说明符。我们将在本章后面描述这个匹配过程。

  • 我们是在向本地广播还是远程广播前进?

    如果所有的广播都完全发生在你的应用中,你应该使用本地广播来发送和接收消息。对于隐式广播,这种情况可能不会太常见,但对于大型复杂的应用,这是完全可以接受的。如果涉及系统广播或来自其他应用的广播,您必须使用远程广播。后者是大多数例子中的默认情况,所以您会经常看到这种模式。

意图过滤器匹配

广播接收机通过声明动作类别数据说明符来表示接受广播。

先说动作。这些只是没有任何语法限制的字符串。更彻底地观察它们,您会看到我们首先有一组或多或少严格定义的预定义动作名称。我们在第三章中列出了它们。此外,您可以定义自己的操作。惯例是使用包名加一个点,然后是一个动作说明符。你不一定要遵循这个惯例,但是强烈建议你这样做,这样你的应用就不会和其他应用混淆。如果不指定任何其他筛选条件,指定您在筛选器中指定的特定操作的发件人将会到达所有匹配的收件人。

  • 为了匹配意图过滤器,接收方指定的动作必须与发送方指定的动作相匹配。对于隐式广播,一次广播可以寻址零个、一个或多个接收器。

  • 接收者可以指定一个以上的过滤器。如果其中一个过滤器包含指定的动作,这个特定的过滤器将匹配广播。

5-1 显示了一些例子。

表 5-1

动作匹配

|

接收器

|

发报机

|

比赛

| | --- | --- | --- | | 一个过滤器action = "com.xyz.ACTION1" | action = "com.xyz.ACTION1" | 是 | | 一个过滤器action = "com.xyz.ACTION1" | action = "com.xyz.ACTION2" | 不 | | 两个过滤器action = "com.xyz.ACTION1"``action = "com.xyz.ACTION2" | action = "com.xyz.ACTION1" | 是 | | 两个过滤器action = "com.xyz.ACTION1"``action = "com.xyz.ACTION2" | action = "com.xyz.ACTION3" | 不 |

除了动作,一个类别说明符可以用来限制一个意图过滤器。我们在第三章中列出了几个预定义的类别,但是你也可以定义自己的类别。就像对于动作一样,对于你自己的类别你应该遵循将你的应用的包名加到你的类别名前面的命名惯例。一旦在意图匹配过程中发现动作中的匹配,发送者声明的所有类别也必须出现在接收者的意图过滤器中,以使匹配更进一步。

  • 一旦意图过滤器内的动作匹配广播,并且过滤器也包含类别列表,则只有这样的广播将匹配发送者指定的类别全部包含在接收者的类别列表中的过滤器。

5-2 显示了一些例子(只有一个过滤器;如果有几个过滤器,匹配以“或”为基础。

表 5-2

类别匹配

|

接收器动作

|

接收者类别

|

发报机

|

比赛

| | --- | --- | --- | --- | | com.xyz.ACT1 | com.xyz.cate1 | action = "com.xyz.ACT1" | 是 | | com.xyz.ACT1 |   | action = "com.xyz.ACT1"``categ = "com.xyz.cate1" | 不 | | com.xyz.ACT1 | com.xyz.cate1 | action = "com.xyz.ACT1"``categ = "com.xyz.cate1" | 是 | | com.xyz.ACT1 | com.xyz.cate1 | action = "com.xyz.ACT1"``categ = "com.xyz.cate1"``categ = "com.xyz.cate2" | 不 | | com.xyz.ACT1 | com.xyz.cate1``com.xyz.cate2 | action = "com.xyz.ACT1"``categ = "com.xyz.cate1"``categ = "com.xyz.cate2" | 是 | | com.xyz.ACT1 | com.xyz.cate1``com.xyz.cate2 | action = "com.xyz.ACT1"``categ = "com.xyz.cate1" | 是 | | com.xyz.ACT1 | any | action = "com.xyz.ACT2"``categ = any | 不 |

第三,数据和类型说明符允许过滤数据类型。这种说明符是下列说明符之一:

  • 类型:MIME 类型,例如"text/html""text/plain"

  • 数据:某数据 URI,例如“ http://xyz.com/type1 "

  • 数据类型:两者都有

这里,data元素允许通配符匹配。

  • 假定的 动作 类别 匹配:如果发送方指定的 MIME 类型包含在接收方允许的 MIME 类型列表中,则类型过滤器元素匹配。

  • 假定的 动作 类别 匹配: A 数据过滤元素匹配如果发送方指定的数据 URI 匹配接收方允许的数据 URIs 列表中的任何一个(通配符匹配可能适用)。

  • 假定 动作 类别 匹配:如果 MIME 类型和数据 URI 匹配,即包含在接收者的指定列表中,则数据和类型过滤元素匹配。

5-3 显示了一些例子(只有一个过滤器;如果有几个过滤器,匹配以“或”为基础。

表 5-3

数据匹配

|

接收器类型

|

接收者 URI

。* =任何字符串

|

发报机

|

比赛

| | --- | --- | --- | --- | | text/html |   | type = "text/html" | 是 | | text/html``text/plain |   | type = "text/html" | 是 | | text/html``text/plain |   | type = "image/jpeg" | 不 | |   | http://a.b.c/xyz | data = "http://a.b.c/xyz" | 是 | |   | http://a.b.c/xyz | data = "http://a.b.c/qrs" | 不 | |   | http://a.b.c/xyz/.* | data = "http://a.b.c/xyz/3" | 是 | |   | http://.*/xyz | data = "http://a.b.c/xyz" | 是 | |   | http://.*/xyz | data = "http://a.b.c/qrs" | 不 | | text/html | http://a.b.c/xyz/.* | type = "text/html"``data = "http://a.b.c/xyz/1" | 是 | | text/html | http://a.b.c/xyz/.* | type = "image/jpeg"``data = "http://a.b.c/xyz/1" | 不 |

主动或等待接听

应用必须处于哪个状态才能接收隐式广播?如果我们希望广播接收器只在系统中注册,并且只在匹配的广播到达时按需启动,则必须在应用的清单文件中指定侦听器。然而,对于隐式广播,这不能自由地进行。它仅适用于预定义的系统广播,如在线文本指南的“系统广播”一节所列。

注意

对清单中指定的隐式意图过滤器的这种限制是在 Android 8.0 (API 级别 26)中引入的。在此之前,可以在清单文件中指定任何隐式过滤器。

但是,如果您从应用内部以编程方式启动广播侦听器,并且该应用正在运行,则您可以根据需要定义任意数量的隐式广播侦听器,并且对于广播来自系统、您的应用还是其他应用没有任何限制。同样,对于可用的动作类别名称也没有限制。

因为监听引导完成事件包含在清单文件中允许的监听器列表中,所以您可以自由地启动应用作为活动或服务,并且在这些应用中,您可以注册任何隐式监听器。但这意味着从 Android 8.0 开始,你可以合法地绕过这些限制。只是要注意,如果出现资源短缺,这类应用可能会被 Android OS 杀死,所以你必须采取适当的预防措施。

发送隐式广播

要准备发送隐式广播,请按如下方式指定操作、类别、数据和额外数据:

val intent = Intent()
intent.action = "de.pspaeth.myapp.DO_STH"
intent.addCategory("de.pspaeth.myapp.CATEG1")
intent.addCategory("de.pspaeth.myapp.CATEG2")
// ... more categories
intent.type = "text/html"
intent.data = Uri.parse("content://myContent")
intent.putExtra("EXTRA_KEY", "extraVal")
intent.flags = ...

只有动作是强制性的;所有其他的都是可选的。现在要发送广播,您需要为远程广播编写以下代码:

sendBroadcast(intent)

对于本地广播,您应该这样写:

LocalBroadcastManager.getInstance(this).
      sendBroadcast(intent)

this必须是Context或其子类;如果代码来自一个活动或服务类内部,它将完全像这里显示的那样工作。

对于远程消息,还有一种变体,每次向一个适用的接收器发送广播。

...
sendOrderedBroadcast(...)
...

这使得接收者顺序地获得消息,并且每个接收者可以取消,通过使用BroadcastReceiver.abortBroadcast()将消息转发给队列中的下一个接收者。

接收隐式广播

要接收隐式广播,对于一组有限的广播类型(参见在线文本指南的“系统意图过滤器”一节),您可以在AndroidManifest.xml中指定一个BroadcastListener,如下所示:

<application ...>
  ...
  <receiver android:name=".MyReceiver">
    <intent-filter>
      <action android:name=
          "com.xyz.myapp.DO_STH" />
      <category android:name=
          "android.intent.category.DEFAULT"/>
      <category android:name=
          "com.xyz.myapp.MY_CATEG"/>
      <data android:scheme="http"
            android:port="80"
            android:host="com.xyz"
            android:path="items/7"
            android:mimeType="text/html" />
      </intent-filter>
    </receiver>
</application>

这里显示的<data>元素只是一个例子;参见第 3 章了解所有可能性。

与此相反,向代码中添加隐式广播的编程侦听器是不受限制的。

class MainActivity : AppCompatActivity() {
  private var bcReceiver:BroadcastReceiver? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    bcReceiver = MyReceiver()
    val ifi:IntentFilter =
    IntentFilter("de.pspaeth.myapp.DO_STH")
    registerReceiver(bcReceiver, ifi)
  }

  override fun onDestroy() {
    super.onDestroy()
    // ...
    unregisterReceiver(bcReceiver)
  }
}

MyReceiver是类android.content.BroadcastReceiver的一个实现。

收听系统广播

要收听系统广播,请参阅在线文本指南的“系统广播”一节中的列表。您可以像前面展示的那样使用编程注册。对于它们中的大多数,您不能使用自 Android 8.0 (API 级别 26)以来强加的后台执行限制的清单注册方法。但是,对于其中的一些,您也可以使用清单文件来指定侦听器。

  • ACTION_LOCKED_BOOT_COMPLETEDACTION_BOOT_COMPLETED:

    应用可能需要这些来安排作业、警报等等。

  • ACTION_USER_INITIALIZE"android.intent.action.USER_ADDED""android.intent.action.USER_REMOVED":

    这些受特权权限的保护,所以用例是有限的。

  • "android.intent.action.TIME_SET"ACTION_TIMEZONE_CHANGEDACTION_NEXT_ALARM_CLOCK_CHANGED:

    这些都是时钟 app 需要的。

  • ACTION_LOCALE_CHANGED:

    区域设置已更改,发生这种情况时,应用可能需要更新其数据。

  • ACTION_USB_ACCESSORY_ATTACHEDACTION_USB_ACCESSORY_DETACHEDACTION_USB_DEVICE_ATTACHEDACTION_USB_DEVICE_DETACHED:

    这些都是 USB 相关的事件。

  • ACTION_CONNECTION_STATE_CHANGEDACTION_ACL_ CONNECTEDACTION_ACL_DISCONNECTED:

    这些是蓝牙事件。

  • ACTION_CARRIER_CONFIG_CHANGED TelephonyIntents

    ACTION_*_SUBSCRIPTION_CHANGED,"TelephonyIntents. SECRET_CODE_ACTION":

    OEM 电话应用可能需要接收这些广播。

  • LOGIN_ACCOUNTS_CHANGED_ACTION:

    一些应用需要这一点来为新帐户和更改的帐户设置预定操作。

  • ACTION_PACKAGE_DATA_CLEARED:

    操作系统设置应用清除数据;一个正在运行的应用可能对此感兴趣。

  • ACTION_PACKAGE_FULLY_REMOVED:

    如果某些应用被卸载并且其数据被移除,可能需要通知相关应用。

  • ACTION_NEW_OUTGOING_CALL:

    这会拦截呼出的电话。

  • ACTION_DEVICE_OWNER_CHANGED:

    一些应用可能需要接收此消息,以便知道设备的安全状态已更改。

  • ACTION_EVENT_REMINDER:

    这是由日历供应器发送的,用于向日历应用发布事件提醒。

    ACTION_MEDIA_MOUNTED

    ACTION_MEDIA_CHECKINGACTION_MEDIA_UNMOUNTEDACTION_MEDIA_EJECTACTION_MEDIA_UNMOUNTABLEACTION_MEDIA_REMOVEDACTION_MEDIA_BAD_REMOVAL:

    应用可能需要了解用户与设备的物理交互。

  • SMS_RECEIVED_ACTIONWAP_PUSH_RECEIVED_ACTION:

    这些都是短信接收应用所需要的。

增加广播的安全性

广播消息的安全性由权限系统处理,该系统将在第 7 章中得到更详细的处理。

在下面几节中,我们将区分显式和隐式广播。

保护明确的广播

对于非本地广播(即不使用LocalBroadcastManager),权限可以在双方指定,接收者和发送者。对于后者,广播发送方法具有重载版本,包括一个权限说明符:

...
val intent = Intent(this, MyReceiver::class.java)
...
sendBroadcast(intent, "com.xyz.theapp.PERMISSION1")
...

这表示向受com.xyz.theapp.PERMISSION1保护的接收器发送广播。当然,您应该在这里编写自己的包名,并使用适当的权限名。

相反,在没有许可说明的情况下发送广播可能会针对具有和不具有许可保护的接收者:

...
val intent = Intent(this, MyReceiver::class.java)
...
sendBroadcast(intent)
...

这意味着在发送方指定权限并不意味着告诉接收方发送方受到任何形式的保护。

为了向接收方添加权限,我们首先需要在应用级别的AndroidManifest.xml中声明使用它。

<manifest ...>
    <uses-permission android:name=
          "com.xyz.theapp.PERMISSION1"/>
    ...
    <application ...

接下来,我们将它显式地添加到同一个清单文件中的 receiver 元素中。

<receiver android:name=".MyReceiver"
        android:permission="com.xyz.theapp.PERMISSION1">
    <intent-filter>
        <action android:name=
                    "com.xyz.theapp.DO_STH" />
    </intent-filter>
</receiver>

这里,MyReceiverandroid.content.BroadcastReceiver的一个实现。

第三,由于这是一个自定义权限,您必须在清单文件中声明它自己。

<manifest ...>
    <permission android:name=
          "com.xyz.theapp.PERMISSION1"/>
    ...

<permission>允许更多的属性;请参阅联机文本指南中的“清单顶级条目”一节,了解有关保护级别的更多信息。第 7 章详细解释了它的细节和含义。

对于非定制权限,不需要使用<permission>元素。

警告

当您尝试发送广播时,在发送方指定权限而在接收方没有匹配的权限会自动失败。也没有日志记录条目,所以要小心发送端的权限。

如果通过LocalBroadcastManager使用本地广播,则不能在发送方或接收方指定权限。

保护隐式广播

像非本地显式广播一样,隐式广播中的权限可以在广播发送方和接收方指定。在发送方,您应该编写以下内容:

val intent = Intent()
intent.action = "de.pspaeth.myapp.DO_STH"
// ... more intent coordinates
sendBroadcast(intent, "com.xyz.theapp.PERMISSION1")

这表示向受com.xyz.theapp.PERMISSION1额外保护的所有匹配接收器发送广播。当然,您应该在这里写下自己的包名,并使用适当的权限名。至于用于隐式广播的通常的发送者-接收者匹配过程,添加许可种类作为附加的匹配标准,因此如果有几个接收者候选查看意图过滤器,为了实际接收该广播,只有那些额外提供该许可标志的将被挑选出来。

隐式广播需要注意的另一件事是在AndroidManifest.xml中指定权限用法。因此,为了使该发件人能够使用权限,请将以下内容添加到清单文件中:

 <uses-permission android:name="com.xyz.theapp.
PERMISSION1"/>

和露骨的广播一样。在没有许可说明的情况下发送广播可以寻址具有和不具有许可保护的接收器。

...
sendBroadcast(intent)
...

这意味着在发送方指定权限不应该告诉接收方发送方受到任何形式的保护。

为了使接收器能够获得这样的广播,必须将许可添加到代码中,如下所示:

private var bcReceiver: BroadcastReceiver? = null

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  ...
  bcReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?,
          intent: Intent?) {
      // do s.th. when receiving...
    }
  }
  val ifi: IntentFilter =
         IntentFilter("de.pspaeth.myapp.DO_STH")
  registerReceiver(bcReceiver, ifi,
        "com.xyz.theapp.PERMISSION1", null)
  }

  override fun onDestroy() {
    super.onDestroy()
    unregisterReceiver(bcReceiver)
}

此外,您必须在接收方的清单文件中定义权限并声明使用它。

...
<uses-permission android:name=
      "com.xyz.theapp.PERMISSION1" />
<permission android:name=
      "com.xyz.theapp.PERMISSION1" />
...

同样,对于非定制权限,您不需要使用<permission>元素。有关权限的更多信息,请参见第 7 章

注意

作为提高安全性的附加手段,在适用的情况下,您可以使用Intent.setPackage()来限制可能的接收者。

从命令行发送广播

对于可以通过 Android 调试桥 (ADB)连接的设备,可以在开发 PC 上使用 shell 命令发送广播消息(参见第 18 章)。这里有一个向包de.pspaeth.simplebroadcast的专用接收者MyReceiver发送动作de.pspaeth.myapp.DO_STH的例子(这是一个明确的广播消息):

./adb shell am broadcast -a de.pspaeth.myapp.DO_STH \
      de.pspaeth.simplebroadcast MyReceiver

要获得以这种方式发送广播的完整概要,可以使用如下的 shell:

./adb shell
am

该命令将向您展示使用该am命令创建广播消息和做其他事情的所有可能性。

广播随笔

以下是一些关于广播的附加信息:

  • 您还可以在回调方法onPause()onResume()中注册和注销编程管理的接收器。显然,与使用onCreate() / onDestroy()对相比,注册和注销会更频繁。

  • 一个当前正在执行的onReceive()方法会将进程优先级升级到“前台”级别,防止 Android OS 杀死接收进程。只有在极度资源短缺的情况下,这种情况才会发生。

  • 如果您在onReceive()中有长时间运行的进程,您可能会想到在后台线程上运行它们,提前完成onReceive()。但是由于完成onReceive()后进程优先级会恢复到正常水平,所以你的后台进程被杀的可能性比较大,坏了你的 app。你可以通过使用Context.goAsync()然后启动一个AsyncTask来防止这种情况(在最后你必须在从goAsync()获得的PendingResult对象上调用finish()来最终释放资源),或者你可以使用一个JobScheduler

  • 自定义权限,就像我们在“保护隐式广播”一节中使用的一样,在安装应用时注册。因此,定义自定义权限的应用必须在应用使用它们之前安装。

  • 小心通过隐式广播发送敏感信息。潜在的恶意应用也可能试图接收它们。至少,您可以通过在发送方指定权限来保护广播。

  • 为了清晰起见,也为了不与其他应用混淆,请始终使用名称空间作为广播操作和权限名称。

  • 避免从广播开始活动。这违背了 Android 的可用性原则。