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 启动该 activity。
注意:为了确保应用的安全性,在启动 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 向用户显示的信息(例如要在图库应用中查看的照片或要在地图应用中查看的地址)时,请将此操作与
startActivity()
搭配使用。 ACTION_SEND
- 该 intent 也称为分享 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";
- 数据
- URI(
Uri
对象),用于引用待操作数据和/或该数据的 MIME 类型。提供的数据类型通常由 intent 的操作决定。例如,如果操作为ACTION_EDIT
,则数据应包含要修改的文档的 URI。创建 intent 时,除了指定 URI 之外,通常还需要指定数据类型(其 MIME 类型)。例如,即使 URI 格式可能类似,能够显示图片的 activity 也可能无法播放音频文件。指定数据的 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),请确保将应用的软件包名称作为前缀,如以下示例所示: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。如果没有其他应用可以处理该情况,您的应用可以捕获发生的 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。
例如,以下是一个包含 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 启动您的应用组件。如果只有您自己的应用才能启动您的某个组件,这一点至关重要,请勿在清单中声明 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,请参阅每个相应用例的文档,例如通知和应用 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.
尽可能创建不可变的待处理 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 类型时,才会通过测试。
- 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不包含 URI 的 intent 才会通过测试。
- 仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型(通过显式声明,或可通过 URI 推断得出)的 intent 才会通过测试的 MIME 类型部分。如果相应 URI 的 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()
方法。