Intent
是一个消息传递对象,可用于从其他应用组件请求操作。虽然 intent 可以通过多种方式促进组件之间的通信,但基本用例有三个:
- 启动 activity
Activity
表示应用中的单个屏幕。您可以通过将Intent
传递给startActivity()
来启动Activity
的新实例。Intent
描述了要启动的 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 组件时,系统会立即启动该组件。
当您使用隐式 intent 时,Android 系统会通过比较相应 intent 的内容与设备上其他应用的清单文件中声明的 intent 过滤器,找到要启动的相应组件。如果 intent 与 intent 过滤器匹配,系统会启动该组件,并向其传递 Intent
对象。如果多个 intent 过滤器兼容,系统会显示一个对话框,以便用户选择要使用的应用。
intent 过滤器是应用清单文件中的一个表达式,用于指定该组件要接收的 intent 类型。例如,通过为 activity 声明 intent 过滤器,您可以让其他应用可以直接使用某种类型的 intent 启动您的 activity。同样,如果您没有为 activity 声明任何 intent 过滤器,则只能使用显式 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()
方法添加额外数据,每种方法都接受两个参数:键名和值。您还可以创建一个包含所有额外数据的Bundle
对象,然后使用putExtras()
在Intent
中插入Bundle
。例如,使用
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 时,请勿使用
Parcelable
或Serializable
数据。如果应用尝试访问Bundle
对象中的数据,但无法访问打包或序列化的类,则系统会引发RuntimeException
。 - 标志
- 标志在
Intent
类中定义,用作 intent 的元数据。这些标志可以指示 Android 系统如何启动 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。如果没有其他应用可以处理它,您的应用可以捕获发生的 ActivityNotFoundException
。如果多个 activity 接受该 intent,系统会显示一个对话框(如图 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 是在另一个 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
明确设置值。此属性指示其他应用是否可以访问该应用组件。在某些情况下(例如,activity 的 intent 过滤器包含 LAUNCHER
类别),将此属性设置为 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,但也可以直接从发出隐式 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 与应用声明的 <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 的主要用例包括:
- 声明用户使用您的 Notification 执行操作时要执行的 intent(Android 系统的
NotificationManager
执行Intent
)。 - 声明用户使用您的应用微件执行操作时要执行的 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,请参阅每个相应用例的文档,例如 Notifications 和 App Widgets 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 解析
当系统收到隐式 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 的每个部分都是一个单独的属性: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 类型进行比较。相关规则如下:
- 仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 intent 才会通过测试。
- 对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 intent,仅当其 URI 与过滤器的 URI 格式匹配,且过滤器同样未指定 MIME 类型时,该 intent 才会通过测试。
- 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不包含 URI 的 intent 才会通过测试。
- 仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 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 该组件可以从 content provider 获取并显示图片数据:
<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 过滤器进行匹配,这不仅是为了发现要激活的目标组件,还有助于发现设备上一组组件的相关信息。例如,Home 应用会使用指定 ACTION_MAIN
操作和 CATEGORY_LAUNCHER
类别的 intent 过滤器查找所有 activity,从而填充应用启动器。
如 IntentFilter
类的文档中所述,仅当 intent 中的操作和类别与过滤器匹配时,匹配才会成功。
您的应用可以使用与 Home 应用类似的方式使用 intent 匹配。PackageManager
包含一组 query...()
方法(用于返回可接受特定 intent 的所有组件)和一系列类似的 resolve...()
方法(用于确定响应 intent 的最佳组件)。例如,queryIntentActivities()
会返回一个列表,其中包含可以执行作为参数传递的 intent 的所有 activity,而 queryIntentServices()
会返回一个类似的服务列表。这两种方法都不会激活组件,而只会列出能够响应的组件。对于广播接收器,有一种类似的方法 queryBroadcastReceivers()
。