intent 和 intent 过滤器

Intent 是一种消息传递对象,可用于从其他应用组件请求操作。尽管 intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

  • 启动 activity

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

    如果您希望在 activity 完成后收到结果,请调用 startActivityForResult()。在 activity 的 onActivityResult() 回调中,您的 activity 将结果作为单独的 Intent 对象接收。如需了解详情,请参阅 activity 指南。

  • 启动服务

    Service 是一种不使用界面而在后台执行操作的组件。在 Android 5.0(API 级别 21)及更高版本中,您可以使用 JobScheduler 启动服务。如需详细了解 JobScheduler,请参阅其 API-reference documentation

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

    如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。如需了解详情,请参阅服务指南。

  • 传递广播

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

本页的其余部分将说明 intent 的工作方式及如何使用 intent。 如需了解相关信息,请参阅与其他应用交互共享内容

Intent 类型

Intent 分为两种类型:

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

图 1 显示了如何在启动 activity 时使用 intent。当 Intent 对象显式命名某个具体的 activity 组件时,系统立即启动该组件。

图 1. 隐式 intent 如何通过系统传递以启动其他 activity:[1] activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()[2] Android 系统会搜索所有应用,以查找与 intent 匹配的 intent 过滤器。找到匹配项之后,[3] 该系统通过调用匹配 activity(activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配 activity。

使用隐式 intent 时,Android 系统通过将 intent 的内容与设备上其他应用的清单文件中声明的intent 过滤器进行比较,从而找到要启动的相应组件。如果 intent 与 intent 过滤器匹配,系统将启动该组件,并向其传递 Intent 对象。如果多个 intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。

intent 过滤器是应用清单文件中的一个表达式,用于指定该组件要接收的 intent 类型。例如,通过为 activity 声明 intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 intent 启动 activity。同样,如果您为 activity 声明任何 intent 过滤器,则 activity 只能通过显式 intent 启动。

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

构建 Intent

Intent 对象包含 Android 系统用于确定要启动哪个组件的信息(例如应接收 intent 的确切组件名称或组件类别),以及接收方组件用于正确执行操作的信息(例如要执行的操作和要操作的数据)。

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

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

这是可选项,但也是构建显式 intent 的一项重要信息,这意味着 intent 应当仅传递给由组件名称定义的应用组件。如果没有组件名称,则 intent 是隐式的,且系统将根据其他 intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 intent。如需在应用中启动特定的组件,则应指定该组件的名称。

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

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

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

对于广播 intent,这是指已发生且正在报告的操作。操作在很大程度上决定了其余 intent 的构成,特别是数据和 extra 中包含的信息。

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

ACTION_VIEW
如果您拥有一些某项 activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请通过 intent 将此操作与 startActivity() 结合使用。
ACTION_SEND
这也称为分享 intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 intent 将此操作与 startActivity() 结合使用。

如需了解更多定义通用操作的常量,请参阅 Intent 类参考文档。其他操作在 Android 框架中的其他位置定义。例如,对于在系统的设置应用中打开特定屏幕的操作,将在 Settings 中定义。

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

如以下示例所示,如果定义自己的操作,请确保加入应用的软件包名称作为前缀:

Kotlin

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

Java

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

创建 intent 时,除了指定 URI 以外,指定数据类型(其 MIME 类型)往往也很重要。例如,能够显示图像的 activity 可能无法播放音频文件,即便 URI 格式十分类似时也是如此。因此,指定数据的 MIME 类型有助于 Android 系统找到接收 intent 的最佳组件。不过,有时 MIME 类型可以从 URI 中推断得出,特别是当数据是 content: URI 时。content: URI 表明数据位于设备中,且由 ContentProvider 控制,这使得数据 MIME 类型对系统可见。

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

注意:如果您想同时设置 URI 和 MIME 类型,请调用 setData()setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。

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

有关类别的完整列表,请参阅 Intent 类说明。

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

以上列出的这些属性(组件名称、操作、数据和类别)表示 intent 的既定特征。通过读取这些属性,Android 系统能够解析应当启动哪个应用组件。但是,Intent 也有可能会携带一些不影响其如何解析为应用组件的信息。Intent 还可以提供以下信息:

Extra
携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。

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

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

Intent 类将为标准化的数据类型指定多个 EXTRA_* 常量。如需声明自己的 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 是否属于最近的 activity 列表)。

如需了解详情,请参阅 setFlags() 方法。

显式 Intent 示例

显式 intent 是指用于启动某个特定应用组件(例如,应用中的某个特定 activity 或服务)的 intent。如需创建显式 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,并添加指定共享内容的 extra。使用该 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 中作为 extra 传递的 intent。

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

  1. 您的应用从已传递的 intent 的 extra 中解封嵌套 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 中的必要 extra,并执行任何必要的清理和验证。您的应用可能会将 extra 从一个 intent 复制到用于启动新组件的另一个 intent。当您的应用调用 putExtras(Intent)putExtras(Bundle) 时,就会发生这种情况。如果您的应用执行这些操作其中之一,请仅复制接收组件所期望的 extra。如果另一个 intent(它接收副本)启动一个未导出的组件,请先清理和验证 extra,然后再将其复制到启动该组件的 intent。

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

使用 PendingIntent 而非嵌套 intent。这样一来,当其他应用解封其包含的 IntentPendingIntent 时,该应用可以使用您应用的身份启动 PendingIntent。这种配置允许其他应用安全地启动您应用中的任何组件(包括未导出的组件)。

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

  1. 您的应用会创建一个 intent,用于调用其他应用中的 activity。在该 intent 中,您可以添加 PendingIntent 对象作为 extra。此待处理 intent 会调用应用中的组件;此组件未导出。
  2. 收到应用的 intent 后,其他应用会提取嵌套的 PendingIntent 对象。
  3. 另一个应用会对 PendingIntent 对象调用 send() 方法。
  4. 将控制权传回您的应用后,系统会使用应用的上下文调用待处理 intent。

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

接收隐式 Intent

如需公布应用可以接收哪些隐式 intent,请在清单文件中使用 <intent-filter> 元素为每个应用组件声明一个或多个 intent 过滤器。每个 intent 过滤器均根据 intent 的操作、数据和类别指定自身接受的 intent 类型。仅当隐式 intent 可以通过 intent 过滤器之一传递时,系统才会将该 intent 传递给应用组件。

注意:显式 intent 始终会传递给其目标,无论组件声明的 intent 过滤器如何均是如此。

应用组件应当为自身可执行的每个独特作业声明单独的过滤器。例如,图像库应用中的一个 activity 可能会有两个过滤器,分别用于查看图像和编辑图像。当 activity 启动时,它会检查 Intent,并根据 Intent 中的信息决定具体的行为(例如,是否显示编辑器控件)。

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

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

警告:如果应用中的 activity、服务或广播接收器使用 intent 过滤器,并且未显式设置 android:exported 的值,则您的应用将无法在搭载 Android 12 或更高版本的设备上进行安装。

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

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

注意:如需接收隐式 intent,您必须在 intent 过滤器中添加 CATEGORY_DEFAULT 类别。startActivity()startActivityForResult() 方法将所有 intent 当作声明了 CATEGORY_DEFAULT 类别一样对待。如果您未在 intent 过滤器中声明此类别,则任何隐式 intent 都不会解析为您的 activity。

例如,以下是一个使用包含 intent 过滤器的 activity 声明,当数据类型为文本时,系统将接收 ACTION_SEND intent:

<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> 实例的过滤器。创建时,需确定组件能够处理这些过滤器元素的任何及所有组合。

如需仅以操作、数据和类别类型的特定组合来处理多种 intent,则需创建多个 intent 过滤器。

系统通过将 intent 与所有这三个元素进行比较,根据过滤器测试隐式 intent。隐式 intent 若要传递给组件,必须通过所有这三项测试。如果 intent 甚至无法匹配其中任何一项测试,则 Android 系统不会将其传递给组件。但是,由于一个组件可能有多个 intent 过滤器,因此未能通过某一组件过滤器的 intent 可能会通过另一过滤器。如需详细了解系统如何解析 intent,请参阅下文的intent 解析部分。

注意 :使用 intent 过滤器时,无法安全地防止其他应用启动组件。尽管 intent 过滤器将组件限制为仅响应特定类型的隐式 intent,但如果开发者确定您的组件名称,则其他应用有可能通过使用显式 intent 启动您的应用组件。如果必须确保只有您自己的应用才能启动您的某一组件,请勿在清单中声明 intent 过滤器。而是将该组件的 exported 属性设置为 "false"

同样,为了避免无意中运行其他应用的 Service,请始终使用显式 intent 启动您自己的服务。

注意:对于所有 activity,您必须在清单文件中声明 intent 过滤器。不过,广播接收器的过滤器可以通过调用 registerReceiver() 动态注册。然后,您可以使用 unregisterReceiver() 注销该接收器。这样一来,应用便可仅在应用运行时的某一指定时间段内侦听特定的广播。

过滤器示例

为演示一些 intent 过滤器的行为,请参阅从社交共享应用的清单文件截取的示例:

<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 操作指示这是主要入口点,且不要求输入任何 intent 数据。
  • CATEGORY_LAUNCHER 类别指示此 activity 的图标应放入系统的应用启动器。如果 <activity> 元素未使用 icon 指定图标,则系统将使用 <application> 元素中的图标。

这两个元素必须配对使用,Activity 才会显示在应用启动器中。

第二个 activity ShareActivity 旨在便于共享文本和媒体内容。尽管用户可以通过从 MainActivity 导航进入此 activity,但也可以从发出与两个 intent 过滤器之一匹配的隐式 intent 的另一应用中直接进入 ShareActivity

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

将 intent 与其他应用的 intent 过滤器匹配

如果其他应用以 Android 13(API 级别 33)或更高版本为目标平台,则只有当您的 intent 与该其他应用中的 <intent-filter> 元素的操作和类别相匹配时,该应用才能处理您的应用的 intent。如果系统找不到匹配项,则会抛出 ActivityNotFoundException。发送应用必须处理此异常。

同样,如果您将应用更新为以 Android 13 或更高版本为目标平台,仅当源自外部应用的所有 intent 与应用声明的 <intent-filter> 元素的操作和类别匹配时,系统才会将这些 intent 传送到应用的导出组件。无论发送应用的目标 SDK 版本如何,都会出现这种行为。

在以下情况下,系统不会强制执行 intent 匹配:

  • 传送到未声明任何 intent 过滤器的组件中的 intent。
  • 源自同一应用内的 intent。
  • 源自系统的 intent;也就是说,从“系统 UID”(uid=1000) 发送的 intent。系统应用包括 system_server 和将 android:sharedUserId 设置为 android.uid.system 的应用。
  • 源自根的 intent。

详细了解intent 匹配

使用待定 Intent

PendingIntent 对象是 Intent 对象的封装容器。PendingIntent 的主要目的是授权外部应用使用包含的 Intent,就像是它从您应用本身的进程中执行的一样。

待定 Intent 的主要用例包括:

  • 声明用户使用您的通知执行操作时要执行的 intent(Android 系统的 NotificationManager 会执行 Intent)。
  • 声明用户使用您的应用微件执行操作时要执行的 intent(主屏幕应用执行 Intent)。
  • 声明未来某一特定时间要执行的 intent(Android 系统的 AlarmManager 执行 Intent)。

由于每个 Intent 对象均设计为由特定类型的应用组件(ActivityServiceBroadcastReceiver)进行处理,因此还必须基于相同的考虑因素创建 PendingIntent。使用待定 intent 时,应用不会使用调用(如 startActivity())执行该 intent。相反,通过调用相应的创建器方法创建 PendingIntent 时,您必须声明所需的组件类型:

除非您的应用正在从其他应用中接收待定 intent,否则上述用于创建 PendingIntent 的方法可能是您所需的唯一 PendingIntent 方法。

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

如需详细了解如何使用待处理 intent,请参阅通知应用微件 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.

尽可能创建不可变的待处理 intent

在大多数情况下,您的应用应创建不可变的 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 extra。这些事件包括位置发生变化和提供商变为可用。
  • 使用 AlarmManager 设置闹钟。 可变的 PendingIntent 对象允许系统添加 EXTRA_ALARM_COUNT intent extra。此 extra 表示重复闹钟已触发的次数。通过包含此 extra,intent 可以准确通知应用重复闹钟是否已多次触发(例如在设备处于休眠状态时)。

如果您的应用创建了可变的 PendingIntent 对象,强烈建议您使用显式 intent 并填写 ComponentName。如此一来,每当另一个应用调用 PendingIntent 并将控制权传回您的应用时,应用中的相同组件都会启动。

在待处理 intent 中使用显式 intent

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

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

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

Intent 解析

当系统收到隐式 intent 以启动 activity 时,它会根据以下三个方面将该 intent 与 intent 过滤器进行比较,搜索该 intent 的最佳 activity:

  • 操作。
  • 数据(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 中指定的操作必须与过滤器中列出的某一操作匹配。

如果该过滤器未列出任何操作,则 intent 没有任何匹配项,因此所有 intent 均无法通过测试。但是,如果 Intent 未指定操作,则只要过滤器内包含至少一项操作,就可以通过测试。

类别测试

要指定接受的 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。如果您希望 activity 接收隐式 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 中,架构为 content,主机为 com.example.project,端口为 200,路径为 folder/subfolder/etc

<data> 元素中,上述每个属性均为可选,但存在线性依赖关系:

  • 如果未指定架构,则会忽略主机。
  • 如果未指定主机,则会忽略端口。
  • 如果未指定架构和主机,则会忽略路径。

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

  • 如果过滤器仅指定架构,则具有该架构的所有 URI 均与该过滤器匹配。
  • 如果过滤器指定架构和权限,但未指定路径,则具有相同架构和权限的所有 URI 都会通过过滤器,无论其路径如何均是如此。
  • 如果过滤器指定架构、权限和路径,则仅具有相同架构、权限和路径的 URI 才会通过过滤器。

注意:路径规范可以包含星号通配符 (*),因此仅需部分匹配路径名即可。

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

  1. 仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 intent 才会通过测试。
  2. 对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。
  3. 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不含 URI 的 intent 才会通过测试。
  4. 仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 intent 才会通过测试的 MIME 类型部分。如果 intent 的 URI 与过滤器中的 URI 匹配,或者如果 intent 具有 content:file: URI 且过滤器未指定 URI,则 intent 会通过测试的 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,以此填充应用启动器。如 IntentFilter 类文档所述,只有当 intent 中的操作和类别与过滤器匹配时,匹配才会成功。

您的应用可以使用类似于主页应用的方式使用 intent 匹配。PackageManager 提供了一整套 query...() 方法来返回所有能够接受特定 intent 的组件。此外,它还提供了一系列类似的 resolve...() 方法来确定响应 intent 的最佳组件。例如,queryIntentActivities() 会返回能够执行作为参数传递的 intent 中列出的所有 activity,而 queryIntentServices() 则可返回类似的一系列服务。这两种方法均不会激活组件;而只是列出能够响应的组件。对于广播接收器,有一种类似的方法:queryBroadcastReceivers()