意图和意图过滤器

一个 Intent 是一个消息对象,您可以使用它来请求另一个应用组件执行某个操作。虽然意图通过多种方式促进了组件之间的通信,但有三个基本用例:

  • 启动活动

    一个 Activity 表示应用中的一个屏幕。您可以通过将 Intent 传递给 startActivity() 来启动 Activity 的新实例。Intent 描述要启动的活动并携带任何必要的数据。

    如果您希望在活动完成时接收结果,请调用 startActivityForResult()。您的活动会在其 onActivityResult() 回调中以单独的 Intent 对象接收结果。如需了解更多信息,请参阅活动指南。

  • 启动服务

    一个 Service 是一个在后台执行操作而没有用户界面的组件。在 Android 5.0(API 级别 21)及更高版本中,您可以使用 JobScheduler 启动服务。有关 JobScheduler 的更多信息,请参阅其 API 参考文档

    对于 Android 5.0(API 级别 21)之前的版本,您可以通过使用 Service 类的A方法启动服务。您可以通过将 Intent 传递给 startService() 来启动服务以执行一次性操作(例如下载文件)。Intent 描述要启动的服务并携带任何必要的数据。

    如果服务设计有客户端-服务器接口,您可以通过将 Intent 传递给 bindService() 从另一个组件绑定到服务。如需了解更多信息,请参阅服务指南。

  • 发送广播

    广播是任何应用都可以接收的消息。系统为系统事件(例如系统启动或设备开始充电时)发送各种广播。您可以通过将 Intent 传递给 sendBroadcast()sendOrderedBroadcast() 来向其他应用发送广播。

本页的其余部分解释了意图的工作原理以及如何使用它们。有关相关信息,请参阅与其他应用交互共享内容

意图类型

意图有两种类型:

  • 显式意图通过指定完整的 ComponentName 来指定哪个应用的哪个组件将满足意图。您通常会使用显式意图在自己的应用中启动一个组件,因为您知道要启动的活动或服务的类名。例如,您可能会在用户操作后在应用中启动一个新活动,或者启动一个服务在后台下载文件。
  • 隐式意图不指定特定组件的名称,而是声明要执行的通用操作,这允许来自另一个应用的组件来处理它。例如,如果您想在地图上向用户显示一个位置,您可以使用隐式意图请求另一个有能力的应用在地图上显示指定位置。

图 1 展示了启动活动时意图是如何使用的。当 Intent 对象显式地指定了一个特定的活动组件时,系统会立即启动该组件。

图 1. 隐式意图如何通过系统传递以启动另一个活动:[1] Activity A 创建一个带有操作描述的 Intent,并将其传递给 startActivity()[2] Android 系统搜索所有应用中与该意图匹配的意图过滤器。找到匹配项后,[3] 系统通过调用其 onCreate() 方法并将其传递 Intent 来启动匹配的活动(Activity B)。

当您使用隐式意图时,Android 系统通过比较意图的内容与设备上其他应用的清单文件中声明的意图过滤器来找到要启动的适当组件。如果意图与意图过滤器匹配,系统将启动该组件并向其传递 Intent 对象。如果有多个意图过滤器兼容,系统会显示一个对话框,以便用户选择要使用的应用。

意图过滤器是应用清单文件中的一个表达式,它指定了组件希望接收的意图类型。例如,通过为活动声明意图过滤器,您可以使其他应用能够通过某种意图直接启动您的活动。同样,如果您没有为活动声明任何意图过滤器,那么它只能通过显式意图启动。

注意: 为确保您的应用安全,启动 Service 时务必使用显式意图,并且不要为您的服务声明意图过滤器。使用隐式意图启动服务存在安全隐患,因为您无法确定哪个服务会响应意图,并且用户无法看到哪个服务启动。从 Android 5.0(API 级别 21)开始,如果您使用隐式意图调用 bindService(),系统会抛出异常。

构建意图

一个 Intent 对象携带 Android 系统用于确定要启动哪个组件的信息(例如精确的组件名称或应该接收意图的组件类别),以及接收组件用于正确执行操作的信息(例如要执行的操作和要操作的数据)。

一个 Intent 中包含的主要信息如下:

组件名称
要启动的组件的名称。

这是可选的,但它是使意图显式的关键信息,这意味着意图只能传递给由组件名称定义的应用程序组件。如果没有组件名称,意图就是隐式的,系统会根据其他意图信息(例如动作、数据和类别——下面会描述)来决定哪个组件应该接收该意图。如果您需要启动应用中的特定组件,您应该指定组件名称。

注意: 启动 Service 时,务必指定组件名称。否则,您无法确定哪个服务会响应意图,并且用户无法看到哪个服务启动。

这个 Intent 的字段是一个 ComponentName 对象,您可以通过目标组件的完全限定类名(包括应用的包名,例如 com.example.ExampleActivity)来指定。您可以使用 setComponent()setClass()setClassName()Intent 构造函数设置组件名称。

操作
一个字符串,指定要执行的通用操作(例如查看选择)。

在广播意图的情况下,这是发生并被报告的动作。该动作在很大程度上决定了意图的其余部分是如何构建的——特别是数据和附加信息中包含的信息。

您可以指定自己的操作以供应用内的意图使用(或供其他应用调用您应用中的组件),但您通常指定由 Intent 类或其他框架类定义的操作常量。以下是一些用于启动活动的常见操作:

ACTION_VIEW
当您有一些信息可以显示给用户时,例如要在图库应用中查看的照片或要在地图应用中查看的地址,请在意图中使用此操作与 startActivity() 一起使用。
ACTION_SEND
也称为共享意图,当您有一些数据可以供用户通过其他应用(例如电子邮件应用或社交共享应用)共享时,您应该在意图中使用此操作与 startActivity() 一起使用。

有关定义通用操作的更多常量,请参阅 Intent 类参考。其他操作在 Android 框架的其他地方定义,例如 Settings 中定义了打开系统设置应用中特定屏幕的操作。

您可以使用 setAction()Intent 构造函数为意图指定操作。

如果您定义自己的操作,请务必在前面加上您应用的软件包名称作为前缀,如下例所示:

Kotlin

const val ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"

Java

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
数据
引用要操作的数据的 URI(一个 Uri 对象)和/或该数据的 MIME 类型。提供的数据类型通常由意图的操作决定。例如,如果操作是 ACTION_EDIT,则数据应包含要编辑的文档的 URI。

创建意图时,除了 URI,通常指定数据类型(其 MIME 类型)也很重要。例如,能够显示图像的活动可能无法播放音频文件,即使 URI 格式可能相似。指定数据的 MIME 类型有助于 Android 系统找到接收意图的最佳组件。然而,MIME 类型有时可以从 URI 推断出来——特别是当数据是 content: URI 时。 content: URI 表示数据位于设备上并由 ContentProvider 控制,这使得数据 MIME 类型对系统可见。

要仅设置数据 URI,请调用 setData()。要仅设置 MIME 类型,请调用 setType()。如有必要,您可以使用 setDataAndType() 显式设置两者。

注意: 如果您想同时设置 URI 和 MIME 类型,不要调用 setData()setType(),因为它们会使对方的值为空。始终使用 setDataAndType() 来同时设置 URI 和 MIME 类型。

类别
一个字符串,包含有关应该处理意图的组件类型的附加信息。意图中可以放置任意数量的类别描述,但大多数意图不需要类别。以下是一些常见类别:
CATEGORY_BROWSABLE
目标活动允许自身通过网页浏览器启动,以显示链接引用的数据,例如图像或电子邮件消息。
CATEGORY_LAUNCHER
该活动是任务的初始活动,并列在系统的应用程序启动器中。

请参阅 Intent 类描述以获取完整的类别列表。

您可以使用 addCategory() 指定类别。

上面列出的这些属性(组件名称、操作、数据和类别)代表了意图的定义特征。通过读取这些属性,Android 系统能够解析它应该启动哪个应用组件。但是,意图可以携带不影响其解析为应用组件的附加信息。意图还可以提供以下信息:

附加数据
键值对,携带完成请求操作所需的额外信息。就像某些操作使用特定类型的数据 URI 一样,某些操作也使用特定的额外数据。

您可以使用各种 putExtra() 方法添加额外数据,每个方法都接受两个参数:键名和值。您还可以创建一个包含所有额外数据的 Bundle 对象,然后使用 putExtras()Bundle 插入到 Intent 中。

例如,当创建使用 ACTION_SEND 发送电子邮件的意图时,您可以使用 EXTRA_EMAIL 键指定收件人,并使用 EXTRA_SUBJECT 键指定主题

Intent 类为标准化数据类型指定了许多 EXTRA_* 常量。如果您需要声明自己的额外键(用于您的应用接收的意图),请务必在前面加上您应用的软件包名称作为前缀,如下例所示:

Kotlin

const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"

Java

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

注意:发送您期望另一个应用接收的意图时,请勿使用 ParcelableSerializable 数据。如果某个应用尝试访问 Bundle 对象中的数据但无法访问被 Parcel 或序列化的类,系统会引发 RuntimeException

标志
标志定义在 Intent 类中,作为意图的元数据。这些标志可以指示 Android 系统如何启动活动(例如,活动应该属于哪个任务)以及启动后如何处理它(例如,它是否属于最近活动列表)。

如需了解更多信息,请参阅 setFlags() 方法。

显式意图示例

显式意图是您用来启动特定应用组件(例如应用中特定活动或服务)的意图。要创建显式意图,请为 Intent 对象定义组件名称——所有其他意图属性都是可选的。

例如,如果您在应用中构建了一个名为 DownloadService 的服务,旨在从网络下载文件,您可以使用以下代码启动它:

Kotlin

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
val downloadIntent = Intent(this, DownloadService::class.java).apply {
    data = Uri.parse(fileUrl)
}
startService(downloadIntent)

Java

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Intent(Context, Class) 构造函数提供应用 Context 和组件 Class 对象。因此,此意图显式启动应用中的 DownloadService 类。

有关构建和启动服务的更多信息,请参阅服务指南。

隐式意图示例

隐式意图指定一个可以调用设备上任何能够执行该操作的应用的操作。当您的应用无法执行该操作,但其他应用可能可以并且您希望用户选择要使用的应用时,使用隐式意图非常有用。

例如,如果您有希望用户与他人共享的内容,请使用 ACTION_SEND 操作创建一个意图,并添加指定要共享内容的附加信息。当您使用该意图调用 startActivity() 时,用户可以选择一个应用来共享内容。

Kotlin

// Create the text message with a string.
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = "text/plain"
}

// Try to invoke the intent.
try {
    startActivity(sendIntent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

Java

// Create the text message with a string.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// Try to invoke the intent.
try {
    startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

调用 startActivity() 时,系统会检查所有已安装的应用,以确定哪些应用可以处理这种类型的意图(具有 ACTION_SEND 操作并携带“text/plain”数据的意图)。如果只有一个应用可以处理它,该应用会立即打开并接收意图。如果没有其他应用可以处理它,您的应用可以捕获发生的 ActivityNotFoundException。如果有多个活动接受该意图,系统会显示一个对话框,如图 2 所示,以便用户选择要使用的应用。

有关启动其他应用的更多信息,请参阅将用户发送到另一个应用的指南。

图 2. 一个选择器对话框。

强制使用应用选择器

当有多个应用响应您的隐式意图时,用户可以选择使用哪个应用,并将其设为该操作的默认选择。选择默认应用的功能在执行用户可能每次都想使用相同应用的操作时很有用,例如打开网页(用户通常只喜欢一个网页浏览器)。

但是,如果有多个应用可以响应意图,并且用户可能每次都想使用不同的应用,则应明确显示选择器对话框。选择器对话框会要求用户选择要用于该操作的应用(用户无法为该操作选择默认应用)。例如,当您的应用使用 ACTION_SEND 操作执行“共享”时,用户可能希望根据其当前情况使用不同的应用进行共享,因此您应始终使用选择器对话框,如图 2 所示。

要显示选择器,请使用 createChooser() 创建一个 Intent 并将其传递给 startActivity(),如以下示例所示。此示例将显示一个对话框,其中包含响应传递给 createChooser() 方法的意图的应用列表,并使用提供的文本作为对话框标题。

Kotlin

val sendIntent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title: String = resources.getString(R.string.chooser_title)
// Create intent to show the chooser dialog
val chooser: Intent = Intent.createChooser(sendIntent, title)

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

Java

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

检测不安全的意图启动

您的应用可能会启动意图以在应用内部组件之间导航,或代表另一个应用执行操作。为了提高平台安全性,Android 12(API 级别 31)及更高版本提供了一个调试功能,当您的应用执行不安全的意图启动时会发出警告。例如,您的应用可能会执行不安全的嵌套意图启动,嵌套意图是作为额外信息传递给另一个意图的意图。

如果您的应用同时执行以下两项操作,系统会检测到不安全的意图启动,并发生 StrictMode 违规:

  1. 您的应用从已传递意图的附加信息中解封嵌套意图。
  2. 您的应用立即使用该嵌套意图启动应用组件,例如将意图传递给 startActivity()startService()bindService()

有关如何识别此情况并对您的应用进行更改的更多详细信息,请阅读 Medium 上的博客文章Android 嵌套意图

检查不安全的意图启动

要在您的应用中检查不安全的意图启动,请在配置 VmPolicy 时调用 detectUnsafeIntentLaunch(),如以下代码片段所示。如果您的应用检测到 StrictMode 违规,您可能希望停止应用执行以保护潜在的敏感信息。

Kotlin

fun onCreate() {
    StrictMode.setVmPolicy(VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build())
}

Java

protected void onCreate() {
    StrictMode.setVmPolicy(new VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build());
}

更负责任地使用意图

为了最大程度地降低不安全意图启动和 StrictMode 违规的可能性,请遵循以下最佳实践。

仅在意图中复制必要的附加信息,并执行任何必要的清理和验证。您的应用可能会将一个意图的附加信息复制到另一个用于启动新组件的意图。当您的应用调用 putExtras(Intent)putExtras(Bundle) 时会发生这种情况。如果您的应用执行了这些操作之一,请仅复制接收组件预期的附加信息。如果接收复制的另一个意图启动了一个未导出的组件,请在将其复制到启动该组件的意图之前,清理并验证附加信息。

不要不必要地导出您的应用组件。例如,如果您打算使用内部嵌套意图启动应用组件,请将其组件的 android:exported 属性设置为 false

使用 PendingIntent 代替嵌套意图。这样,当另一个应用解封其包含的 IntentPendingIntent 时,该应用可以使用您的应用的身份启动 PendingIntent。此配置允许其他应用安全地启动您应用中的任何组件,包括未导出的组件。

图 2 中的图表显示了系统如何将控制权从您的(客户端)应用传递给另一个(服务)应用,然后再传回您的应用:

  1. 您的应用创建一个意图,调用另一个应用中的活动。在该意图中,您添加一个 PendingIntent 对象作为额外信息。此待处理意图调用您应用中的一个组件;该组件未导出。
  2. 收到您应用的意图后,另一个应用会提取嵌套的 PendingIntent 对象。
  3. 另一个应用调用 PendingIntent 对象上的 send() 方法。
  4. 将控制权传回您的应用后,系统会使用您应用的上下文调用待处理意图。

图 2. 使用嵌套的待处理意图时应用间通信的示意图。

接收隐式意图

要声明您的应用可以接收哪些隐式意图,请在您的清单文件中,为每个应用组件声明一个或多个带有 <intent-filter> 元素的意图过滤器。每个意图过滤器都根据意图的动作、数据和类别指定它接受的意图类型。系统仅在意图能够通过您的一个意图过滤器时,才会将隐式意图传递给您的应用组件。

注意: 显式意图总是传递给它的目标,无论组件声明了任何意图过滤器。

一个应用组件应为其可以执行的每个独特任务声明单独的过滤器。例如,图像图库应用中的一个活动可能有两个过滤器:一个过滤器用于查看图像,另一个过滤器用于编辑图像。当活动启动时,它会检查 Intent,并根据 Intent 中的信息决定如何行为(例如是否显示编辑器控件)。

每个意图过滤器都由应用清单文件中的 <intent-filter> 元素定义,嵌套在相应的应用组件(例如 <activity> 元素)中。

在每个包含 <intent-filter> 元素的应用组件中,明确设置 android:exported 的值。此属性指示应用组件是否可供其他应用访问。在某些情况下,例如意图过滤器包含 LAUNCHER 类别的活动,将此属性设置为 true 会很有用。否则,将其设置为 false 更安全。

警告: 如果您的应用中的活动、服务或广播接收器使用意图过滤器并且没有明确设置 android:exported 的值,您的应用将无法在运行 Android 12 或更高版本的设备上安装。

<intent-filter> 内部,您可以使用以下三个元素中的一个或多个来指定要接受的意图类型:

<action>
声明接受的意图动作,在 name 属性中。该值必须是动作的字面字符串值,而不是类常量。
<data>
声明接受的数据类型,使用一个或多个属性来指定数据 URI(schemehostportpath)和 MIME 类型的各个方面。
<category>
声明接受的意图类别,在 name 属性中。该值必须是动作的字面字符串值,而不是类常量。

注意: 要接收隐式意图,您必须在意图过滤器中包含 CATEGORY_DEFAULT 类别。startActivity()startActivityForResult() 方法将所有意图视为声明了 CATEGORY_DEFAULT 类别。如果您未在意图过滤器中声明此类别,则没有隐式意图会解析到您的活动。

例如,这是一个活动声明,其中包含一个意图过滤器,用于在数据类型为文本时接收 ACTION_SEND 意图:

<activity android:name="ShareActivity" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

您可以创建包含一个以上 <action><data><category> 实例的过滤器。如果这样做,您需要确保组件可以处理这些过滤器元素的任何和所有组合。

当您想处理多种意图,但仅限于动作、数据和类别类型的特定组合时,您需要创建多个意图过滤器。

隐式意图根据三个方面与意图过滤器进行比较:意图的动作、数据和类别。要传递给组件,意图必须通过所有三个测试。如果它未能匹配其中任何一个,Android 系统就不会将意图传递给该组件。但是,由于组件可能具有多个意图过滤器,因此未能通过组件一个过滤器的意图可能会通过另一个过滤器。有关系统如何解析意图的更多信息,请参阅下面关于意图解析的部分。

注意: 使用意图过滤器不是阻止其他应用启动您的组件的安全方式。尽管意图过滤器限制了组件只能响应某些类型的隐式意图,但如果开发者确定您的组件名称,其他应用仍可能通过使用显式意图启动您的应用组件。如果只有您自己的应用才能启动您的某个组件非常重要,请不要在清单中声明意图过滤器。相反,将该组件的 exported 属性设置为 "false"

同样,为避免意外运行其他应用的 Service,请务必使用显式意图启动您自己的服务。

注意: 对于所有活动,您必须在清单文件中声明您的意图过滤器。但是,广播接收器的过滤器可以通过调用 registerReceiver() 进行动态注册。然后您可以使用 unregisterReceiver() 取消注册接收器。这样做允许您的应用仅在应用运行的特定时间段内侦听特定广播。

过滤器示例

为了演示一些意图过滤器行为,以下是一个来自社交共享应用清单文件的示例:

<activity android:name="MainActivity" android:exported="true">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity" android:exported="false">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

第一个活动 MainActivity 是应用的主要入口点——当用户最初使用启动器图标启动应用时打开的活动:

  • ACTION_MAIN 动作表示这是主入口点,不期望任何意图数据。
  • CATEGORY_LAUNCHER 类别表示此活动的图标应放置在系统的应用启动器中。如果 <activity> 元素没有使用 icon 指定图标,则系统将使用来自 <application> 元素的图标。

这两个必须配对使用才能使活动出现在应用启动器中。

第二个活动 ShareActivity 旨在方便共享文本和媒体内容。尽管用户可以通过从 MainActivity 导航到此活动,但他们也可以直接从发出与两个意图过滤器之一匹配的隐式意图的其他应用进入 ShareActivity

注意: MIME 类型 application/vnd.google.panorama360+jpg 是一种特殊数据类型,用于指定全景照片,您可以使用 Google 全景 API 进行处理。

将意图匹配到其他应用的意图过滤器

如果另一个应用以 Android 13(API 级别 33)或更高版本为目标,它只能在您的意图与该应用中 <intent-filter> 元素的动作和类别匹配时才能处理您的应用的意图。如果系统找不到匹配项,它会抛出 ActivityNotFoundException。发送应用必须处理此异常。

同样,如果您更新您的应用使其以 Android 13 或更高版本为目标,则所有源自外部应用的意图只有在该意图与您的应用声明的 <intent-filter> 元素的动作和类别匹配时,才会传递给您应用的导出组件。无论发送应用的目标 SDK 版本如何,都会出现此行为。

在以下情况下,不强制执行意图匹配:

  • 传递给未声明任何意图过滤器的组件的意图。
  • 源自同一应用内的意图。
  • 源自系统的意图;即从“系统 UID”(uid=1000)发送的意图。系统应用包括 system_server 以及将 android:sharedUserId 设置为 android.uid.system 的应用。
  • 源自 root 的意图。

详细了解意图匹配

使用待处理意图

一个 PendingIntent 对象是 Intent 对象的一个包装器。 PendingIntent 的主要目的是授予外部应用程序使用所包含的 Intent 的权限,就像它从您自己的应用程序进程执行一样。

待处理意图的主要用例如下:

就像每个 Intent 对象都设计为由特定类型的应用组件(要么是 Activity,要么是 Service,要么是 BroadcastReceiver)处理一样, PendingIntent 也必须以同样的考量创建。使用待处理意图时,您的应用不会通过调用 startActivity() 等方式执行意图。相反,您必须在创建 PendingIntent 时通过调用相应的创建方法来声明预期的组件类型:

除非您的应用正在接收来自其他应用的待处理意图,否则上述创建 PendingIntent 的方法可能是您唯一需要的 PendingIntent 方法。

每个方法都接受当前应用 Context、您要封装的 Intent,以及一个或多个指定意图应如何使用的标志(例如,意图是否可以多次使用)。

有关使用待处理意图的更多信息,请参阅每个相应用例的文档,例如通知应用小部件 API 指南。

指定可变性

如果您的应用以 Android 12 或更高版本为目标,您必须指定您的应用创建的每个 PendingIntent 对象的mutability。要声明给定的 PendingIntent 对象是可变的还是不可变的,请分别使用 PendingIntent.FLAG_MUTABLEPendingIntent.FLAG_IMMUTABLE 标志。

如果您的应用尝试创建 PendingIntent 对象而未设置任何可变性标志,系统会抛出 IllegalArgumentException,并且以下消息会出现在 Logcat 中:

PACKAGE_NAME: Targeting S+ (version 31 and above) requires that one of \
FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if \
some functionality depends on the PendingIntent being mutable, e.g. if \
it needs to be used with inline replies or bubbles.

尽可能创建不可变待处理意图

在大多数情况下,您的应用应创建不可变的 PendingIntent 对象,如以下代码片段所示。如果 PendingIntent 对象是不可变的,则其他应用无法修改意图以调整调用意图的结果。

Kotlin

val pendingIntent = PendingIntent.getActivity(applicationContext,
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE)

Java

PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE);

然而,某些用例需要可变的 PendingIntent 对象:

  • 支持通知中的直接回复操作。直接回复要求更改与回复关联的 PendingIntent 对象中的剪辑数据。通常,您通过将 FILL_IN_CLIP_DATA 作为标志传递给 fillIn() 方法来请求此更改。
  • 使用 CarAppExtender 实例将通知与 Android Auto 框架关联。
  • 使用 PendingIntent 实例将对话放入气泡中。可变的 PendingIntent 对象允许系统应用正确的标志,例如 FLAG_ACTIVITY_MULTIPLE_TASKFLAG_ACTIVITY_NEW_DOCUMENT
  • 通过调用 requestLocationUpdates() 或类似 API 请求设备位置信息。可变的 PendingIntent 对象允许系统添加表示位置生命周期事件的意图附加信息。这些事件包括位置变化和提供者可用。
  • 使用 AlarmManager 调度闹钟。可变的 PendingIntent 对象允许系统添加 EXTRA_ALARM_COUNT 意图额外数据。此额外数据表示重复闹钟已触发的次数。通过包含此额外数据,意图可以准确地通知应用重复闹钟是否多次触发,例如当设备处于休眠状态时。

如果您的应用创建了一个可变的 PendingIntent 对象,强烈建议您使用显式意图并填充 ComponentName。这样,每当另一个应用调用 PendingIntent 并将控制权传回您的应用时,您的应用中始终启动相同的组件。

在待处理意图中使用显式意图

为了更好地定义其他应用如何使用您的应用的待处理意图,请始终将待处理意图包装在显式意图中。为了遵循此最佳实践,请执行以下操作:

  1. 检查基本意图的动作、包和组件字段是否已设置。
  2. 使用 Android 6.0(API 级别 23)中添加的 FLAG_IMMUTABLE 来创建待处理意图。此标志可防止接收 PendingIntent 的应用填充未填充的属性。如果您的应用的 minSdkVersion22 或更低,您可以使用以下代码同时提供安全性和兼容性:

    if (Build.VERSION.SDK_INT >= 23) {
      // Create a PendingIntent using FLAG_IMMUTABLE.
    } else {
      // Existing code that creates a PendingIntent.
    }

意图解析

当系统接收到用于启动活动的隐式意图时,它会通过根据意图的以下三个方面与意图过滤器进行比较,来搜索最适合该意图的活动:

  • 动作。
  • 数据(包括 URI 和数据类型)。
  • 类别。

以下各节描述了如何根据应用清单文件中意图过滤器的声明,将意图与适当的组件进行匹配。

动作测试

为了指定接受的意图动作,意图过滤器可以声明零个或多个 <action> 元素,如下例所示:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

要通过此过滤器,Intent 中指定的动作必须与过滤器中列出的动作之一匹配。

如果过滤器未列出任何动作,则意图无匹配项,因此所有意图都将失败。但是,如果 Intent 未指定动作,则只要过滤器包含至少一个动作,它就会通过测试。

类别测试

要指定接受的意图类别,意图过滤器可以声明零个或多个 <category> 元素,如下例所示:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

要使意图通过类别测试,Intent 中的每个类别都必须与过滤器中的类别匹配。反之则不必要——意图过滤器可以声明比 Intent 中指定的类别更多的类别,并且 Intent 仍然通过。因此,没有类别的意图总是通过此测试,无论过滤器中声明了哪些类别。

注意: Android 会自动将 CATEGORY_DEFAULT 类别应用于所有传递给 startActivity()startActivityForResult() 的隐式意图。如果您希望您的活动接收隐式意图,它必须在其意图过滤器中包含 "android.intent.category.DEFAULT" 的类别,如前面的 <intent-filter> 示例所示。

数据测试

为了指定接受的意图数据,意图过滤器可以声明零个或多个 <data> 元素,如下例所示:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

每个 <data> 元素都可以指定 URI 结构和数据类型(MIME 媒体类型)。URI 的每个部分都是一个单独的属性:schemehostportpath

<scheme>://<host>:<port>/<path>

以下示例显示了这些属性的可能值:

content://com.example.project:200/folder/subfolder/etc

在此 URI 中,方案是 content,主机是 com.example.project,端口是 200,路径是 folder/subfolder/etc

这些属性中的每一个在 <data> 元素中都是可选的,但存在线性依赖关系:

  • 如果未指定方案,则忽略主机。
  • 如果未指定主机,则忽略端口。
  • 如果方案和主机都未指定,则忽略路径。

当意图中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的 URI 部分进行比较。例如:

  • 如果过滤器仅指定方案,则所有具有该方案的 URI 都将匹配过滤器。
  • 如果过滤器指定了方案和权限但没有路径,则所有具有相同方案和权限的 URI 都会通过过滤器,无论它们的路径如何。
  • 如果过滤器指定了方案、权限和路径,则只有具有相同方案、权限和路径的 URI 才能通过过滤器。

注意: 路径规范可以包含通配符星号 (*) 以仅要求部分匹配路径名。

数据测试会将意图中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。规则如下:

  1. 不包含 URI 也不包含 MIME 类型的意图仅当过滤器未指定任何 URI 或 MIME 类型时才通过测试。
  2. 包含 URI 但不包含 MIME 类型(既不显式也不可从 URI 推断)的意图仅当其 URI 匹配过滤器的 URI 格式且过滤器同样未指定 MIME 类型时才通过测试。
  3. 包含 MIME 类型但不包含 URI 的意图仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时才通过测试。
  4. 一个同时包含 URI 和 MIME 类型(无论是显式声明还是可从 URI 推断)的 intent,只有当其 MIME 类型与过滤器中列出的类型匹配时,才通过 MIME 类型部分的测试。它通过 URI 部分的测试,要么是其 URI 与过滤器中的 URI 匹配,要么是它具有 content:file: URI 且过滤器未指定 URI。换句话说,如果某个组件的过滤器列出了 MIME 类型,则假定该组件支持 content:file: 数据。

注意:如果 intent 指定了 URI 或 MIME 类型,但 <intent-filter> 中没有 <data> 元素,则数据测试将失败。

最后一条规则,即规则 (d),反映了组件能够从文件或内容提供程序获取本地数据的预期。因此,它们的过滤器可以只列出数据类型,而无需显式命名 content:file: 方案。以下示例展示了一个典型情况,其中 <data> 元素告知 Android,该组件可以从内容提供程序获取图像数据并显示它

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

指定数据类型但未指定 URI 的过滤器或许是最常见的,因为大多数可用数据都是由内容提供程序分发的。

另一种常见配置是带有方案和数据类型的过滤器。例如,以下 <data> 元素会告知 Android,该组件可以从网络检索视频数据以执行操作

<intent-filter>
    <data android:scheme="http" android:mimeType="video/*" />
    ...
</intent-filter>

Intent 匹配

Intent 与 intent 过滤器匹配不仅是为了发现要激活的目标组件,也是为了发现设备上组件集的一些信息。例如,主屏幕应用通过查找所有具有指定 ACTION_MAIN 操作和 CATEGORY_LAUNCHER 类别的 intent 过滤器的 activity 来填充应用启动器。只有当 Intent 中的操作和类别与过滤器匹配时(如 IntentFilter 类的文档中所述),匹配才算成功。

您的应用可以像主屏幕应用那样使用 intent 匹配。 PackageManager 具有一组 query...() 方法,用于返回可以接受特定 intent 的所有组件,以及一组类似的 resolve...() 方法,用于确定响应 intent 的最佳组件。例如,queryIntentActivities() 返回可以执行作为参数传递的 intent 的所有 activity 列表,而 queryIntentServices() 返回一个类似的 service 列表。这两种方法都不会激活组件;它们只是列出可以响应的组件。对于广播接收器,有一个类似的方法 queryBroadcastReceivers()