Intent 和 Intent 过滤器
标签(空格分隔): Android开发 AndroidAPI指南
Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:
- 启动 Activity:
- 启动服务:
- 传递广播:
Intent 类型
Intent分为两种类型:
- 显式Intent
创建显式 Intent 启动 Activity 或服务时,系统将立即启动 Intent 对象中指定的应用组件。 - 隐式Intent
创建隐式 Intent时,Android系统通过将Intent的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
注意:为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会引发异常。
构建 Intent
Intent 中包含的主要信息如下:- 组件名称(Component name)
如需在应用中启动特定的组件,则应指定该组件的名称。如果没有组件名称,则 Intent 是隐式的,且系统将根据其他 Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。
注:启动 Service 时,您应始终指定组件名称。否则,您无法确定哪项服务会响应 Intent,且用户无法看到哪项服务已启动。Intent 的这一字段是一个 ComponentName 对象,您可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。 例如, com.example.ExampleActivity。您可以使用 setComponent()、setClass()、setClassName() 或 Intent 构造函数设置组件名称。
操作(action)
指定要执行的通用操作(例如,“查看”或“选取”)的字符串。您可以使用 setAction() 或 Intent 构造函数为 Intent 指定操作。
如果定义自己的操作,请确保将应用的软件包名称作为前缀。 例如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
数据(Data)
data的语法如下所示。<data android:scheme="string" android:host="string" android:port="string" android:path="string" android:pathPattern="string" android:pathPrefix=string" android:mimeType="string" />
data由两部分组成,mimeType和URI。
mimeType指媒体类型,比如image/jepg、audio/mpeg4和video/*等,可以表示图片、文本、视频等不同的媒体格式。有时,MIME类型可以从URI中推断得出,特别当数据是 content: URI时尤其如此。这表明数据位于设备中,且由ContentProvider 控制,这使得数据 MIME 类型对系统可见。
URI主要分三个部分:scheme, authority and path。其中authority又分为host和port。格式如下:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
举个实际的例子:
content://com.example.project:200/folder/subfolder/etc http://www.baidu.com:80/search/info
Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,这也意味着URI是无效的。
Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。
Port:URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
Path、pathPattern和pathPrefix:这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“”,“”表示0个或多个任意字符。需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“”要写成“\”,“\”要写成“\\”;pathPrefix表示路径的前缀信息。
要仅设置数据 URI,请调用 setData()。 要仅设置 MIME 类型,请调用 setType()。如有必要,您可以使用 setDataAndType() 同时显式设置二者。
注意:若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。
类别(Category)
您可以使用 addCategory() 指定类别。Extra
携带完成请求操作所需的附加信息的键值对。
您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。例如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
标志
在 Intent 类中定义的、充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。例如:
Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_SINGLE_TOP Intent.FLAG_ACTIVITY_CLEAR_TOP
显式 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 示例
例如,如果您希望用户与他人共享您的内容,请使用 ACTION_SEND 操作创建 Intent,并添加指定共享内容的 extra。 使用该 Intent 调用 startActivity() 时,用户可以选取共享内容所使用的应用。
// 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);
}
注意:用户可能没有任何应用处理您发送到 startActivity() 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 Intent,请对Intent对象调用resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。 如果结果为空,则不应使用该 Intent。如有可能,您应停用发出该 Intent 的功能。
强制使用应用选择器
要显示选择器,请使用 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);
}
这将显示一个对话框,其中有响应传递给 createChooser() 方法的 Intent 的应用列表,并且将提供的文本用作对话框标题。
接收隐式 Intent
每个 Intent 过滤器均由应用清单文件中的 <intent-filter>
元素定义,并嵌套在相应的应用组件(例如,<activity>
元素)中。 在 <intent-filter>
内部,您可以使用以下三个元素中的一个或多个指定要接受的 Intent 类型:
<action>
在 name 属性中,声明接受的Intent操作。该值必须是操作的文本字符串值,而不是类常量。
<data>
使用一个或多个指定数据 URI 各个方面(scheme、host、port、path 等)和 MIME 类型的属性,声明接受的数据类型。
<category>
在 name 属性中,声明接受的Intent类别。该值必须是操作的文本字符串值,而不是类常量。
注:为了接收隐式 Intent,必须将 CATEGORY_DEFAULT 类别包括在 Intent 过滤器中。 方法 startActivity() 和 startActivityForResult() 将按照已申明 CATEGORY_DEFAULT 类别的方式处理所有 Intent。 如果未在 Intent 过滤器中声明此类别,则隐式 Intent 不会解析为您的 Activity。
系统通过将 Intent 与所有这三个元素进行比较,根据过滤器测试隐式 Intent。 隐式 Intent 若要传递给组件,必须通过所有这三项测试。如果 Intent 甚至无法匹配其中任何一项测试,则 Android 系统不会将其传递给组件。 但是,由于一个组件可能有多个 Intent 过滤器,因此未能通过某一组件过滤器的 Intent 可能会通过另一过滤器。
例如,以下是一个使用包含 Intent 过滤器的 Activity 声明,当数据类型为文本时,系统将接收 ACTION_SEND Intent :
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
过滤器示例
为了更好地了解一些 Intent 过滤器的行为,我们一起来看看从社交共享应用的清单文件中截取的以下片段。
<activity android:name="MainActivity">
<!-- 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">
<!-- 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 指定图标,则系统将使用 元素中的图标。
这两个元素必须配对使用,Activity 才会显示在应用启动器中。
第二个 Activity ShareActivity 旨在便于共享文本和媒体内容。 尽管用户可以通过从 MainActivity 导航进入此 Activity,但也可以从发出隐式 Intent(与两个 Intent 过滤器之一匹配)的另一应用中直接进入 ShareActivity。
注:MIME 类型 application/vnd.google.panorama360+jpg 是一个指定全景照片的特殊数据类型,您可以使用 Google panorama API 对其进行处理。
使用待定 Intent
PendingIntent 对象是 Intent 对象的包装器。PendingIntent 的主要目的是授权外部应用使用包含的 Intent,就像是它从您应用本身的进程中执行的一样。
待定 Intent 的主要用例包括:
- 声明用户使用您的通知执行操作时所要执行的 Intent(Android 系统的 NotificationManager 执行 Intent)。
- 声明用户使用您的 应用小部件执行操作时要执行的 Intent(主屏幕应用执行 Intent)。
- 声明未来某一特定时间要执行的 Intent(Android 系统的 AlarmManager 执行 Intent)。
PendingIntent.getActivity(),适用于启动 Activity 的 Intent。
PendingIntent.getService(),适用于启动 Service 的 Intent。
PendingIntent.getBroadcast(),适用于启动 BroadcastReceiver 的 Intent。
Intent 解析
当系统收到隐式 Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,搜索该 Intent 的最佳 Activity:
- Intent 操作
- Intent 数据(URI 和数据类型)
- Intent 类别
下文根据如何在应用的清单文件中声明 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中的每个类别均必须与过滤器中的类别匹配,反之则未必然。
注:Android 会自动将 CATEGORY_DEFAULT 类别应用于传递给 startActivity() 和 startActivityForResult() 的所有隐式 Intent。因此,如需 Activity 接收隐式 Intent,则必须将 “android.intent.category.DEFAULT” 的类别包括在其 Intent 过滤器中(如上文的 示例所示)。
数据测试
要指定接受的 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 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。 如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有 content: 或 file: URI 且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。 换言之,如果过滤器只是列出 MIME 类型,则假定组件支持 content: 和 file: 数据。
总结(weijie)
这四条比较规则可已概括为:
1. Intent的URI和MIME必须和过滤器中指定的URI和MIME一一对应。
2. 过滤器中URI默认值为content: 或file:
Intent 匹配
通过 Intent 过滤器匹配Intent,这不仅有助于发现要激活的目标组件,还有助于发现设备上组件集的相关信息。 例如,主页应用通过使用指定 ACTION_MAIN 操作和 CATEGORY_LAUNCHER 类别的 Intent 过滤器查找所有 Activity,以此填充应用启动器。 例如:- queryIntentActivities():返回能够执行那些作为参数传递的 Intent 的所有 Activity 列表
- queryIntentServices():返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。
- queryBroadcastReceivers()。
Common Intents
闹钟
日历
相机
联系人/人员应用
电子邮件
文件存储
本地操作
地图
音乐或视频
新笔记
电话
搜索
设置
发送短信
网络浏览器
使用 Android 调试桥验证 Intent
闹钟
创建闹钟
示例 Intent:
public void createAlarm(String message, int hour, int minutes) {
Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
.putExtra(AlarmClock.EXTRA_MESSAGE, message)
.putExtra(AlarmClock.EXTRA_HOUR, hour)
.putExtra(AlarmClock.EXTRA_MINUTES, minutes);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
注:
为了调用 ACTION_SET_ALARM Intent,您的应用必须具有 SET_ALARM 权限:
创建定时器
示例 Intent:
public void startTimer(String message, int seconds) {
Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER)
.putExtra(AlarmClock.EXTRA_MESSAGE, message)
.putExtra(AlarmClock.EXTRA_LENGTH, seconds)
.putExtra(AlarmClock.EXTRA_SKIP_UI, true);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
注:
为了调用 ACTION_SET_TIMER Intent,您的应用必须具有 SET_ALARM 权限:
显示所有闹铃
如需显示闹铃列表,请使用 ACTION_SHOW_ALARMS 操作。
日历
添加日历事件
示例 Intent:
public void addEvent(String title, String location, Calendar begin, Calendar end) {
Intent intent = new Intent(Intent.ACTION_INSERT)
.setData(Events.CONTENT_URI)
.putExtra(Events.TITLE, title)
.putExtra(Events.EVENT_LOCATION, location)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
相机
拍摄照片或视频并将其返回
示例 Intent:
static final int REQUEST_IMAGE_CAPTURE = 1;
static final Uri mLocationForPhotos;
public void capturePhoto(String targetFilename) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.withAppendedPath(mLocationForPhotos, targetFilename));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bitmap thumbnail = data.getParcelable("data");
// Do other work with full size photo saved in mLocationForPhotos
...
}
}
以静态图像模式启动相机应用
如需以静态图像模式打开相机应用,请使用 INTENT_ACTION_STILL_IMAGE_CAMERA 操作。
示例 Intent:
public void capturePhoto() {
Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent);
}
}
以视频模式启动相机应用
如需以视频模式打开相机应用,请使用 INTENT_ACTION_VIDEO_CAMERA 操作。