2.1 Intent and Intent Filters

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在每个例子中的用法,请参照NotificationsApp 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的列表。这些方法只能列出可以产生响应的组件,但是不能激活它们。

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值