intent 和 intent 过滤器

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

  • 启动 activity

    Activity 表示应用中的单个屏幕。您可以创建新的 通过传递 Intent 来调用 Activity 的实例 发送至 startActivity()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 通过指定完整的 ComponentName 来指定哪个应用的哪个组件将满足 intent。通常,您会在自己的应用中使用显式 intent 来启动组件,这是因为您知道要启动的 activity 或服务的类名称。对于 例如,您可以在应用中启动新 activity 以响应用户操作,或启动 用于在后台下载文件的服务。
  • 隐式 intent 不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。例如,如果您希望 在地图上向用户显示位置,则可以使用隐式 Intent 请求另一个具有 应用在地图上显示指定位置。

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

图 1. 隐式 intent 启动另一个 activity:[1] activity A 会创建一个 Intent,包含操作说明,并将其传递给 startActivity()[2] Android 系统会搜索所有 应用。找到匹配后,[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 中才能实现。

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

构建 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 使用) 应用来调用应用中的组件),但您通常需要指定操作常量 由 Intent 类或其他框架类定义。以下是一些用于启动 activity 的常见操作:

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

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

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

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

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 的最佳组件。 不过,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,并列在 系统的应用启动器。

如需查看完整的 API 列表,请参阅 Intent 类说明。 类别。

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

上面列出的这些属性(组件名称、操作、数据和类别)代表 intent 的特征。通过读取这些属性,Android 系统 能够解析应启动哪个应用组件。不过,intent 可以 对现有代码没有影响的 解析为应用组件的方式Intent 还可以提供以下信息:

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

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

例如,在创建意图以发送 ACTION_SEND,则可以使用 EXTRA_EMAIL 键,并使用 EXTRA_SUBJECT 键。

Intent 类会指定多个 EXTRA_* 常量 用于标准化数据类型。如果您需要声明自己的 extra 键(对于 应用接收的内容),请务必加入应用的软件包名称 用作前缀,如以下示例所示:

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

注意:在发送您希望另一个应用接收的 intent 时,请勿使用 ParcelableSerializable 数据。如果应用 它尝试访问 Bundle 对象中的数据,但未 有权访问 Parceled 或序列化类,则系统会引发 RuntimeException

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

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

显式 Intent 示例

显式 intent 是指用于启动特定应用组件的 intent,如 特定 activity 或服务。如需创建显式 intent,请定义 Intent 对象的组件名称 - all 其他 intent 属性是可选的。

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

// 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。致电后 startActivity() 与该 intent 相关联,则用户可以 选择用来分享内容的应用。

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

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 是指 另一个 intent 中的 extra。

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

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

如需详细了解如何识别这种情况并更改您的应用, 阅读有关Android Nesting 的博客文章 intent 来自 Medium。

检查是否存在不安全的 intent 启动

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

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。 此属性指示其他应用是否可以访问应用组件。在某些 例如 activity 的 intent 过滤器包含 LAUNCHER 则有必要将此属性设置为 true。否则,将此属性设置为 false 会更安全。

警告:如果某项活动、服务或广播 应用中的接收器使用 intent 过滤器,并且未明确设置值 android:exported 时,您的应用将无法安装在符合以下条件的设备上 搭载 Android 12 或更高版本。

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

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

注意:要接收隐式 intent,您需要 必须包含 CATEGORY_DEFAULT 类别。方法 startActivity()startActivityForResult() 处理所有 intent 就像声明了 CATEGORY_DEFAULT 类别一样。 如果您不在 intent 过滤器中声明此类别,则任何隐式 intent 都不会解析为 活动

例如,以下是一个使用包含 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 是应用的主入口点, 当用户首次使用启动器图标启动应用时打开:

  • ACTION_MAIN 操作 表示这是主要入口点,不需要任何 intent 数据。
  • CATEGORY_LAUNCHER 类别表明此活动的 图标应放置在系统的应用启动器中。如果 <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> 元素。如果系统没有找到 匹配,则会抛出 ActivityNotFoundException。 发送应用必须处理此异常。

同样,如果您更新应用,使其以 Android 13 为目标平台 或更高版本的 intent,源自外部应用的所有 intent 都会传递给 应用的导出组件 应用声明的 <intent-filter> 元素的类别。无论发送应用的目标 SDK 版本如何,都会出现这种行为。

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

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

详细了解意图匹配

使用待定 Intent

PendingIntent 对象是 Intent 对象的封装容器。PendingIntent 的主要用途 就是向外部应用授予权限 使用包含的 Intent,就像是从 应用自己的进程。

待定 Intent 的主要用例包括:

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

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

除非您的应用正在接收来自其他应用的待处理 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 的结果。

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. 使用 FLAG_IMMUTABLE。 Android 6.0(API 级别 23)中引入的新增功能,用于创建待处理 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 过滤器可以声明 0 个或多个 <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 过滤器可以 声明的类别多于 IntentIntent仍然通过。因此,没有类别的 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)通过测试。 而过滤器同样不指定 MIME 类型。
  3. 包含 MIME 类型但不包含 URI 的 intent 通过了测试 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时。
  4. 一个同时包含 URI 和 MIME 类型(显式或可通过 URI)通过测试的 MIME 类型部分,前提是 type 与过滤条件中列出的类型匹配。它通过了测试的 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() 则可返回类似的一系列服务。这两种方法均不会激活组件;而只是列出能够响应的组件。对于广播接收器,有一种类似的方法:queryBroadcastReceivers()