Intent 和 Intent 过滤器

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

  • 启动活动

    一个 Activity 代表应用中的单个屏幕。您可以通过将一个 Intent 传递给 startActivity() 来启动 Activity 的新实例。该 Intent 描述了要启动的活动并承载任何必要的數據。

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

  • 启动服务

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

    对于早于 Android 5.0(API 级别 21)的版本,您可以使用 Service 类的

    如果服务是使用客户端-服务器接口设计的,则可以通过将一个 Intent 传递给 bindService() 从另一个组件绑定到该服务。有关更多信息,请参见 服务 指南。

  • 发送广播

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

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

意图类型

意图有两种类型:

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

图 1 显示了启动活动时如何使用意图。当 Intent 对象显式命名特定活动组件时,系统会立即启动该组件。

图 1. 隐式意图如何通过系统传递以启动另一个活动:[1] 活动 A 创建一个带有操作描述的 Intent,并将其传递给 startActivity()[2] Android 系统会搜索所有应用程序,查找与意图匹配的意图过滤器。找到匹配项后,[3] 系统会通过调用其 onCreate() 方法并向其传递 Intent 来启动匹配的活动(活动 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
目标活动允许 web 浏览器启动它以显示链接引用的数据,例如图像或电子邮件消息。
CATEGORY_LAUNCHER
该活动是任务的初始活动,它列在系统的应用程序启动器中。

有关类别的完整列表,请参见 Intent 类描述。

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

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

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

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

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

Intent 类为标准化数据类型指定了许多 EXTRA_* 常量。如果您需要声明自己的额外键(用于您的应用接收的 Intent),请确保在键名中包含您的应用包名作为前缀,如下例所示

Kotlin

const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"

Java

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

注意:不要在发送您期望其他应用接收的 Intent 时使用 ParcelableSerializable 数据。如果应用尝试访问 Bundle 对象中的数据,但没有访问序列化类的权限,系统会引发 RuntimeException

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

有关更多信息,请参阅 setFlags() 方法。

显式 Intent 示例

显式 Intent 用于启动特定的应用组件,例如您应用中的特定 Activity 或服务。要创建显式 Intent,请为 Intent 对象定义组件名称 - 所有其他 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 对象。因此,此 Intent 显式地启动了应用中的 DownloadService 类。

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

隐式 Intent 示例

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

例如,如果您有想要与其他人共享的内容,请创建一个带有 ACTION_SEND 操作的 Intent,并添加指定要共享内容的额外信息。当您使用该 Intent 调用 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() 时,系统会检查所有已安装的应用,以确定哪些应用可以处理此类 Intent(带有 ACTION_SEND 操作并携带“text/plain”数据的 Intent)。如果只有一个应用可以处理它,该应用会立即打开并获得该 Intent。如果没有其他应用可以处理它,您的应用可以捕获发生的 ActivityNotFoundException。如果多个 Activity 接受该 Intent,系统会显示一个类似于图 2 中所示的对话框,以便用户可以选择要使用的应用。

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

图 2. 选择器对话框。

强制应用选择器

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

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

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

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);
}

检测不安全的 Intent 启动

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

如果您的应用执行了以下两个操作,系统会检测到不安全的 Intent 启动,并会发生 StrictMode 违规

  1. 您的应用从已传递 Intent 的额外信息中反序列化嵌套 Intent。
  2. 您的应用立即使用该嵌套 Intent 启动 应用组件,例如将 Intent 传递到 startActivity()startService()bindService()

有关如何识别这种情况并对您的应用进行更改的更多详细信息,请阅读 Medium 上有关 Android 嵌套 Intent 的博文。

检查不安全的 Intent 启动

要检查您的应用中的不安全的 Intent 启动,请在配置 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());
}

更负责任地使用 Intent

要最大程度地降低发生不安全的 Intent 启动和 StrictMode 违规的可能性,请遵循以下最佳实践。

只复制 Intent 中的必要额外信息,并执行必要的清理和验证。您的应用可能会将额外信息从一个 Intent 复制到另一个用于启动新组件的 Intent。当您的应用调用 putExtras(Intent)putExtras(Bundle) 时会发生这种情况。如果您的应用执行了其中一项操作,请只复制接收组件期望的额外信息。如果另一个 Intent(接收副本)启动的组件不是 导出的,请在将额外信息复制到启动该组件的 Intent 之前清理和验证它们。

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

使用 PendingIntent 而不是嵌套 Intent。这样,当另一个应用反序列化其包含的 IntentPendingIntent 时,另一个应用可以使用您的应用的身份启动 PendingIntent。这种配置允许另一个应用安全地启动您的应用中的任何组件,包括非导出组件。

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

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

图 2. 使用嵌套 Pending Intent 时应用间通信的图表。

接收隐式 Intent

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

注意:显式意图始终会传递到其目标,而不管组件声明的任何意图过滤器。

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

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

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

警告:如果应用程序中的 Activity、服务或广播接收器使用意图过滤器但没有显式设置 android:exported 的值,则应用程序无法安装在运行 Android 12 或更高版本的设备上。

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

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

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

例如,以下是一个 Activity 声明,它具有一个意图过滤器,用于在数据类型为文本时接收 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,请始终使用显式意图启动自己的服务。

注意:对于所有 Activity,您必须在清单文件中声明意图过滤器。但是,广播接收器的过滤器可以通过调用 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>

第一个 Activity MainActivity 是应用程序的主入口点——用户使用启动器图标首次启动应用程序时打开的 Activity

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

这两个必须配对,以便 Activity 才能出现在应用程序启动器中。

第二个 Activity ShareActivity 用于促进文本和媒体内容的分享。虽然用户可以通过从 MainActivity 导航到此 Activity 来进入此 Activity,但他们也可以从发出与两个意图过滤器之一匹配的隐式意图的另一个应用程序直接进入 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 对象都旨在由特定类型的应用程序组件(无论是 ActivityService 还是 BroadcastReceiver)处理一样,挂起意图也必须使用相同的考虑创建。使用挂起意图时,您的应用程序不会使用诸如 startActivity() 之类的调用来执行意图。相反,您必须在使用相应的创建者方法创建 PendingIntent 时声明预期的组件类型

除非您的应用接收来自其他应用的 PendingIntent,否则上面创建 PendingIntent 的方法可能就是您唯一需要的 PendingIntent 方法。

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

有关使用 PendingIntent 的更多信息,请参阅每个用例的文档,例如 通知应用程序小部件 API 指南。

指定可变性

如果您的应用面向 Android 12 或更高版本,您必须指定应用创建的每个 PendingIntent 对象的可变性。要声明给定的 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 对象,如下面的代码片段所示。如果 PendingIntent 对象是不可变的,那么其他应用就无法修改 Intent 来调整调用 Intent 的结果。

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 对象允许系统添加代表位置生命周期事件的 Intent 附加信息。这些事件包括位置发生变化和提供者变为可用。
  • 使用 AlarmManager 安排闹钟。可变的 PendingIntent 对象允许系统添加 EXTRA_ALARM_COUNT Intent 附加信息。此附加信息表示重复闹钟被触发的次数。通过包含此附加信息,Intent 可以准确地通知应用重复闹钟是否被触发多次,例如当设备处于休眠状态时。

如果您的应用创建了一个可变的 PendingIntent 对象,强烈建议您使用 显式 Intent 并填写 ComponentName。这样,每当其他应用调用 PendingIntent 并将控制权返回给您的应用时,应用中的同一组件始终启动。

在 PendingIntent 中使用显式 Intent

为了更好地定义其他应用如何使用您的应用的 PendingIntent,始终将 PendingIntent 包装在 显式 Intent 周围。为了帮助遵循此最佳做法,请执行以下操作

  1. 检查基本 Intent 的 action、package 和 component 字段是否已设置。
  2. 使用 FLAG_IMMUTABLE(在 Android 6.0(API 级别 23)中添加)创建 PendingIntent。此标志可防止接收 PendingIntent 的应用填充未填充的属性。如果您的应用的 minSdkVersion22 或更低,则可以使用以下代码同时提供安全性与兼容性

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

Intent 解析

当系统收到启动活动的隐式 Intent 时,它会根据以下三个方面比较 Intent 与基于 Intent 过滤器的 Intent,从而搜索最适合该 Intent 的活动

  • 操作。
  • 数据(URI 和数据类型)。
  • 类别。

以下部分将介绍如何根据应用清单文件中声明的 Intent 过滤器,将 Intent 匹配到相应的组件。

操作测试

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

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

为了通过此过滤器,Intent 中指定的 action 必须与过滤器中列出的 action 之一匹配。

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

类别测试

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

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

为了使 Intent 通过类别测试,Intent 中的每个类别都必须与过滤器中的类别匹配。反之则不一定成立——Intent 过滤器可以声明的类别多于 Intent 中指定的类别,并且 Intent 仍然通过测试。因此,没有类别的 Intent 始终通过此测试,无论过滤器中声明了哪些类别。

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

数据测试

为了指定接受的 Intent 数据,Intent 过滤器可以声明零个或多个 <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 中,scheme 为 content,host 为 com.example.project,port 为 200,path 为 folder/subfolder/etc

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

  • 如果未指定 scheme,则 host 将被忽略。
  • 如果未指定 host,则 port 将被忽略。
  • 如果 scheme 和 host 都未指定,则 path 将被忽略。

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

  • 如果过滤器只指定了一个 scheme,则所有具有该 scheme 的 URI 都将匹配该过滤器。
  • 如果过滤器指定了一个 scheme 和一个 authority 但没有 path,则所有具有相同 scheme 和 authority 的 URI 都将通过过滤器,无论它们的 path 如何。
  • 如果过滤器指定了一个 scheme、一个 authority 和一个 path,则只有具有相同 scheme、authority 和 path 的 URI 才能通过过滤器。

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

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

  1. 既不包含 URI 也不包含 MIME 类型的 Intent 仅在过滤器没有指定任何 URI 或 MIME 类型时才通过测试。
  2. 包含 URI 但不包含 MIME 类型(既没有显式 MIME 类型,也没有从 URI 推断出的 MIME 类型)的 Intent 仅在其 URI 匹配过滤器的 URI 格式且过滤器同样没有指定 MIME 类型时才通过测试。
  3. 包含 MIME 类型但不包含 URI 的 Intent 仅在其过滤器列出相同的 MIME 类型且没有指定 URI 格式时才通过测试。
  4. 包含 URI 和 MIME 类型(显式或从 URI 推断出)的 Intent 仅在该类型与过滤器中列出的类型匹配时才通过 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>

意图匹配

意图不仅与意图过滤器匹配以发现要激活的目标组件,而且还用于发现有关设备上组件集的信息。例如,主屏幕应用程序通过查找所有具有意图过滤器的活动来填充应用程序启动器,这些意图过滤器指定 ACTION_MAIN 操作和 CATEGORY_LAUNCHER 类别。只有当意图中的操作和类别与过滤器匹配时,匹配才成功,如 IntentFilter 类的文档中所述。

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