Intents and Intent Filters
Intent是一个消息传递对象,可以用来请求另一个app组件的动作。尽管intent在组件之间传递信息有多种方式,但是基本的使用例子有三种:
● 开启一个activity
一个activity在一个app中表示一个单独的屏幕。你可以通过一个intent传递到startActivity()方法中直接开启一个activity。Intent描述该activity,同时可以传递任何重要的信息。
如果你想要从一个已经结束的activity中获取结果,可以调用startActivityForResult()方法。在你的activity的onActivityResult()回调方法中你可以以intent类型的方式获取你想要的结果。
● 开启一个service
一个service 是一个不需要与用户交互的运行在后台的组件,你可以传递一个intent给startService()来启动一个service进行一次有效操作,例如下载一个文件。Intent可以携带任何需要的信息传递给service。
如果该service被设计成客户端接口,你可以从另外一个组件通过intent传递到bindService()方法来绑定service。
● 传递一个广播(broadcast)
广播是任何app可以接收的信息。系统会因为系统事件发出各种各样的广播,例如系统启动或设备开始充电等。你可以给其他的程序发送广播,只要将intent发送给sendBroadcast()或sendOrderedBroadcast()或者sendStickyBroadcast()方法即可。
Intent Types
Intent有两种类型:
● 显式intent(Explicitintents)
指定组件的名称(完成的类名)。显式intent典型的用法是开启你自己app中的组件,因为你知道想要开启的activity或者service的完整类名。
● 隐式intent
不指定组件的名称,但是会声明执行方式,该执行方式可以被别的app的组件所控制。 例如,你需要向用户展示地图上的一个位置,你可以发送intent给别的app请求它在地图上显示出该特殊地址。
当你创建一个显式的intent去开启一个activity或service的时候,系统会直接启动相应的app组件。
当你创建一个隐式intent时,android系统会根据在intent中描述的内容,通过在别的app上manifest文件中声明的intent filters来找到相应的组件开启。如果该intent与其中的一个intent filter匹配,系统会启动相应的组件并且将它传递给Intent对象(Intent Object)。如果有多个intentfilter与之匹配,系统则会显示一个对话框让用户选择使用其中一个app。
一个intentfilter是一个存在于app的manifest文件中的表达式,它指定某个组件更倾向接受intent的类型。例如,在一个activity中声明一个intentfilter,则该activity可以被其他的app以某种类型的intent启动。此外,如果你没有在一个activity中声明任何intent filter,则该activity只可能被显式intent启动。
图一:解释了一个隐式intent是如何通过系统传递去启动别的app的activity的。ActivityA创建了一个带有行为描述的(actiondescription)隐式的intent,并且通过startActivity()把它传递出去。然后Android系统搜索所有设备上的app的intentfilter是否匹配该intent。当找到匹配的intent filter时,系统通过调用onCreate()方法启动该匹配的activity(Activity B),同时将它传递给Intent对象。
注意:从安全的角度考虑,通常使用显式intent启动service而不在manifest中给service声明intentfilter。使用隐式intent开启一个service存在安全隐患,因为你不能确定哪一个service会对intent产生响应,同时用户看不到哪一个service被启动了。
Building an Intent
一个Intent中应该包括的重要信息有:
● 组件名称(Componentname)
要启动的组件的名称。这是一个可选择的信息,但它是使得一个intent成为显式的重要部分,因为它意味着这个intent只能传递给描述了该组件名的组件。如果没有组件名,则这个intent是一个隐式的intent。你可以用方法setComponent(),setClass(),setClassName()或者直接使用Intent构造器来设置组件名。
● 行为(Action)
是一个字符串,用来指定操作行为(例如是看还是选择)。在广播intent的情况下发生action,同时action还被报告出来。Action在很大程度上决定intent的结构——特别是什么被包含在数据和附加条件中。通常你需要在Intent类或者其他框架类中用action的常量来定义。下面是一些启动一个activity时的常用action:
ACTION_VIEW
当一个activity中有内容需要展示给用户看的时候,可以在startActivity()中调用的intent里使用这个action属性,例如在相册app中展示图片,或者在地图app中显示地址。
ACTION_SEND
当需要传递数据给别的app的时候,可以在startActivity()方法中调用的intent里使用这个action属性,例如一些社交app或者邮件app
可以在Intent类中查看更多的action属性。其他的action会在android框架中被定义,例如在Android系统中的Settings选项定义特定的屏幕操作。
你可以在一个intent中用setAction()方法声明一个action,也可以用Intent构造器声明。
如果你自己定义action,确定该action前缀包含你的app包名(package name)
static final String ACTION_TIMETRAVEL=“com.example.action.TIMETRAVEL”;
● 数据
URI是引用数据类型(URI是Uniform Resource Identifier,用来定位资源位置),而MIME是数据的具体类型(MIME类型包括音频,视频,图片,文档等)。数据的类型是被intent的action所决定的。例如,如果action的属性是ACTION_EDIT,为了实现编辑的功能,数据应该包含文档的URI。
在创建一个intent的时候,最重要的就指定它的数据的类型(它的MIME类型),而URI则不需要指定其类型。例如,一个activity可以展示图片,那么它就很有可能无法展示视频文件,尽管URI可能是一样的。所以说明你的数据的MIME类型有利于Android系统找到最适合的组件接收你的intent。然后,有时候MIME类型的数据也需要通过URI来推断——当数据是一个content: URI的时候。这表明这个数据位于设备的某处,且被ContentProvider控制,这样MIME数据对系统是不可见的。
设置数据的唯一URI,可以调用方法setData()。调用setType()方法可以设置唯一的MIME类型。如果需要的话,可以用方法setDataAndType()设置两者。注意不能同时使用setData()方法和setType()方法,因为他们会使对方无效。
● 种类(Category)
是附加信息的字符串,补充说明那个组件应该处理这个intent。下面是常用的category:
CATEGORY_BROWSABLE
目标activity允许它自身通过一条连接以浏览器的方式启动来展示相应的数据,例如一张图片,或者一封邮件。
CATEGORY_LAUNCHER
该activity是一个任务的最初的activity,在系统的应用启动器列表中。(listedin the system’s application launcher)
通过上面列出来的属性,已经能够明确表示一个intent的特征。Android系统可以李彤上面的特征解析出哪个app的组件应该启动。
● 附加条件(Extras)
Extras使用键值储存信息。可以用putExtras()方法重复添加extra数据。也可以使用Bundle对象存储所有的extra数据,然后将Bundle对象绑定到intent上。
例如,创造一个intent并且用ACTION_SEND来发送邮件,你可以用“EXTRA_EMAIL”关键字指定接收邮件者,然后用“EXTRA_SUBJECT”指定发送邮件的主题
Intent类中给常用数据类型指定了很多EXTRA_*的常量。如果你需要描述你自己的extra关键字,请确保使用你的app包名作为其前缀,例如:
staticfinal String EXTRA_GIGAWATTS=“com.example.EXTRA_GIGAWATTS“
● 标记(Flag)
Flag是作为intent的元数据起作用的。Flag可能会通知android系统如可发起(launch)一个activity(例如这个activity属于哪一个task的),以及在activity发起(launch)之后应该被如何对待(例如,它是否属于最近的activity列表中)。可以参考setFlags()方法。
Example explicit intent
例如,在你的app中建立一个叫做DownloadService的service,其功能是从网络上下载一个文件,你可以如下启动它:
//The fileUrl is a string URL, such as”http://www.example.com/image.png”
IntentdownloadIntent=new Intent (this, DownloadService.class);
downloadIntent.setData (Uri. parse (fileUrl));
startService(downloadIntent);
可以参照Services的指南看如何创建和开启一个service
Example implicit intent
一个隐式的intent指定一个action,该intent可以调用设备上任何可以表现出这个action的app。使用隐式的intent有时候很有用,比方说在你的app不能执行某个action而别的app可以执行该action,且恰好你希望用户选择一个app来执行的时候。
例如,你希望自己的用户可以与其他人分享一些内容,可以创建一个intent带有action的ACTION_SEND属性,同时添加上你希望分享的内容在extras上。当你用该intent调用startActivity()方法的时候,用户可以选择一个app来分享这个内容。
注意:有这种可能性:用户不希望任何app处理这个你发送给startActivity()的隐式intent。当这种情况发生的时候,调用会失败且你的app会退出。为了证实会有activity接收这个intent,在你的intent对象中调用resolvedActivity()方法,如果结果不是null,那么至少有一个app可以处理这个intent,这时候调用startActivity()是安全的。如果结果是null,你不应该使用这个intent,如果可能的话你应该禁用这个intent的功能。
//Createthe text message with a string
IntentsendIntent=new Intent ();
sendIntent.setAction (Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); //“text/plain“ MIME type
//Verify that the intent will resolved to an activity
if(sendIntent.resolveActivity (getPackageManager ())! = null) {
startActivity (sendIntent);
}
当调用startActivity()方法的时候,系统会就搜索所有已经安装的app判断哪些可以处理这种类型的intent(带有ACTION_SEND行为并且带有“text/plain”类型的数据)。如果只有一个app可以处理,则直接打开这个app且传递这个intent。如果有多个app都可以处理这个intent,系统会显示一个对话框让用户选择其中之一启动。
Forcing an app chooser
当有多个app都可以处理一个隐式intent时,系统会让用户选择其中之一打开,并且用户可以选择这个app作为默认打开。对于用户频繁打开相似的应用的时候,这个设计非常合理(就像用户打开网页的时候,可以选择设置一个默认的浏览器,则此后每次打开网页时,都会启动该浏览器的app)。
然而,当有多个app会对一个intent响应而用户想要在每次都使用不同的app的时候,你需要显示一个选择对话框。这个选择对话框会在每次用户访问的时候都出现,询问用户想要的打开方式(这种情况下用户不可以选择一个默认的app)。例如,当你的app有一个“分享”的功能的时候(使用ACTION_SEND),用户可能想要分享给不同的社交app。
为了展示这样一个选择器,需要使用createChooser()方法创建一个intent,同时将它传递给startActivity()方法。例如:
Intentintent=new Intent (Intent.ACTION_SEND);
… …
Stringtitle=getResource ().getString (R.string.chooser_title);
//Createintent to show chooser
Intentchooser = Intent.createChooser (intent, title);
//Verifythe intent will resolve to at least one activity
if(intent.resolveActivity (getPackageManager ())! =null) {
startActivity (intent);
}
这样就显示了一个app列表的对话框,且里面的app会对传递到createChooser()里面的intent响应,且使用提供的text作为标题。
Receiving an Implicit Intent
为了通知你的app哪些隐式的intent可以接收,可以在你的manifest文件中对app组件使用<intent-filter>元素进行描述一个或多个intent filter。每一个intentfilter指定它接收的intent类型,基于这个intent的action,data和category。如果一个隐式的intent可以通过你的app的过滤器,则系统也会将这个隐式的intent传递给你的app。
一个app组件应该描述清楚它各自filter的作用。例如,一个在相册app中的activity可能会有两个filter:一个filter查看图片,另一个filter编辑图片。当这个activity启动的时候,它会检查传递过来的intent并且根据这个intent的信息决定是显式图片还是编辑图片。
每个intentfilter都会在manifest文件中使用<intent-filter>元素定义,且嵌套在相应的app组件的描述中。在<intent-filter>中你可以用下面的一个或几个元素指定接收intent的类型:
<action>
声明接收intent的action类,在name属性中描述。这个属性必须是action的string值,而不能用类常量来表示
<data>
声明接收数据的类别,使用一个或多个属性描述数据URI。(scheme, host, port, path, etc.)和MIME类型。
<category>
声明接收intent的种类,在name属性中描述。 这个属性必须是category的string值,而不能用类常量来表示。
注意:为了接收隐式intent,你必须包括CATEGORY_DEFAULT种类在intent filter中。方法startActivity()和startActivityForResult()用同一种方式对待所有的intent——认为这些intent都有CATEGORY_DEFAULT种类。如果你不在你的intent-filter中描述这个属性,则你的app不会解析隐式intent。
例如,这是一个可以接收描述有ACTION_SEND属性intent的intentfilter,同时intent的数据类型是text:
<activityandroid: name=”ShareActivity”>
<intent-filter>
<actionandroid: name=”android.intent.action.SEND”/>
<category android: name=”android.intent.category.DEFAULT”/>
<data android: mimeType=”text/plain”/>
</intent-filter>
</activity>
在一个intent filter中添加多个<action>,<category>,<data>实例是被允许的,只要你的组件可以处理其中一个以及所有的实例组合情况即可。
一个隐式的intent会被filter与其定义的三个元素逐一比较,intent需要通过三个元素的测试才能被传递给组件。即只要其中一个元素不匹配,android系统不会把intent传递给该app组件。然而, 因为一个组件可能有多个intentfilter,它不能通过其中的一个filter并不代表它不能通过该组件的另一个filter。关于系统如何解析intent的请求的信息,可以参照Intent Resolution。
注意: 对于所有的activity来说,intent filter必须定义在manifest文件里。但是broadcast接收者的intentfilter可以动态的通过调用方法registerReceiver()来定义。还可以通过方法unregisterReceiver()来注销这个接收者。这样做可以使得你的app在运行的一段特殊的时间内接受某个特殊的广播。
Example filters
为了更好的理解intent filter的行为,下面是从一个社交分享app上截取的片段:
<activityandroid: name=”ManiActivity”>
<!--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>
<activityandroid: 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 mediadata -->
<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的关键点。这个activity会在用户第一次点击app图标时启动:
● ACTION_MAIN这个action表明这是app的主要入口点且不需要任何intent的数据
● CATEGORY_LAUNCHER这个种类表明这个activity的图标应该被放在系统app launcher里面。如果<activity>元素中并不用icon属性来指定一个图标,那么系统会使用<application>中描述的图标。
以上两点应该放在一起声明使得这个activity可以被app launcher识别。
在第二个activity,ShareActivity,是为了分享视频和文档内容。尽管用户可能是从MainActivity的导航进入该activity的,用户也可以从别的app中使用一个隐式intent匹配上该activity的intent filter而进入到ShareActivity中。
注意:上述的MIME类型:applicatoin/vnd.google.panorama360+jpg是一个特别的指定全景照片数据类型,你可以在GooglepanoramaAPIs中控制它。
Using a Pending Intent
一个PendingIntent对象是一个Intent对象的包装类。使用PendingIntent的最主要目的是给别的使用了该PendingIntent包含的Intent对象的应用程序授予许可,就像从你的应用程序中执行Intent。
使用PendingIntent的主要情况包括:
● 声明一个intent并执行:when the user performs an action with your Notification (android系统的NotificationManager执行这个intent)
● 声明一个intent并执行:when the user performs an action with your App Widget (Home screen app执行这个intent)
● 声明一个intent并在未来的一段特定时间执行(android系统的AlarmManager执行这个intent)
因为每一个intent对象都被设计成会被某个特别的app组件类型处理(一个Activity,Service或者是BroadcastReceiver),所以一个PendingIntent对象也要以相同的考虑来创建(即PendingIntent对象也要被某个特别的app组件处理)。当使用一个PendingIntent时,你的app不会调用类似startActivity()的方法来执行intent。你必须在调用相应的创建方法来创建PendingIntent对象时声明目标组件类型:
● PendingIntent.getActivity ()for an Intent that starts anActivity.
● PendingIntent.getService ()for an Intent that starts aService.
● PendingIntent.getBroadcast ()for an Intent that starts aBroadcastReceiver.
除非你的app是从别的app上接收PendingIntent,否则上面列出来的方法就是你创建PendingIntent对象需要使用的唯一的PendingIntent类的方法。
每一个方法都能拿到当前app的Context,可以抓取你想要获得的intent以及一个或者多个flag说明该intent应该被怎样使用(例如该intent可否使用多次)
更多关于PendingIntent在每个例子中的用法,请参照Notifications和App Widgets API指南。
Intent Resolution
当系统获得一个隐式的intent去启动一个activity的时候,它会通过下面三个方面搜索最匹配的activity:
● The intent action
● The intent data(bothURI and data type)
● The intent category
下面的选择能说明一个intent是怎样通过在app的manifest文件中描述的intent filter和相应的组件(们)分别匹配上的。
Action Test
为了明确接收的intent的action类型,intentfilter可以声明0个到很多个<action>元素,例如:
<intent-filter>
<action android:name=”android.intent.action.EDTI”/>
<action android:name=”android.intent.action.VIEW”/>
…
</intent-filter>
为了通过这个intentfilter,该intent必须匹配其中一条action描述。
如果filter中没有列出任何action,则不会有任何intent与之匹配,所以所有的intent都不会通过这个filter。然而,如果一个intent没有描述任何一个action,它依然会通过测试。(只要filter中包含至少一个action)
Category test
为了明确接收intent的category,intentfilter可以声明0个到多个<category>元素。
<intent-filter>
<category android: name=”android.intent.category.DEFAULT”/>
<category android: name=”android.intent.category.BROWSABLE”/>
…
</intent-filter>
一个intent想要通过category的测试,则该intent中的每个category都必须匹配filter中的category。反过来则并不一定——intentfilter可能会描述比intent中的更多的category,intent也会通过category的测试。一个没有category描述的intent总会通过category测试的。
注意:Android系统会自动设置隐式intent在通过startActivity()方法和startActivityForResult()具有CATEGORY_DEFAULT属性。所以如果你希望你的activity能够接受隐式intent,它必须在intentfilter 中包含category的属性描述“android.intent.category.DEFAULT”
Data Test
为了声明可以接收的intent的数据,intent filter可以声明0个到多个<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中,scheme是content,host是com.example.project,port是200,路径(path)则是folder/subfolder/etc
上面的属性在<data>元素中是可选择的,但是它们又相互依赖:
● 如果没有指定scheme,则host会被忽略
● 如果没有指定host,则port会被忽略
● 如果scheme和host都没有指定,则path会被忽略
当一个在URI里的intent与在filter中被指定的URI进行对比的时候,只会比较描述在filter中的URI部分,例如:
● 如果filter只指定了一个scheme,所有包含这个scheme的URI都和该filter匹配
● 如果filter指定了一个scheme和一个authority,但是没有路径,所有包含这个scheme和该authority的URI都可以通过filter(忽略其路径定义)
● 如果一个filter只指定了一个scheme,一个authority和一个路径,则只有完全匹配上述三项的URI才可以通过这个filter
数据测试比较了intent中URI和MIME类型和在filter中指定的两者,规则如下:
a. 一个即不包含URI也不包含MIME类型数据的intent可以通过数据测试,原因只可能是该filter没有指定任何类型的URI和MIME类型。
b. 一个intent只包含一个URI而不包含MIME(即不包含显式的URI,也不包含可拼凑出的URI)可以通过数据测试的原因是,该URI匹配了filter中指定的URI且filter同样没有指定MIME类型。
c. 一个intent只包含MIME而不包含URI可以通过数据测试原因同样是因为filter只指定了相同的MIME类型而没有声明URI样式。
d. 一个intent同时包括URI和MIME类型(eitherexplicit or inferable from URI)可以在匹配filter的MIME声明类型的情况下通过MIME数据测试。而它通过URI部分的测试有两种情况。一种是intent中的URI直接与filter中的URI匹配。另一种是intent中的URI有content:或者file:,同时filter中没有定义URI。
在最后一个规则中,表示组件希望通过文件或者contentprovider获得本地数据。下面的例子就表示组件希望从contentprovider获得一个图片数据并展示它
<intent-filter>
<data android: mimeType=”image/*”/>
…
</intent-filter>
因为几乎所有的可用数据都是由contentprovider分配的,filter只指定MIME而不指定URI是最常见的情况。
另一个比较常见的情况是filter指定一个scheme和一个数据类型。例如,一个如下描述的<data>元素告诉Android系统该组件可以从网络上获得视屏数据:
<intent-filter>
<data android: scheme=”http” android:type=”video/*” />
…
</intent-filter>
Intent matching
Intent与filter的匹配不仅仅是为了发现一个目标组件去激活,而且也是为了发现设备上相关组件的设置。例如,“HOME”app可以寻找到所有带有ACTION_MAIN和CATEGORY_LAUNCHER描述的intentfilter的activity并用它们填入app launcher中。
你的应用可以以一种很简单的方法适应intent匹配。PackageManager有一个query…()的方法能返回可以接收某个指定intent 的所有的组件,而一系列相似的resolve…()的方法可以返回响应某个intent的最佳组件。例如,queryIntentActivity()返回所有可以将intent作为参数传递的activity的list,而queryIntentServices()返回类似的service的list,同样的,queryBroadcastReceiver()方法返回类似的广播receiver的列表。这些方法只能列出可以产生响应的组件,但是不能激活它们。