前言
这里可以查看一些常用 Intent 的定义和使用说明
Common Intents
一、Intent
Intent 是一个用来传递消息的对象,类似于 “中间人”。Intent 可以通过多种方式协助组件之间通信,但它的用法主要有以下三种:
启动 Activity
启动 Service
传递 Broadcast
二、Intent 类型
Intent 分为两种类型:
- 显式 Intent:按名称(完全限定类名)指定要启动的组件。 通常,你会在自己的应用中使用显式 Intent 来启动组件,这是因为你知道要启动的 Activity 或服务的类名。
使用显式 Intent 启动 Activity 或服务时,系统将立即启动指定的应用组件。
- 隐式 Intent :不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用。
使用隐式 Intent 时,Android 系统会将 Intent 的内容与在其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的组件。如果有多个 Intent 过滤器兼容,则系统会显示一个对话框,让用户选择要使用的应用。
Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。 例如,通过为 Activity 声明 Intent 过滤器,可以让其他应用使用某一特定类型的 Intent 启动 Activity。但是,如果没有为 Activity 声明任何 Intent 过滤器,则 Activity 只能通过显式 Intent 启动。
注意:为了确保应用的安全性,请始终使用显式 Intent 启动 Service,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务不安全是因为你无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会引发异常。
三、构建 Intent
Intent 对象携带了 Android 系统用来确定要启动哪个组件的信息(例如准确的组件名称或应当接收该 Intent 的组件类别),以及组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。
Intent 中包含的主要信息如下:
1. 组件名称(Component name)
用来指定要启动的组件名称。这是可选项,但也是构建显式 Intent 的一项重要信息,这意味着如需在应用中启动特定的组件,则应指定该组件的名称。 如果没有组件名称,则 Intent 是隐式的,且系统将根据其他 Intent 信息(例如,操作、数据和类别)决定哪个组件应当接收 Intent。
Intent 的这一字段是一个 ComponentName
对象,可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。 例如,com.example.ExampleActivity
。可以使用 setComponent()
、setClass()
、setClassName()
或 Intent 构造函数设置组件名称。
2. 操作(Action)
用来指定要执行的通用操作。对于广播 Intent,则是指已发生且将要报告的操作。
你可以指定自己的操作,供 Intent 在应用内使用(或者供其他应用调用你的应用组件)。但是更常用的是 Intent 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作:
ACTION_VIEW
如果你想使用某个 Activity 向用户显示一些信息(例如,使用图库查看照片;或者使用地图查看地址),则应在 Intent 中设置此操作。ACTION_SEND
这也被称为 “共享” Intent。如果你想与其他应用共享数据(例如,使用电子邮件应用或社交共享应用),则应在 Intent 中设置此操作。
可以使用 setAction()
或 Intent 构造函数为 Intent 指定操作。
如果定义自己的操作,请确保将应用的软件包名称作为前缀。 例如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
3. 数据(Data)
待操作数据的 URI(Uri 对象)引用或该数据 MIME 类型。提供的数据类型通常由 Intent 的操作决定。例如,如果操作是 ACTION_EDIT
,则数据应包含待编辑文档的 URI。
除了指定 URI,指定数据类型(其 MIME 类型)往往也很重要。例如,能够显示图像的 Activity 可能无法播放音频文件,即便 URI 格式十分类似。因此,指定数据的 MIME 类型有助于 Android 系统找到接收 Intent 的最佳组件。但有时,MIME 类型可以从 URI 中推断得出,特别当数据格式是 content: URI
时。这表明数据位于设备中,且由 ContentProvider
控制,这使得数据 MIME 类型对系统可见。
要仅设置数据 URI,请调用 setData()
。 要仅设置 MIME 类型,请调用 setType()
。如有必要,可以使用 setDataAndType()
同时显式设置二者。
注意:若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。
4. 类别(Category)
用来指定处理 Intent 的组件的类别。 可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 不需要设置类别。 以下是一些常见类别:
CATEGORY_BROWSABLE
目标 Activity 允许本身通过 web 浏览器启动,以显示链接引用的数据,如图像或电子邮件。CATEGORY_LAUNCHER
该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。
可以使用 addCategory()
指定类别。
以上列出的这些属性(组件名称、操作、数据和类别)表示 Intent 的既定特征。 通过读取这些属性,Android 系统能够解析应当启动哪个应用组件。
但是,Intent 也可以携带一些不影响其被解析的信息。例如,Intent 还可以提供:
5. Extras
用作附加信息的键值对。可以使用各种 putExtra()
方法添加 extra 数据,每种方法均接受两个参数:键名和值。你还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras()
将 Bundle 插入 Intent 中。
例如,使用 ACTION_SEND
创建用于发送电子邮件的 Intent 时,可以使用 EXTRA_EMAIL
键指定 “目标” 收件人,并使用 EXTRA_SUBJECT
键指定 “主题”。
Intent 类将为标准化的数据类型指定多个 EXTRA_*
常量。如需声明自己的 extra 键,请确保将应用的软件包名称作为前缀。 例如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
注意:请勿使用 Parcelable 或 Serializable 数据,如果你希望另一个应用程序接收该 Intent。当应用程序尝试访问 Bundle 对象中的数据,但无法访问序列化的类,则系统会引发一个 RuntimeException。
6. 标志(Flags)
定义在 Intent 类中,充当 Intent 的元数据。标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。
3.1 显式 Intent 示例
显式 Intent 是指用于启动某个特定应用组件的 Intent。 要创建显式 Intent,请为 Intent 对象定义组件名称,Intent 的所有其他属性均为可选属性。
例如,如果在应用中构建了一个名为 DownloadService 的服务,为了从网页下载文件,则可使用以下代码启动该服务:
// Executed in an Activity, so 'this' is the Context
// 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 类。
3.2 隐式 Intent 示例
隐式 Intent 能调用设备上任何应用的操作(如果能够执行)。如果自身应用无法执行该操作而其他应用可以,则使用隐式 Intent 非常有用。
例如,如果你希望用户与他人共享内容,请使用 ACTION_SEND
操作创建 Intent,并添加指定共享内容的 extra
。使用该 Intent 调用 startActivity() 时,用户可以选取可以执行共享操作的应用。
注意:用户可能没有任何能够处理发送到
startActivity()
的隐式 Intent。若出现这种情况,则调用将会失败,且应用会崩溃。要验证是否会有 Activity 接收该 Intent,可以对 Intent 对象调用resolveActivity()
。如果结果非空,则至少有一个应用能够处理该 Intent,且可以安全调用startActivity()
。 如果结果为空,则不应使用该 Intent。如有必要,你应该停用该 Intent 的功能。
下面的例子展示了如何验证 Intent 可以被一个 Activity 解析:
// 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");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
注意:上面的例子并没有使用 URI,但声明了 Intent 的数据类型,用于指定 extra 携带的内容。
调用 startActivity() 时,系统将检查已安装的所有应用,确定哪些应用能够处理这种 Intent(含 ACTION_SEND
操作并携带 “text/plain”
数据)。 如果只有一个应用能够处理,则该应用将立即打开并为接收该 Intent。 如果多个 Activity 接受 Intent,则系统将显示一个对话框,使用户能够选择要打开的应用。
3.3 强制使用应用选择器
如果有多个应用响应隐式 Intent,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。
然而,用户可能希望每次使用不同的应用,这时应该始终弹出选择器对话框。选择器对话框每次都会要求用户选择用于操作的应用(用户无法为该操作选择默认应用)。例如,当应用使用 ACTION_SEND
操作执行 “共享” 时,用户根据情况可能需要使用不同的应用,如下图:
要显示选择器,请使用 createChooser()
创建 Intent,并将其传递给 startActivity()
。例如:
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
要通知应用可以接收哪些隐式 Intent,需要在清单文件中使用
元素为每个应用组件声明一个或多个 Intent 过滤器。仅当隐式 Intent 可以通过 Intent 过滤器之一时,系统才会将该 Intent 传递给应用组件。
注意:显式 Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器是什么。
组件应该为每个作业声明单独的过滤器。例如,图库应用中的一个 Activity 可能会有两个过滤器,分别用于查看图像和编辑图像。 当 Activity 启动时,它将检查 Intent 并根据 Intent 中的信息决定具体的行为(例如,是否显示编辑器控件)。
每个 Intent 过滤器均由
元素定义,并嵌套在相应的应用组件(例如,
元素)中。 在
内部,可以使用以下三个元素中的一个或多个指定要接受的 Intent 类型:
-
在name
属性中,声明接受的 Intent 操作。该值必须是操作的文本字符串值,而不是类常量。
声明接受的数据类型,使用一个或多个属性指定数据 URI 各个方面(scheme、host、port、path 等)和 MIME 类型。
在name
属性中声明接受的 Intent 类别。该值必须是操作的文本字符串值,而不是类常量。
注意:为了接收隐式 Intent,必须将
CATEGORY_DEFAULT
类别包括在 Intent 过滤器中。startActivity() 和 startActivityForResult() 默认按照CATEGORY_DEFAULT
类别处理所有 Intent。 如果没有在 Intent 过滤器中声明此类别,则任何隐式 Intent 都不会被解析。
例如,以下是一个使用包含 Intent 过滤器的 Activity 声明,当数据类型为文本时,系统将接收 ACTION_SEND Intent :
可以创建包括多个
、或
实例的过滤器。只不过要确定组件能够处理所有这些过滤器元素及它们的组合。如果仅需要以
、和
的特定组合来处理多种 Intent,则需创建多个 Intent 过滤器。
五、限制对组件的访问
尽管 Intent 过滤器将组件限制为仅响应特定类型的隐式 Intent,但如果别人知道你的组件名称,则可能通过显式 Intent 启动你的组件。如果要确保组件不被其他应用启动,请把组件的 exported
属性设置为 "false"
。
隐式 Intent 若要传递给组件,必须通过
、 和
这三项测试。如果其中有一项不匹配,则 Android 系统不会将其传递给组件。 但是,一个组件可能有多个 Intent 过滤器,所以沒能通过某一组件过滤器的 Intent 可能会通过另一组过滤器。
注意:广播接收器的过滤器可以通过调用
registerReceiver()
动态注册。 之后可以使用unregisterReceiver()
注销该接收器。这样就可以仅在应用运行时的某一指定时间段内侦听特定的广播。
过滤器示例
下面是一个社交共享应用的清单文件中的代码片段。
MainActivity 是应用的主要入口点:
ACTION_MAIN
操作指示这是主要入口点,且不要求输入任何 Intent 数据。CATEGORY_LAUNCHER
类别指示此 Activity 的图标应放入系统的应用启动器。 如果
元素未使用icon
指定图标,则系统将使用
元素中的图标。
这两个元素必须配对使用,该 Activity 才会显示在应用启动器中。
ShareActivity 用于共享文本和媒体内容。用户通过 MainActivity 进入此 Activity,也可以使用隐式 Intent(与两个 Intent 过滤器之一匹配)从其他应用直接进入 ShareActivity。
注意:MIME 类型
application/vnd.google.panorama360+jpg
是一个指定全景照片的特殊数据类型,可以使用 Google panorama API 对其进行处理。
六、使用 Pending Intent
PendingIntent 对象是 Intent 对象的包装。PendingIntent 的主要目的是授权外部应用使用其包含的 Intent,就像是在你自己的应用内执行的一样。
PendingIntent 的主要用例包括:
声明用户使用你的通知执行操作时要执行的 Intent(Android 系统的
NotificationManager
执行该 Intent)。声明用户使用你的应用部件执行操作时要执行的 Intent(主屏幕应用执行该 Intent)。
声明未来某一特定时间要执行的 Intent(Android 系统的
AlarmManager
执行该 Intent)。
由于每个 PendingIntent 对象均设计为由特定类型的应用组件(Activity、Service 或 BroadcastReceiver)进行处理,因此使用 PendingIntent 时,应用不会通过调用(如 startActivity())执行该 Intent。相反,创建 PendingIntent 时,必须声明所需的组件类型:
PendingIntent.getActivity()
,适用于启动 Activity 的 Intent。PendingIntent.getService()
,适用于启动 Service 的 Intent。PendingIntent.getBroadcast()
,适用于启动 BroadcastReceiver 的 Intent。
除非你的应用将要从其他应用中接收待定 Intent,否则上面用于创建 PendingIntent
的方法可能是你所需的唯一 PendingIntent
方法。
每种方法均会提取当前的应用 Context
、要包装的 Intent 以及一个或多个指定应如何使用该 Intent 的标志(例如,是否可以多次使用该 Intent)。
七、Intent 解析
当系统收到隐式 Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,搜索该 Intent 的最佳 Activity:
Action(操作).
Data(数据,包括 URI 和数据类型).
Category(类别).
1. 操作测试(Action test)
Intent 过滤器可以声明零个或多个
元素。例如:
...
Intent 中指定的操作必须与过滤器中列出的某一操作匹配,才能通过该过滤器。如果该过滤器未列出任何操作,则 Intent 没有任何匹配项,因此所有 Intent 均无法通过测试。 但是,如果 Intent 未指定操作,则会通过测试(只要过滤器至少包含一个操作)。
2. 类别测试(Category test)
Intent 过滤器可以声明零个或多个
元素。例如:
...
若要使 Intent 通过类别测试,则 Intent 中的每个类别均必须与过滤器中的类别匹配。反之不然,Intent 过滤器声明的类别可以超出 Intent 中指定的数量,且 Intent 仍会通过测试。因此,不含类别的 Intent 始终会通过测试,无论过滤器中声明了什么类别。
3. 数据测试(Data test)
Intent 过滤器可以声明零个或多个 元素。例如:
...
每个 元素均可指定 URI 结构和数据类型(MIME 媒体类型)。 URI 的每个部分均包含单独的
scheme
、host
、port
和 path
属性:
://:/
例如:
content://com.example.project:200/folder/subfolder/etc
在 元素中,上述每个属性均为可选,但存在线性依赖关系:
如果未指定
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 才会通过过滤器。
注意:路径可以包含星号通配符 (*),因此仅需部分匹配路径名即可。
数据测试会将 Intent 中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。 规则如下:
仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 Intent 才会通过测试。
对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 Intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。
仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型、但不含 URI 的 Intent 才会通过测试。
仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。 如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有
content:
或file: URI
且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。 换言之,如果过滤器只是列出 MIME 类型,则假定组件支持content:
和file:
数据。
最后一条规则[4],反映了组件期望能够从文件中或内容提供程序获得本地数据。过滤器可以仅列出数据类型,而不必显式命名 content:
和 file: scheme
。例如,下文中的 元素向 Android 指出,组件可从内容提供器获得并显示图像数据:
...
由于大部分可用数据均由内容提供器分发,因此指定数据类型(而非 URI)的过滤器最为常见。
另一常见的配置是具有模式和数据类型的过滤器。例如,下文中的 元素向 Android 指出,组件可从网络中检索视频数据以执行操作:
...
八、Intent 匹配
通过 Intent 过滤器匹配 Intent,不仅有助于发现要激活的目标组件,还有助于收集设备上所有组件的信息。 例如,主页应用通过指定 ACTION_MAIN
操作和 CATEGORY_LAUNCHER
类别的 Intent 过滤器查找所有用作启动的 Activity。
我们也可以采用类似的方式使用 Intent 匹配。PackageManager
提供了一整套 query...()
方法来返回所有能够接受特定 Intent 的组件。此外,它还提供了一系列类似的 resolve...()
方法来确定响应 Intent 的最佳组件。 例如,queryIntentActivities()
将返回所有能够执行作为参数传递的 Intent 的 Activity 列表,而 queryIntentServices()
则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。 对于广播接收器,有一种类似的方法: queryBroadcastReceivers()
。