Intent
是一种消息传递对象,可用于向其他应用组件请求操作。虽然 intent 可以通过多种方式促进组件之间的通信,但有三种基本用例:
- 启动 activity
Activity
表示应用中的单个界面。您可以通过将Intent
传递给startActivity()
来启动Activity
的新实例。Intent
用于描述要启动的 activity,并携带任何必要的数据。如果您想在 activity 完成时接收结果,请调用
startActivityForResult()
。您的 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。例如,如果您想在地图上向用户显示某个位置,可以使用隐式 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 过滤器,则只能使用显式 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
构造函数设置组件名称。 - 操作
- 一个字符串,用于指定要执行的通用操作(例如 view 或 pick)。
对于广播 intent,这是已发生并正在报告的操作。 操作在很大程度上决定了 intent 的其余结构,尤其是数据和 extra 中包含的信息。
您可以指定自己的操作以供应用内的 intent 使用(或供其他应用用来调用您应用中的组件),但通常情况下,您会指定由
Intent
类或其他框架类定义的操作常量。以下是开始活动的一些常见操作:ACTION_VIEW
- 如果您有一些 activity 可以向用户显示的信息,例如要在图库应用中查看的照片或要在地图应用中查看的地址,请在包含
startActivity()
的 intent 中使用此操作。 ACTION_SEND
- 也称为分享 intent,当您有一些数据可供用户通过其他应用(例如电子邮件应用或社交分享应用)分享时,应在包含
startActivity()
的 intent 中使用此操作。
如需了解定义通用操作的更多常量,请参阅
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";
- 数据
- 引用要处理的数据的 URI(一个
Uri
对象)和/或相应数据的 MIME 类型。提供的数据类型通常由 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
键指定 to 收件人,并使用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 时,请勿使用
Parcelable
或Serializable
数据。如果应用尝试访问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 theContext
// 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 theContext
// 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,则系统会立即打开该应用并将 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 会检测到违规事件:
- 您的应用从已传递的 intent 的 extra 中解封嵌套 intent。
- 您的应用立即使用该嵌套 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。这样一来,当另一个应用解封包含 Intent
的 PendingIntent
时,该应用可以使用您的应用的身份启动 PendingIntent
。此配置允许该应用安全地启动您应用中的任何组件,包括未导出的组件。
图 2 中的图表显示了系统如何将控制权从您的(客户端)应用传递到另一个(服务)应用,然后再传递回您的应用:
- 您的应用会创建一个 intent,用于调用另一个应用中的 activity。在该 intent 中,您会添加一个
PendingIntent
对象作为 extra。此待处理 intent 会调用应用中的一个组件;此组件未导出。 - 在收到您应用的 intent 后,另一个应用会提取嵌套的
PendingIntent
对象。 - 另一个应用会调用
PendingIntent
对象的send()
方法。 - 将控制权传递回您的应用后,系统会使用应用的上下文调用待处理的 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 的各个方面(
scheme
、host
、port
、path
)和 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 传递给组件,该 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,但也可以直接从另一个应用进入 ShareActivity
,该应用会发出与两个 intent 过滤器之一匹配的隐式 intent。
注意:MIME 类型 application/vnd.google.panorama360+jpg
是一种特殊的数据类型,用于指定全景照片,您可以使用 Google 全景 API 来处理全景照片。
将 intent 与其他应用的 intent 过滤器匹配
如果另一个应用以 Android 13(API 级别 33)或更高版本为目标平台,则只有当您的 intent 与该应用中 <intent-filter>
元素的操作和类别相匹配时,该应用才能处理您的应用的 intent。如果系统未找到匹配项,则会抛出 ActivityNotFoundException
。发送应用必须处理此异常。
同样,如果您更新应用,使其以 Android 13 或更高版本为目标平台,那么仅当源自外部应用的 intent 与应用声明的 <intent-filter>
元素的 action 和 category 相匹配时,该 intent 才会传递给应用的导出组件。无论发送方的应用的目标 SDK 版本如何,都会出现此行为。
在以下情况下,系统不会强制要求匹配 intent:
- 传送到未声明任何 intent 过滤器的组件中的 intent。
- 源自同一应用内的 intent。
- 源自系统的 intent;也就是说,从“系统 UID”(uid=1000) 发送的 intent。系统应用包括
system_server
和将android:sharedUserId
设置为android.uid.system
的应用。 - 源自根的 intent。
详细了解意向匹配。
使用待定 Intent
PendingIntent
对象是 Intent
对象的封装容器。PendingIntent
的主要目的是向外部应用授予权限,以使用所含的 Intent
,就像从您自己的应用进程执行一样。
待定 Intent 的主要用例包括:
- 声明当用户对 Notification 执行操作时要执行的 intent(Android 系统的
NotificationManager
执行Intent
)。 - 声明当用户对您的应用 widget 执行操作时要执行的 intent(主屏幕应用执行
Intent
)。 - 声明要在指定的未来时间执行的 intent(Android 系统的
AlarmManager
执行Intent
)。
正如每个 Intent
对象都旨在由特定类型的应用组件(Activity
、Service
或 BroadcastReceiver
)处理一样,在创建 PendingIntent
时也必须考虑这一点。使用待定 intent 时,您的应用不会通过 startActivity()
等调用来执行该 intent。正确做法是,您必须在通过调用相应的创建者方法来创建 PendingIntent
时声明预期组件类型:
PendingIntent.getActivity()
,用于启动Activity
的Intent
。PendingIntent.getService()
,用于启动Service
的Intent
。PendingIntent.getBroadcast()
,用于启动BroadcastReceiver
的Intent
。
除非您的应用正在接收来自其他应用的待处理 intent,否则上述创建 PendingIntent
的方法可能就是您唯一需要使用的 PendingIntent
方法。
每种方法都接受当前应用的 Context
、要封装的 Intent
以及一个或多个用于指定 intent 应如何使用的标志(例如,intent 是否可以多次使用)。
如需详细了解如何使用待处理 intent,请参阅各个相应用例的文档,例如通知和应用微件 API 指南。
指定可变性
如果您的应用以 Android 12 或更高版本为目标平台,您必须指定应用创建的每个 PendingIntent
对象的可变性。如需声明特定 PendingIntent
对象是否可变,请分别使用 PendingIntent.FLAG_MUTABLE
或 PendingIntent.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_TASK
和FLAG_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 中。为帮助您遵循此最佳实践,请执行以下操作:
- 检查是否已设置基本 intent 的操作、软件包和组件字段。
-
使用 Android 6.0(API 级别 23)中添加的
FLAG_IMMUTABLE
创建待处理 intent。此标志可防止接收PendingIntent
的应用填充未填充的属性。如果应用的minSdkVersion
为22
或更低,您可以使用以下代码同时提供安全性和兼容性: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 的每个部分都是一个单独的属性:scheme
、host
、port
和 path
:
<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 类型进行比较。规则如下:
- 如果 intent 既不包含 URI 也不包含 MIME 类型,则只有当过滤条件未指定任何 URI 或 MIME 类型时,该 intent 才能通过测试。
- 如果 intent 包含 URI 但不包含 MIME 类型(无论是显式还是可从 URI 推断出的 MIME 类型),则只有当其 URI 与过滤器的 URI 格式匹配且过滤器同样未指定 MIME 类型时,该 intent 才能通过测试。
- 如果 intent 包含 MIME 类型但不包含 URI,则只有当过滤条件列出相同的 MIME 类型且未指定 URI 格式时,该 intent 才能通过测试。
- 如果 intent 同时包含 URI 和 MIME 类型(显式或可从 URI 推断),则只有当该类型与过滤器中列出的类型匹配时,才能通过 MIME 类型部分的测试。如果测试的 URI 与过滤器中的 URI 相匹配,或者测试具有
content:
或file:
URI,但过滤器未指定 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
类的文档中所述。
您的应用可以采用与 Home 应用类似的方式使用 intent 匹配。
PackageManager
具有一组 query...()
方法,这些方法会返回可接受特定 intent 的所有组件;此外,PackageManager
还具有一系列类似的 resolve...()
方法,这些方法可确定响应 intent 的最佳组件。例如,queryIntentActivities()
会返回可执行作为实参传递的 intent 的所有 activity 的列表,而 queryIntentServices()
会返回类似的服务列表。这两种方法都不会激活组件,只会列出可以响应的组件。对于广播接收器,有一个类似的方法 queryBroadcastReceivers()
。