http://developer.android.com/guide/components/intents-filters.html#PendingIntent
Intent 是一个messaging对象,可以用于从另一个组件请求一个action。Intent 在组件之间有很多种方便的通信方式,有三种基本的使用方式。
启动一个activity
一个App中,一个activity表示一屏。可以通过传递一个Intent给startActivity()来实例化一个Activity。Intent描述了activity启动和执行时的必要数据。
如果你想在activity结束时接受它的结果,可以调用startActivityForResult()。你的activity在onActivityResult()回调中接收这个结果,这个结果是一个独立的Intent对象。启动一个service
Service 是一个没有UI在后台执行的组件。可以传递一个Intent给startService()启动一个Service执行一次性操作(比如说下载文件)。Intent描述了Service启动和执行时的必要数据。
如果Service设计为C/S接口,可以在另一个组件中传递Intent给bindService()来绑定这个Service.- 发布一个broadcast
broadcast是任何APP可以接受的消息。系统为系统事件发送各种广播。比如说,系统启动或者设备开始充电。可以通过传递Intent给sendBroadcast()或者sendOrderBroadcast()或者sendStickyBroadcast()来发送广播给其他APP。
Intent Types
两种Intent类型:
- 显式Intent
通过类名启动指定的组件。通常在自己的APP中显式启动组件,因为自己知道要启动的activity或service的类名。比如说启动一个activity来响应一个用户操作或者启动一个service在后台下载文件。 - 隐式Intent
不需要指定组件的名字,取而代之的是声明允许其他APP操作的动作。比如说你想在地图中展示用户的位置,就可以用隐式Intent来请求另一个有能力展示在地图中显示用户位置的APP来启动展示。
当用显示Intent来启动activity或service时,系统立即启动指定的APP组件。
当创建一个隐式的Intent时,Android系统通过比较Intent的内容和设备中其他APP的manifest中声明的intent filters来启动一个合适的组件。如果一个Intent和一个intent filter匹配,系统启动这个组件,并将intent对象传给它。如果多个intent filter匹配,系统展示一个对话框列表供用户自己选择。
在一个App的manifest文件中,intent filer是表示组件想要接收哪种类型的intent。举例来说,通过给一个activity声明一个intent filter来使得其他APP使用特定类型的intent来直接启动你的activity。同样的,如果你不为你的activity声明任何intent filter,那只能用显示的intent启动。
Caution:为保证你的APP安全,在启动service时总是显示intent启动,不要为你的service的声明intent filter。使用隐式intent启动service是非常不安全的,因为你不能确定哪个服务响应intent,用户不能看到哪个service启动了。从Android5.0(API21)开始,如果你调用bindService()绑定隐式的intent,系统将会抛出异常。
Building an Intent
一个Intent对象携带了android系统用于判断哪个组件应该启动的信息(比如说精确的组件名称或者应该接收的intent的组件category)和接收组件用于执行操作的信息。(简单说intent有两部分数据:一是用于选择哪个组件启动,一个是传给组件要使用的数据)
Intent包含以下一些信息:
Component name: 启动组件的名字
这是可选的,但这是使intent成为显式的关键信息,意味着定义了组件名称的intent只传递给本APP的组件。没有组件名称,intent是隐式的,系统根据intent的其他信息(比如说 action,category)决定哪个组件接收intent。所以,如果你需要启动你APP中的特定组件,你应该指定组件的名称。
Note: 当启动Service时,你应该总是指定组件名称。
Intent的这个域是ComponentName对象,你可以使用目标组件的完全限定域名,包括APP的包名。比如说,com.example.ExampleActivity。你可以用setComponent()、setClass()、或者setClassName(),或者在Intent的构造函数中设置组件名。
Action
一个字符串,指定执行的操作。
在broadcast intent的例子中,就是这个action发生被报告。action很大程度上决定了剩余intent的结构,尤其是是否包含data和extras。
你可以在你自己的APP中定义自己的actions来使用(其他APP可以通过它来唤醒你APP中的组件),但是你应该通常使用Intent类或者框架类定义的action常量。这有很多启动activity的通用action。
ACTION_VIEW
当你有些信息需要在一个activity中展示给用户时,比如说在画廊APP中展示相片或者在地图的APP中展示位置,就可以使用这个action。
ACTION_SEND
也称为“share”Intent,当要分享一些数据给其他APP使用,比如Email或者社交分享App。
可以用setAction()或者在Intent的构造函数中指定action。
如果你定义自己的action,确定包含你的APP的包名做前缀。举例:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVER";
Data
Data有两部分,URI 表示data要执行什么,MIME表示数据的多媒体类型。数据支持的类型通常被intent的action所规定。比如说:如果action是ACTION_EDIT,data应该包含文档的URI来编辑。
当创建一个intent,除了URI,指定data的MIME类型也很重要。比如说,一个activity可以显示图片但不能播放音频文件,而URI格式很相似,所以data的MIME类型可以帮助android 系统找到接收intent最好的组件。然而,MIME类型有时可以从URI推断出来,尤其是当data是content:的URI时,系统推断数据在本设备上,被ContentProvider控制,使得数据的MIME类型对系统可见。
如果只设置data的URI的话,调用setData(),只设置MIME类型的话,调用setType()。如果需要,可以都显式设置,使用setDataAndType()。
Caution 如果想要设置URI和MIME,不要分别调用setData()和setType(),因为他们会覆盖。使用setDataAndType()设置URI和MIME。
Category
一个字符串,包含能够处理intent的组件种类的信息。任何种类的category描述都可以放到intent中,但是大部分intent不要求category。下面是一些常用的category。
CATEGORY_BROWSABLE
目标activity允许它被一个web浏览器启动来显示数据。比如说一张图片或者一封email信息。
CATEGORY_LAUNCHER
这个activity是一个任务的初始activity,被列在系统应用程序启动列表里。
你可以用addCategory()来指定一个category。
上面列的这些表示intent的特征(组件名称、action、data、category)。系统通过这些特征来判断哪个APP组件应该启动。
而且,一个intent还可以携带不影响判断启动哪个组件的信息。比如说下面这些:
Extras
键值对携带一些用于完成action所需要的信息。就像一些action需要用一些特定类型的URIdata,一些action也需要一些额外的信息。
可以使用各种putExtra()方法来添加extra数据,每一个接收两个参数,一个是键名,一个是值。你也可以创建一个bundle对象来存额外信息,最后使用putExtras()将bundle对象放入intent中。
比如说,当用ACTION_SEND创建一个intent发送email时,你可以使用EXTRA_EMAIL这个键来指定收件人,用EXTRA_SUBJECT键来指定主题。
Intent类中为很多标准化的数据类型设置了EXTRA_常量。如果你需要声明自己的extra键(为了让你的APP接收到)的话,确保将你的APP的包名作为前缀。比如说:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags
Intent类中定义的Flags作为intent的元数据。flags指导系统怎样启动activity(比如说,activity应该属于哪个task)和在启动之后如何处理它(比如说,它是否在最近的activity的列表中)。
更多详细的信息,可以查看setFlags()方法。
显式intent示例
显式intent用于启动指定的APP组件,比如自己APP中的一个activity或者一个service。使用显式intent时,指定组件的名字,其它的intent属性都是可选的。
比如说,你在你的APP中建立一个名称为DownloadService的service,用于从web下载文件,你可以用下面的代码启动它:
//Executed in an Activity,so 'this' is the Context
//The fileUrl is a string URL,such as "heep://www.example.com.image.png"
Intent downloadIntent = new Intent(this,DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
Intent(Context,Class)构造函数中,一个是APP的context,一个是组件的类。这样,intent显式的启动了APP中的DownloadService类。
关于创建和启动service的更多信息,可以参考Service Guide。
隐式Intent示例
隐式Intent指定一个action来唤起设备中能够执行这个action的APP。当你的APP中不能执行某个action时,而其他APP可以执行时而且你想让用户选择使用某个APP来执行时,使用隐式intent是非常有用的。
比如说:用户想要分享内容给其他用户,创建一个action为ACTION_SEND的intent,添加上想要分享的内容。当调用startActivity()时,用户可以选择用哪个APP来共享内容。
Caution: 有一种可能,用户没有任何APP来处理用startActivity发送的隐式intent。如果这样的话,调用将失败,而且你的APP将crash。为了解决这个问题,先用intent对象调用resolveActivity()来验证是否有activity在接收你的intent。如果结果不是null,那至少有一个APP可以处理你的intent,那么调用startActivity是安全的。如果结果是null,你不应该使用这个intent,可能的话你应该禁用这个intent。
//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);
}
Note: 在这个例子中,没有使用URI,但是通过指定data的类型指定了携带内容信息的类型。
当startActivity()调用后,系统检查安装的所有APP来决定哪个来处理这种intent。如果只有一个APP能够处理的话,这个APP立马启动,如果多个activity可以接受这个intent的话,系统会列出一个对话框供用户自己选择。
强制选择一个APP
当不止一个APP可以响应你的隐式intent时,用户可以选择一个APP来使用,并且使这个APP作为这个action的默认选择。执行一个action,用户可能想要一直使用这个app来执行这个action时,这是非常好,比如说打开一个网页(用户通常只会用一个浏览器)。
然而,如果多个APP可以响应intent,并且用户每次想要使用不同的APP来响应,你应该显式的给一个选择框。这个选择框来询问用户每次用哪个APP来响应这个action(用户不为这个action选择默认的APP)。比如说,当你的APP使用ACTION_SEND执行分享时,用户可能会根据不同的情况选择不同的app来执行,所以你应该总是使用选择对话框。如图2所示。
为了显示一个选择器,使用createChoose()来创建intent,把这个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
为了告诉你的APP可以接受什么隐式的intent,在你的manifest文件中使用为你的每个组件声明一个或多个intent filter。每个intent filter根据intent的action、data、category来判定可以接收的intent。只要intent可以通过你的一个intentfilter,系统就会发送一个隐式的intent给你的APP组件。
Note: 即使有组件的过滤声明,显式intent也是发送给它的目标。
一个APP的组件应该为它能完成的每个工作声明独立的滤波器。比如说:一个画廊的APP中的activity可能有两个滤波器:一个用来看image,另一个用来编辑image。当这个activity启动时,它会检查这个intent,然后根据这个intent的信息来决定它的行为。(比如说是否展示编辑功能)
在APP的manifest文件中,用来定义intent滤波器,嵌在相应的组件中(比如说一个元素中)。在里面,你可以使用一个或者多个参数(下面三种元素)来指定可以接收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>
创建一个包含多个<action>
,<data>
,<category>
实例的intent filter是可以的。如果这样的话,你需要确定这个组件可以处理这些滤波元素的一些或者全部的组合。
当你想要处理多种intent时,但action、data、category只有一种组合,那么你需要创建多种intent filter。
隐式intent要与intentfilter的三个元素进行比较,要想启动组件,intent必须通过三个元素的测试。如果它不能匹配其中的一个即失败,系统不会将这个intent传给组件。因为一个组件可能有多个intentfilter,一个intent通不过这个filter还可能通过另一个filter。更多关于系统怎样处理intent的信息将在下面Intent Resolution部分给出。
Caution: 为了避免无意的运行不同APP的service,总是使用显式的intent来启动自己的service,不为自己的service声明intent filter。
Note:对于所有的activity,你必须在manifest文件中声明intent filter。然而,broadcast receiver的filter可以通过registerReceiver()动态的注册,然后通过unregisterReceiver()来解除注册。这样做可以让你的APP在运行的特定阶段监听特定的广播。
滤波器示例
为了更好的理解intent filter的行为,看一下下面这个来自社交分享APP的manifest文件的代码片段。
<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.LUNCHER"/>
</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,是APP的主要入口点,当用户通过APP的icon启动APP时,就会打开这个activity。
action: ACTION_MAIN , 表示这是APP主要的入口点,不期望任何intent data。
category:CATEGORY_LUNCHER ,表示这个activity的icon应该放到系统APP启动的地方。如果这个
使用一个Pending Intent
PendingIntent对象是对Intent对象的包装。PendingIntent的主要目的是允许外部应用使用Intent就像在自己的进程中执行一样。
Pending Intent的主要使用情况包括:
- 声明一个Intent在用户执行Notification时执行(Android系统的NotificationManager执行这个Intent)。
- 声明一个Intent当用户执行桌面部件时执行(主屏幕的小部件执行这个Intent)。
- 声明一个Intent在将来某个时间点执行(Android 系统的AlarmManager执行这个Intent)。
因为设计每个Intent对象都是要被特定类型的APP组件处理(不论是Activity,Service还是BroadcastReceiver),所以处于相同的考虑,也必须创建一个PendingIntent。当使用PendingIntent时,你的app使用startActivity()方法将不会执行这个Intent。在你创建PendingIntent对象时,你必须声明想要启动的组件类型。创建PendingIntent对象,要调用响应的方法创建:
- PendingIntent.getActivity(),获得用于启动Activity的Intent。
- PendingIntent.getService(),获得用于启动Service的Intent。
- PendingIntent.getBroadcast(),获得用于启动BroadcastReceiver的Intent。
除非你的APP用于接收来自其他APP的PendingIntent,否则的话你可能只需要使用上面的一个方法创建PendingIntent对象。
每一个方法都带有当前APP的Context,你想要包装的Intent,一个或多个指定intent怎样使用的标记位(比如说这个Intent能否多次使用)。
使用PendingIntent的更多信息在使用例子的相应文档中,比如:Notifications和APP widgetsAPI Guide。
Intent Resolution
当系统接收隐式intent来启动activity时,系统通过比较intent和intent-filter中的下面三方面来找到最佳的activity:
- intent 的 action
- intent 的 data(URI和data type)
- intent 的 category
下面的部分描述intent是怎样依据manifest文件中intent-filter来匹配合适的组件。
Action test
为了指定可接收的intent action,一个intent filter可以声明零个或多个
<intent-filter>
<action android:name="android.intent.action.EDIT"/>
<action android:name="android.intent.action.VIEW"/>
...
</intent-filter>
为了通过这个滤波器,Intent指定的action必须匹配滤波器列表中的一个action。
如果滤波器没有列出任何action,就没有action供Intent来匹配,所以任何intent都不会匹配成功。但是,如果一个Intent没有指定action,它将会通过滤波器的匹配(只要这个滤波器中至少有一个action)。
总结:滤波器中的action必须有,否则不能接收隐式Intent,而Intent的action可以没有,如果没有可以通过任何滤波器的匹配(只要这个滤波器可以接受隐式Intent,即至少有一个action)。
Category test
为了指定可以接收的Intent category,滤波器可以声明零个或者多个
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
...
</intent-filter>
一个intent要想通过category的测试,Intent中的每个category必须在滤波器中有一个匹配的(换句话说,就是Intent中有的滤波器中必须有)。滤波器中声明的category可能比intent中指定的多。所以,无论滤波器中声明多少category,没有指定category的intent总是可以通过category的测试。
Note: Android系统会自动为每个通过startActivity()或startActivityForResult()启动的隐式Intent添加CATEGORY_DEFAULT分类。所以,你想要你的activity接收隐式Intent的话,你需要在你的intent-filter中添加android.intent.category.DEFAULT分类。
Data test
为了指定可以接收的intent data,滤波器可以声明零个或者多个
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" .../>
<data android:mimeType="audio/mpeg" android:scheme="http" .../>
...
</intent-filter>
每个
URI:<scheme>://<host>:<port>/<path>
举例:
content://com.example.project:200/folder/subfolder/etc
在这个URI中,scheme是content,host是com.example.project,port是200,path是folder/subfolder/etc.
<intent-filter>
<data android:mimeType="image/*"/>
...
</intent-filter>
因为大多数可获得的数据都是由content provider分发的,滤波器指定数据类型而不是URI可能更为常见。
滤波器另一种常见的配置是scheme和data type。比如说,下面这个<data>
元素告诉Android,这个组件执行的操作可以接收来自网络的video数据:
<intent-filter>
<data android:scheme="http" android:type="video/*"/>
...
</internt-filter>
Intent matching
Intent 匹配滤波器不仅用于找到目标组件来启动,也用于找到设备上的一系列组件。举个例子,操作系统中存着app的启动器,就是通过intent-filter中的特殊action:ACTION_MAIN和特殊category CATEGORY_LAUNCHER来找到相应的activity。
你的app可以使用相似的方式进行intent匹配。PackageManager有一系列query…()方法来返回可以接收特定intent的所有组件,一系列resolve…()方法来找到响应intent最佳的组件。举个例子,queryIntentActivities()返回能够匹配intent的所有activity组件,queryIntentServices()返回能够匹配intent的所有service。没有方法激活组件,它们只是列出可以响应的组件。对于broadcast receivers来说,也有queryBroadcastReceivers()方法。