一个 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
传递给startService()
来启动服务以执行一次性操作(例如下载文件)。该Intent
描述要启动的服务,并携带任何必要的数据。如果服务的设计采用客户端-服务器接口,则可以通过将
Intent
传递给bindService()
从另一个组件绑定到服务。有关更多信息,请参阅 服务 指南。 - 传送广播
广播是任何应用都可以接收的消息。系统会针对系统事件(例如系统启动或设备开始充电时)传送各种广播。您可以通过将
Intent
传递给sendBroadcast()
或sendOrderedBroadcast()
来向其他应用传送广播。
本页的其余部分解释了意图的工作原理以及如何使用它们。有关相关信息,请参阅 与其他应用交互 和 共享内容。
意图类型
意图有两种类型
- 显式意图通过指定完整的
ComponentName
来指定哪个应用程序的哪个组件将满足意图。您通常会使用显式意图启动您自己应用程序中的组件,因为您知道要启动的活动或服务的类名。例如,您可以在响应用户操作时启动应用程序内的新的活动,或者启动一个在后台下载文件的服务。 - 隐式意图不命名特定的组件,而是声明要执行的通用操作,这允许来自另一个应用程序的组件来处理它。例如,如果您想向用户显示地图上的位置,您可以使用隐式意图来请求另一个能够处理该意图的应用程序在地图上显示指定位置。
图1显示了启动活动时如何使用意图。当Intent
对象显式地命名特定的活动组件时,系统会立即启动该组件。
当您使用隐式意图时,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 系统找到最佳组件来接收您的意图。但是,有时可以从 URI 推断出 MIME 类型——尤其是在数据是
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 系统能够解析它应该启动哪个应用程序组件。但是,意图可以携带不会影响其解析为应用程序组件的方式的附加信息。意图还可以提供以下信息:
- 额外信息
- 键值对,携带完成请求操作所需的附加信息。就像某些操作使用特定类型的数据 URI 一样,某些操作也使用特定的额外信息。
您可以使用各种
putExtra()
方法添加额外数据,每个方法都接受两个参数:键名和值。您还可以创建一个包含所有额外数据的Bundle
对象,然后使用putExtras()
将Bundle
插入Intent
。例如,当创建使用
ACTION_SEND
发送电子邮件的意图时,您可以使用EXTRA_EMAIL
键指定收件人,并使用EXTRA_SUBJECT
键指定主题。Intent
类为标准化数据类型指定了许多EXTRA_*
常量。如果您需要声明自己的额外键(用于您的应用程序接收的意图),请务必包含应用程序的包名作为前缀,如下例所示: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 系统如何启动活动(例如,活动应该属于哪个任务)以及如何在启动后处理它(例如,它是否属于最近活动列表)。有关更多信息,请参阅
setFlags()
方法。
显式 Intent 示例
显式 Intent 用于启动特定的应用组件,例如应用中的特定活动或服务。要创建显式 Intent,请为Intent
对象定义组件名称——所有其他 Intent 属性都是可选的。
例如,如果您在应用中构建了一个名为DownloadService
的服务,用于从 Web 下载文件,则可以使用以下代码启动它
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,并添加指定要共享的内容的额外信息。当您使用该 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
。如果多个活动接受 Intent,系统会显示如图 2 所示的对话框,以便用户可以选择要使用的应用。
有关启动其他应用的更多信息,还可在关于将用户发送到另一个应用的指南中找到。
强制使用应用选择器
当有多个应用响应您的隐式 Intent 时,用户可以选择要使用的应用,并将其设置为该操作的默认选择。当执行用户可能每次都想要使用同一应用的操作时(例如打开网页时,用户通常只喜欢一个 Web 浏览器),选择默认值的功能很有用。
但是,如果多个应用可以响应 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冲突
- 您的应用从已传递 Intent 的额外信息中解包嵌套 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 中必不可少的额外信息,并执行任何必要的清理和验证。您的应用可能会将额外信息从一个 Intent 复制到另一个用于启动新组件的 Intent。当您的应用调用putExtras(Intent)
或putExtras(Bundle)
时,就会发生这种情况。如果您的应用执行其中一项操作,则仅复制接收组件期望的额外信息。如果另一个 Intent(接收副本的 Intent)启动的组件未导出,请在将额外信息复制到启动组件的 Intent 之前对其进行清理和验证。
不要不必要地导出应用的组件。例如,如果您打算使用内部嵌套 Intent 启动应用组件,请将该组件的android:exported
属性设置为false
。
使用PendingIntent
代替嵌套 Intent。这样,当另一个应用解包其包含的Intent
的PendingIntent
时,另一个应用可以使用您的应用的身份启动PendingIntent
。此配置允许另一个应用安全地启动应用中的任何组件,包括未导出的组件。
图 2 中的图表显示了系统如何将控制权从您的(客户端)应用传递到另一个(服务)应用,然后再传递回您的应用
- 您的应用创建一个调用另一个应用中活动的 Intent。在此 Intent 中,您添加一个
PendingIntent
对象作为额外信息。此挂起 Intent 调用应用中的组件;此组件未导出。 - 收到您的应用的 Intent 后,另一个应用会提取嵌套的
PendingIntent
对象。 - 另一个应用在
PendingIntent
对象上调用send()
方法。 - 将控制权传递回您的应用后,系统会使用您的应用的上下文调用挂起的 Intent。
图 2. 使用嵌套挂起 Intent 时应用间通信的图表。
接收隐式 Intent
要宣传您的应用可以接收哪些隐式 Intent,请为每个应用组件声明一个或多个 Intent 过滤器,并在您的清单文件中使用<intent-filter>
元素。每个 Intent 过滤器都根据 Intent 的操作、数据和类别指定它接受的 Intent 类型。只有当 Intent 可以通过您的一个 Intent 过滤器时,系统才会将隐式 Intent 传递到您的应用组件。
注意:显式 Intent 始终会传递到其目标,无论组件声明了哪些 Intent 过滤器。
应用组件应为其可以执行的每个唯一任务声明单独的过滤器。例如,图像库应用中的一个活动可能有两个过滤器:一个过滤器用于查看图像,另一个过滤器用于编辑图像。活动启动时,它会检查Intent
并根据Intent
中的信息决定如何运行(例如是否显示编辑器控件)。
每个 Intent 过滤器都由应用清单文件中的<intent-filter>
元素定义,嵌套在相应的应用组件(例如<activity>
元素)中。
在每个包含`<intent-filter>
` 元素的应用程序组件中,显式设置`android:exported
`的值。此属性指示应用程序组件是否可供其他应用程序访问。在某些情况下,例如意图过滤器包含`LAUNCHER
` 类别的活动,将此属性设置为`true
` 很有用。否则,将此属性设置为`false
` 更安全。
警告:如果应用程序中的活动、服务或广播接收器使用意图过滤器并且没有显式设置`android:exported
` 的值,则您的应用程序无法安装在运行 Android 12 或更高版本的设备上。
在`<intent-filter>
`内部,您可以使用以下三个元素中的一个或多个来指定要接受的意图类型
<action>
- 在`
name
` 属性中声明要接受的意图操作。该值必须是操作的字面字符串值,而不是类常量。 <data>
- 使用一个或多个属性声明要接受的数据类型,这些属性指定数据的各种方面 URI(`
scheme
`、`host
`、`port
`、`path
`)和 MIME 类型。 <category>
- 在`
name
` 属性中声明要接受的意图类别。该值必须是操作的字面字符串值,而不是类常量。注意:要接收隐式意图,您必须在意图过滤器中包含`
CATEGORY_DEFAULT
` 类别。方法`startActivity()
` 和`startActivityForResult()
` 将所有意图视为已声明`CATEGORY_DEFAULT
` 类别。如果您的意图过滤器中未声明此类别,则没有任何隐式意图将解析到您的活动。
例如,这是一个活动声明,其中包含一个意图过滤器,用于在数据类型为文本时接收`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
`,请始终使用显式意图启动您自己的服务。
注意:对于所有活动,您必须在清单文件中声明您的意图过滤器。但是,广播接收器的过滤器可以通过调用`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>
第一个活动`MainActivity
` 是应用程序的主要入口点——当用户使用启动器图标首次启动应用程序时打开的活动。
- `
ACTION_MAIN
` 操作表示这是主要入口点,并且不期望任何意图数据。 - `
CATEGORY_LAUNCHER
` 类别表示此活动的图标应放置在系统的应用程序启动器中。如果`<activity>
` 元素没有使用`icon
` 指定图标,则系统使用`<application>
` 元素中的图标。
这两个必须配对才能使活动出现在应用程序启动器中。
第二个活动`ShareActivity
` 用于促进文本和媒体内容的共享。尽管用户可能会通过从`MainActivity
` 导航到它来进入此活动,但他们也可以直接从发出与两个意图过滤器之一匹配的隐式意图的其他应用程序进入`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
`,就像它从您自己的应用程序进程中执行一样。
挂起意图的主要用例包括:
- 声明在用户使用您的通知(Android 系统的`
NotificationManager
` 执行`Intent
`)执行操作时要执行的意图。 - 声明在用户使用您的应用程序小部件(主屏幕应用程序执行`
Intent
`)执行操作时要执行的意图。 - 声明在指定未来时间执行的意图(Android 系统的`
AlarmManager
` 执行`Intent
`)。
就像每个`Intent
` 对象都设计为由特定类型的应用程序组件(活动、服务或广播接收器)处理一样,`PendingIntent
` 也必须以同样的考虑来创建。使用挂起意图时,您的应用程序不会使用诸如`startActivity()
` 的调用来执行意图。相反,您必须在创建`PendingIntent
` 时通过调用相应的创建器方法来声明预期的组件类型。
- 用于启动
Activity
的Intent
的PendingIntent.getActivity()
。 - 用于启动
Service
的Intent
的PendingIntent.getService()
。 - 用于启动
BroadcastReceiver
的Intent
的PendingIntent.getBroadcast()
。
除非您的应用正在接收来自其他应用的 PendingIntent,否则以上创建PendingIntent
的方法可能是您唯一需要的PendingIntent
方法。
每个方法都接收当前应用的Context
、您想要包装的Intent
以及一个或多个标志,这些标志指定 Intent 的使用方法(例如,Intent 是否可以多次使用)。
有关使用 PendingIntent 的更多信息,请参阅每个用例的文档,例如通知和App Widget 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.
尽可能创建不可变的 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_TASK
和FLAG_ACTIVITY_NEW_DOCUMENT
。 - 通过调用
requestLocationUpdates()
或类似的 API 来请求设备位置信息。可变PendingIntent
对象允许系统添加表示位置生命周期事件的 Intent 附加信息。这些事件包括位置更改和提供程序可用。 - 使用
AlarmManager
安排闹钟。可变PendingIntent
对象允许系统添加EXTRA_ALARM_COUNT
Intent 附加信息。此附加信息表示重复闹钟已触发的次数。通过包含此附加信息,Intent 可以准确地通知应用重复闹钟是否被触发多次,例如当设备处于休眠状态时。
如果您的应用创建了可变的PendingIntent
对象,强烈建议您使用显式 Intent并填写ComponentName
。这样,每当另一个应用调用PendingIntent
并将控制权返回到您的应用时,您的应用中的同一组件始终会启动。
在 PendingIntent 中使用显式 Intent
为了更好地定义其他应用如何使用您的应用的 PendingIntent,请始终围绕显式 Intent包装 PendingIntent。为了遵循此最佳实践,请执行以下操作:
- 检查基本 Intent 的 action、package 和 component 字段是否已设置。
-
使用在 Android 6.0(API 级别 23)中添加的
FLAG_IMMUTABLE
来创建 PendingIntent。此标志可防止接收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 过滤器的 Activity 进行比较,来搜索最适合该 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 中,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 类型进行比较。规则如下:
- 既不包含 URI 也未包含 MIME 类型的 Intent 仅当过滤器未指定任何 URI 或 MIME 类型时才能通过测试。
- 包含 URI 但不包含 MIME 类型(既没有显式 MIME 类型,也无法从 URI 推断出 MIME 类型)的 Intent 仅当其 URI 与过滤器的 URI 格式匹配且过滤器同样未指定 MIME 类型时才能通过测试。
- 仅当过滤器列出了相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不包含 URI 的 Intent 才能通过测试。
- 包含 URI 和 MIME 类型(显式或可从 URI 推断)的 Intent 只有在该类型与过滤器中列出的类型匹配时,才能通过测试的 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 过滤器的所有活动来填充应用程序启动器。只有当 Intent 中的操作和类别与过滤器匹配时,匹配才成功,如 IntentFilter
类文档中所述。
您的应用程序可以使用类似于主屏幕应用程序的方式进行 Intent 匹配。PackageManager
有一组 query...()
方法,这些方法返回所有可以接受特定 Intent 的组件,以及一系列类似的 resolve...()
方法,这些方法确定响应 Intent 的最佳组件。例如,queryIntentActivities()
返回可以执行作为参数传递的 Intent 的所有活动的列表,而 queryIntentServices()
返回类似的服务列表。这两种方法都不会激活组件;它们只是列出可以响应的组件。对于广播接收器,还有类似的方法 queryBroadcastReceivers()
。