intent 和 intent 过滤器

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

  • 启动 activity

    Activity 表示应用中的单个界面。您可以通过将 Intent 传递给 startActivity() 来启动 Activity 的新实例。Intent 用于描述要启动的 activity,并携带所有必要的数据。

    如果您想在 activity 结束时从 activity 接收结果,请调用 startActivityForResult()。您的 activity 会在 activity 的 onActivityResult() 回调中以单独的 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 通过指定完整的 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 的 onCreate() 方法并将 Intent 传递给该 activity 来启动匹配 activity (Activity B)。

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

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

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

构建 Intent

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

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 中使用此 intent 与 startActivity() 搭配使用。

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

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

如果您定义自己的操作,请务必将应用的软件包名称作为前缀添加到操作中,如以下示例所示:

KotlinJava
const val ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
数据
引用要执行操作的数据和/或该数据的 MIME 类型的 URI(一个 Uri 对象)。所提供数据的类型通常由 intent 的操作决定。例如,如果操作为 ACTION_EDIT,则数据应包含要修改的文档的 URI。

创建 intent 时,除了 URI 之外,通常还需要指定数据类型(其 MIME 类型)。例如,能够显示图片的 activity 可能无法播放音频文件,即使 URI 格式可能相似也是如此。指定数据的 MIME 类型有助于 Android 系统找到最适合接收 intent 的组件。不过,有时可以从 URI 推断出 MIME 类型,尤其是当数据是 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() 方法添加额外数据,每种方法都接受两个参数:键名称和值。您还可以创建包含所有额外数据的 Bundle 对象,然后使用 putExtras()Bundle 插入 Intent

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

Intent 类为标准化数据类型指定了许多 EXTRA_* 常量。如果您需要声明自己的 extra 键(针对应用收到的 intent),请务必添加应用的软件包名称作为前缀,如以下示例所示:

KotlinJava
const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"
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 对象定义组件名称,所有其他 intent 属性都是可选的。

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

KotlinJava
// 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)
// 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() 时,用户可以选择要用于分享内容的应用。

KotlinJava
// 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.
}
// 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 的应用列表,并将提供的文本用作对话框标题。

KotlinJava
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)
}
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 违规行为,您可能需要停止应用的执行以保护潜在的敏感信息。

KotlinJava
fun onCreate() {
    StrictMode.setVmPolicy(VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build())
}
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。

例如,下面这个 activity 声明包含一个 intent 过滤器,用于在数据类型为文本时接收 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 必须通过这三项测试。如果它与其中任一项都不匹配,Android 系统将不会将 intent 传递给组件。不过,由于组件可以具有多个 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 来进入此 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 的主要用例包括:

  • 声明在用户通过通知执行操作时要执行的 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,请参阅各个相应用例的文档,例如 NotificationsApp Widgets 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 的结果。

KotlinJava
val pendingIntent = PendingIntent.getActivity(applicationContext,
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE)
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 解析

当系统收到用于启动 activity 的隐式 intent 时,会根据以下三个方面将 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 部分进行比较。例如:

  • 如果过滤器仅指定了 scheme,则所有采用该 scheme 的 URI 都与过滤器匹配。
  • 如果过滤器指定了 scheme 和 authority,但未指定路径,则所有具有相同 scheme 和 authority 的 URI 都会通过过滤器,无论其路径如何。
  • 如果过滤器指定了 scheme、authority 和路径,则只有具有相同 scheme、authority 和路径的 URI 才能通过过滤器。

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

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

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

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

最后一个规则(规则 (d))反映了预期组件能够从文件或 content provider 获取本地数据。因此,其过滤器只需列出数据类型,而无需明确指定 content:file: 架构。以下示例展示了 <data> 元素告知 Android 该组件可以从内容提供程序获取图片数据并显示该数据的典型情况:

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

指定数据类型但不指定 URI 的过滤器可能是最常见的过滤器,因为大多数可用数据都是由 content provider 提供的。

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

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

Intent 匹配

系统会将 intent 与 intent 过滤器进行匹配,不仅是为了发现要激活的目标组件,还为了发现设备上这组组件的相关信息。例如,“家庭”应用会通过查找 intent 过滤器指定 ACTION_MAIN 操作和 CATEGORY_LAUNCHER 类别的所有 activity 来填充应用启动器。只有当 intent 中的操作和类别与过滤器匹配时,匹配才会成功,如 IntentFilter 类文档中所述。

您的应用可以使用与 Home 应用类似的方式使用 intent 匹配。PackageManager 包含一组 query...() 方法,用于返回可以接受特定 intent 的所有组件,以及一组类似的 resolve...() 方法,用于确定响应 intent 的最佳组件。例如,queryIntentActivities() 会返回可执行作为参数传递的 intent 的所有 activity 的列表,而 queryIntentServices() 会返回类似的服务列表。这两种方法都不会激活组件,只会列出可以响应的组件。广播接收器也有类似的方法 queryBroadcastReceivers()