Intent
是一种消息传递对象,可用于从其他应用组件请求操作。虽然 intent 可通过多种方式促进组件之间的通信,但主要有以下三种用例:
- 启动 activity
Activity
表示应用中的单个界面。您可以通过将Intent
传递给startActivity()
来启动Activity
的新实例。Intent
用于描述要启动的 activity,并携带所有必要的数据。如果您想在 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 组件时,系统会立即启动该组件。

图 1. 如何通过系统传递隐式 intent 以启动其他 activity:[1] activity A 创建包含操作说明的 Intent
,并将其传递给 startActivity()
。[2] Android 系统会搜索所有应用,以查找与 intent 匹配的 intent 过滤器。找到匹配项后,[3] 系统会通过调用匹配 activity 的 onCreate()
方法并将 Intent
传递给该 activity 来启动匹配 activity (Activity B)。
当您使用隐式 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
构造函数设置组件名称。 - 操作
- 一个字符串,用于指定要执行的通用操作(例如查看或选择)。
对于广播 intent,这是发生的操作,并且正在报告该操作。操作在很大程度上决定了 intent 的其余部分的结构,尤其是数据和 extra 中包含的信息。
您可以指定自己的操作,以供应用内的 intent 使用(或供其他应用调用您应用中的组件),但通常会指定由
Intent
类或其他框架类定义的操作常量。以下是启动 activity 的一些常见操作:ACTION_VIEW
- 如果 activity 可以向用户显示一些信息(例如要在图库应用中查看的照片或要在地图应用中查看的地址),请在 intent 中使用此操作并附加
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";
- 数据
- 引用要执行操作的数据和/或该数据的 MIME 类型的 URI(一个
Uri
对象)。所提供数据的类型通常由 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
键指定收件人,并使用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 所示),以便用户选择要使用的应用。
如需详细了解如何启动其他应用,请参阅有关将用户转到其他应用的指南。

图 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 必须通过这三项测试。如果它与其中任一项都不匹配,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-filter>
元素的操作和类别匹配时,系统才会将这些 intent 传递给应用的导出组件。无论发送应用的目标 SDK 版本如何,都会出现这种行为。
在以下情况下,系统不会强制执行 intent 匹配:
- 传送到未声明任何 intent 过滤器的组件中的 intent。
- 源自同一应用内的 intent。
- 源自系统的 intent;也就是说,从“系统 UID”(uid=1000) 发送的 intent。系统应用包括
system_server
和将android:sharedUserId
设置为android.uid.system
的应用。 - 源自根的 intent。
详细了解intent 匹配。
使用待定 Intent
PendingIntent
对象是 Intent
对象的封装容器。PendingIntent
的主要用途是为外部应用授予使用所含 Intent
的权限,就像该 Intent
是从应用自己的进程中执行一样。
待定 Intent 的主要用例包括:
- 声明在用户通过通知执行操作时要执行的 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 解析
当系统收到用于启动 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 部分进行比较。例如:
- 如果过滤器仅指定了 scheme,则所有采用该 scheme 的 URI 都与过滤器匹配。
- 如果过滤器指定了 scheme 和 authority,但未指定路径,则所有具有相同 scheme 和 authority 的 URI 都会通过过滤器,无论其路径如何。
- 如果过滤器指定了 scheme、authority 和路径,则只有具有相同 scheme、authority 和路径的 URI 才能通过过滤器。
注意:路径规范可以包含通配符星号 (*),以要求仅与路径名称部分匹配。
数据测试会将 intent 中的 URI 和 MIME 类型与过滤条件中指定的 URI 和 MIME 类型进行比较。具体规则如下:
- 只有当过滤器未指定任何 URI 或 MIME 类型时,既不包含 URI 也不包含 MIME 类型的 intent 才会通过测试。
- 如果 intent 包含 URI 但没有 MIME 类型(既不是显式 MIME 类型,也无法从 URI 推断出 MIME 类型),只有当其 URI 与过滤条件的 URI 格式匹配且过滤条件同样未指定 MIME 类型时,该 intent 才会通过测试。
- 只有当过滤条件列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不包含 URI 的 intent 才能通过测试。
- 如果 intent 同时包含 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 该组件可以从内容提供程序获取图片数据并显示该数据的典型情况:
<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 过滤器进行匹配,不仅是为了发现要激活的目标组件,还为了发现设备上这组组件的相关信息。例如,“家庭”应用会通过查找 intent 过滤器指定 ACTION_MAIN
操作和 CATEGORY_LAUNCHER
类别的所有 activity 来填充应用启动器。只有当 intent 中的操作和类别与过滤器匹配时,匹配才会成功,如 IntentFilter
类文档中所述。
您的应用可以使用与 Home 应用类似的方式使用 intent 匹配。PackageManager
包含一组 query...()
方法,用于返回可以接受特定 intent 的所有组件,以及一组类似的 resolve...()
方法,用于确定响应 intent 的最佳组件。例如,queryIntentActivities()
会返回可执行作为参数传递的 intent 的所有 activity 的列表,而 queryIntentServices()
会返回类似的服务列表。这两种方法都不会激活组件,只会列出可以响应的组件。广播接收器也有类似的方法 queryBroadcastReceivers()
。