c2. App Components-Intents and Intent Filters

来源:互联网 发布:易语言30000源码图片 编辑:程序博客网 时间:2024/06/06 00:08

Intent是一个消息载体, 可以用来对其他App 组件请求相关的操作。组件间通过Intent来通信有几种方法,比较基础的有三种,分别是:

1. 通过Intent来启动一个Activity:

一个Activity 表示一个简单的界面,你可以把一个Intent传递给startActivity()函数来启动一个新的Activity,当然事先你得配置好这个Intent。

如果你想启动后的新的Activity 返回结果回原来的Activity,那么你可以将一个Intent传递给函数startActivityForResult(),通过在原来的Activity的回调函数onActivityResult()来接收新的Activity返回的结果(结果封装在一个新的Intent里面)。

2. 通过Intent来启动一个Service:

一个Service是一个在后台运行的组件,没有用户界面。你可以把一个Intent传递给函数startService()来启动一个Service(比如下载文件),当然事先你得配置好这个Intent。

如果Service是设计成客服端/服务器模式,那么可以将一个Intent传递给函数bindService()来绑定一个Service。

3. 通过Intent来发送一个Broadcast

一个Broadcast是一个任何App都能接收的消息。当发生系统事件时,系统会派发各种各样的Broadcast给各个App,比如当系统开机,设备开始充电等。你可以把一个Intent传给sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast()来派发一个broadcast。

Intent 种类

在Android中有两种Intent:

显式Intent: 通过指明组件的名字(类完整的名字)来启动activity。典型的做法是你会通过一个显式的Intent在你自己的App启动一个组件,因为你实现知道这个组件(activity,service)的类名。比如启动一个activity来响应用户的动作或者启动一个后台service来下载文件。

隐式Intent: 不指明特定组件的名字,而是通过声明一个普遍的动作来实现,这样可以使得在其他App的组件来处理。比如,如果你想要给用户在地图上展示一个位置,那么你可以通过隐式的Intent来先请求那些有map的app来完成这个任务。

当你创建一个显式的Intent来启动一个Activity或者Service时,系统马上启动指定的组件。

当你创建一个隐式的Intent,android通过比较比较此时创建的Intent与其他App中的manifest file中intent-filter声明来启动那些符合要求的组件,如果有多个组件符合要求,那么系统将符合要求的组件显示给用户选择。

Intent-filter是在app中的manifest file 中的组件指定什么样的intent可以接收。比如,通过声明在你的组件上声明一个intent-filter,你可以使得其他的apps通过符合条件的intent来启动你的组件。如果你没有在你的组件上声明intent-filter,那么其他的组件只能通过显式的intent来启动你的组件。

注意:为了保证你的app是安全的,记得用显式的intent来启动service,并且不要再service中声明intent-filter,使用隐式的intent来启动service是不安全的,因为你不能保证那个service会响应,而且你不能看到哪个service被启动。

创建一个Intent

Intent包含android系统用于决定启动那个组件的信息(比如组件的类名或者应该接收这个intent的组件类别),除了这些,还包括一些用于指导接收组件的动作的信息(包括接收组件该执行的动作和需要的数据)。

包含在intent的主要信息有一下几个:

组件名称

需要启动的组件的名称

这个是可选的,但是对于显式的intent是必须的,指定好组件的类名后,intent只能去启动指定的组件。如果没有包括组件的类名,这样的intent是隐式的,系统根据其他信息来决定启动哪个组件(比如接下来提到的动作,数据,类别)。所以如果你需要在你的app中启动一个指定的组件,那么你应该在intent中指定组件的类名。

注意:当启动一个service,你必须指定组件的名称,不然你不能保证哪个service会响应你的intent,而且用户不能看到哪个service启动。

组件名称对应在intent的是ComponentName,必须将目标组件的全类名赋值给给这个变量,包括组件所在的包。比如com.example.ExampleActivity。你可以通过函数setComponent(), setClass(), setClassName()或者Intent的构造函数来设置目标组件的类名。

动作(Action)

一个字符串变量,用来指定需要执行的动作。

如果这是个用于broadcast的intent,那么action这个变量用来表示此时被通报的发生的动作。这个动作决定了intent其他信息的组织,特别是包含在data和extras部分的信息。

你可以在你的apps里面指定你要用到的动作(或者用于其他apps要调用你的组件时),但你必须使用intent类或者其他框架类定义的动作常量。下面是一些用来启动activity常用的动作:

ACTION_VIEW:在要传人startActivity()的intent中使用合格动作,表示你在这个activity有些信息要展示给用户,比如在一个相册apps中查看图片或者在一个地图app中显示地址。

ACTION_SEND:又名分享intent,当你有些数据要与其它app共享,比如与电子邮件app或者社交分享app,必须在传给startActivity()的intent包含这个信息。

可以查看Intent类中更多关于动作的常量。其它动作定义在android 框架的其它地方,比如在Settings中系统Settings app用于打开指定屏幕的动作。

你可以通过setAction()来指定要执行的动作,或者通过intent的构造函数。

如果你想定义自己的actions,一定要保证将你的app的包名用于前缀。比如

static final String Action_TIMETRAVEL = "com.example.action.TIMETRAVEL";

数据(Data)

包含要被执行的引用URI或者数据的MIME类型。数据的类型通常是由intent的动作决定的,例如,当动作是ACTION_EDIT,那么数据就应该包括要编辑文档的URI。

当创建一个intent,一定要记得指定数据的类型(它的MIME类型)和它的URI。比如,一个能够展示图片的activity可能不会播放音频文件,虽然音频文件和图片的URI格式可能相识。所以指定你数据的MIME类型有助于android系统找到最合适的组件来接收你的intent。但是,MIME类型有时候可以从URI推断出来,特别当数据是content: URI,表明数据在设备上,并且通过ContentProvider控制,数据的MIME类型对系统是可见的。

调用setData()来设置数据的URI。调用setType()来设置MIME类型。必要的话,你可以调用setDataAndType()来设置URI和MIME类型。

注意:如果你想要同时设置URI和MIME类型,不要同时调用setData()和setType(),因为它们导致前面设置的参数无效。所以当你需要同时设置这两个,调用函数setDataAndType().

类别(Category)

一个包含关于哪个组件来接收当前intent的额外信息的字符串。可以在一个intent放置任意数目的类别,但是大部分的intent不需要类别这个域。下面是一些通用的类别:

CATEGORY_BROWSABLE

目标activity允许自己被网页浏览器启动去展示有链接引用的数据,比如图片或者电子邮件消息。

CATEGORY_LAUNCHER

一开始被启动的activity,在系统的应用lancher列表上。

详细请见Intent类关于categories的描述。

可以通过 函数addCategory().

上面列出的组件名字,动作,数据,类别是关于怎么去定义一个intent。通过这些参数,android系统可以解析这些参数去决定调用哪个app组件。

除了这些外,intent还可以包含其它与解析那个app组件被

调用无关的信息

Extras

携带额外信息去完成要求的动作的键值对。就像一些动作需要特殊的数据URI,一些动作也需要使用额外的extras。

可以通过函数putExtra()来添加extra数据,这个函数包含两个参数:键(key)和值(value).你可以创建一个包含extra数据的Bundle对象,然后通过函数putExtra()将bundle对象添加到intent.

例如:当创建一个intent来发送邮件,指明动作ACTION_SEND,你可以指定通过EXTRA_EMAIL键来指定接收者(to)。通过EXTRA_SUBJECT键值来指明标题。

intent类指定了需要EXTRA_*的常量用于标准数据类型。如果你需要声明你自己的extra keys, 确保用你的报名作为前缀,例如:

static final String EXTRA_GIGAWAITS = "com.example.EXTRA_GIGAWAITS";

FLags(标示)

Flags定义了intent类怎么作为metadata。flags可以引导android系统怎么launch一个activity(比如,activity属于哪个task)和launched后怎么处理这个activity(例如,它是否属于最近的activity列表)

更详细的请查看setFlags()函数。

显式intent例子

显式的intent用来启动指定的app组件,比如一个特殊的activity或者service。创建一个显式的intent,定义intent对象的组件名字,其他intent属性根据情况定义。

比如,如果你创建一个名为DownloadService的service在你的app中,即从web下载文件,你可以通过下面的代码启动这个service:

//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)构造函数以app的Context和组件的类名为参数。这个intent显式的启动了DownloadService类。

关于service的更多内容,请查阅Services

隐式intent的例子

隐式的intent指定要执行的动作,以便调用设备上的其它app可以执行这个动作。当你的app不能执行某个动作时,就应该使用隐式intent。如果有多个app可以执行相应的动作,那么用户可以选择哪个app。

比如,如果你想和其他人共享你的内容,那么你可以创建一个带有动作ACTION_SEND的intent,可以添加一些额外的内容。当你调用startActivity(Intent)是,你就可以选择一个app来共享这些内容。

注意:在android系统中,有可能没有符合此隐式intent条件的app,如果发生这种情况,那么你的app将会crash。所以事先得验证是否有符合条件的app来接收这个intent,可以调用resolveActivity(Intent)来判断是否有符合条件的app,如果返回是非空,那么说明有符合条件的app,可以继续调用startActivity()。如果返回结果是空,那么不要使用这个intnet,而且以后不要用到类似intent提到的特性。

//Create the text message with a stringIntent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);sendIntent.setType(HTTP.PLAIN_TEXT_TYPE);if(sendIntent.resolveActivity(getPackageManager()) != null) {startActivity(sendIntent);}

注意:在这种情况下,没有用到URI,但是intent的数据类型通过在extras中指定内容来声明。

当startActivity()函数被调用,系统会检查所有的apps来决定哪些可以接收这个intent。如果只有一个app可以接收这个intent,那么这个app将会立即被启动。如果多个app符合条件,那么系统会显示一个对话框让用户选择。

强制一个app选择器

当有多个app可以接收这个隐式的intent时,用户可以选择一个app来执行,并设置这个app为默认执行的app。这种选择方式对于用户接下来一直会用这个app是有好处的,比如用户通常会选择一个浏览器来浏览网页。

但是,当用户希望每次用不同的app来接收这个intent时,必须显示一个选择对话框。通过选择对话框,用户选择一个app来执行。比如,当你的app使用ACTION_SEND来执行分享功能时,用户可能想根据现在的情况使用不同的app来分享。所以必须老是使用选择对话框。

通过将原来的intent作为参数传入到createChooser()来创建一个新的intent,然后将这个新的intent传给startActivity()函数。

Intent intent = 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);Intent chooser = Intent.createChooser(intent, title);//verify the intent will resolve to at least one activityif(chooser.resolveActivity(getPackageManager()) != null) {startActivity(chooser);}

上面会显示一个选择对话框,里面的app符合intent的条件。

接收一个隐式的Intent

你可以在你的app组件中的<intent-filter>中声明相应的条件来介绍隐式的intent。每个intent filter根据intent的action, data, category来设定。如果某个intent符合其中的一项,系统会派发一个隐式的intent给你的app组件。

注意:一个显式的intent会被派发到指定的app组件,不论这个组件声明了哪些intent-filter。

每个app组件应该为其每个任务都声明独立的filter。例如,一个相册的app的一个activity有两个filters: 一个用来浏览图片,一个用来编辑图片。当这个activity启动后, 这个activity会根据Intent携带的信息来执行相关的动作(比如是否显示编辑菜单)。

每个intent filter通过<intent-filter>来定义,内嵌在每个<activity>中。你可以通过下面的元素来设定接收的intent类型:

<action>

声明接收的intent的action,name的值必须是一个action的字符名,不能是类的常量。

<data>

声明接收的数据类型,使用多个属性来指定不能的数据URI (scheme, host, port, path,等)和MIME类型。

<category>

声明接收的intent类型。在name的值必须是字符串,不是类常量。

注意:为了接收隐式的intents,必须在intentfilter中的category包含CATEGORY_DEFAULT。如果组件定义了CATEGORY_DEFAULT类型,那么函数startActivity()和函数startActivityForResult()将会派发给这个组件intent。如果你的组件中没有声明这个类型(CATEGORY_DEFAULT),那么你的组件将不会接收到任何隐式intent.

下面例子是在一个activity中声明一个intent filter来接收包含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>

在一个filter里面可以包含多个<action><data><category>。如果你这样做,确保这个组件可以处理任何这些限制条件组合的所有情形。

但你需要处理多个不同类型的intents,但是是这些action,data, category的特殊组合,那么你必须创建多个独立的intent-filter。

一个隐式的intent通过与filter里面的action,data, category比较来选择启动的组件。一个intent必须通过action,data, category这三个限制条件才能被组件接收。一旦不符合其中一项,android系统将不会将这个intent派发给不符合的组件。但是在一个组件中有多个filter,一个intent可能不通过某个filter,但是符合其他filter。更多详细信息请参照Intent Resolution.

注意:为了防止不经意启动不同app的Service,一定要使用显式的intent来启动你自己的service。而且在对应的service不要添加filter.

注意:对于所有的activities,你必须在manifest文件中声明相应的filter。但是对于广播接收者可以通过调用registerReceiver()函数来动态的注册。可以广汉市unregisterReceiver()来解除。这样做可以让你的app在特定的时间段监听特殊的广播信号。

filter例子

为了更好的理解一些intent filter,下面代码段是一个社交分享app的filter.

<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" action 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看到的第一个activity:

其中的ACTION_MAIN表明这是app的入点,不希望其他intent数据。

其中的CATEGORY_LAUNCHER表明这个activity的图标在系统app的launcher。如果<activity>元素中没有指明图标,那么系统将使用<application>中指定的图标。

这两个元素必须成对出现,以便activity出现在app的launcher。

第二个activity,ShareActivity,是为了分享文本和多媒体内容。用户可能通过MainActivity进入到这个组件,也有可能通过其他app进入到这个组件中。其它app必须通过声明一个隐式的intent匹配对应的两个filter。

注意:MiME类型:application/vnd.google.panorama360+jpg是Google panorama中一种特殊的panoramic图片数据类型。

使用一个Pending的intent

一个PendingIntent对象是intent对象的封装。PendingIntent是为了保证外界的app能够使用已经包含的Intent,正如在自己的进程中执行一样。

对于pending intent的主要用处包括:

声明一个intent当用户在你的通知下执行一个动作执行(android系统的NotificationManager执行Intent)

声明一个intent当用户用你的app widget执行一个动作是被执行(home screen app 运行intent)

声明一个intent在特定的时间呗执行(android系统的alarmManager运行intent)

因为每个intent对象是被用于启动其他组件的(Activity, Service, BroadcastReceiver)。所以PendingIntent也应该这样。当使用一个pending intent是,你的app不会通过调用startActivity()来启动这个PendingIntent,而是通过下列函数来创建一个PendingIntent:

通过PendingIntent.getActivity()用于intent启动一个activity

通过PendingIntent.getService()用于intent启动一个service

通过PendingIntent.getBroadcast()用于intent启动一个broadcastReceiver

除非你的app从其他的app接收pending intent。.否则上面的方法是所有你用于创建PendingIntent的方法。

每个函数以单签的app的Context, 你想要的Intent和一个活多个指定这个intent会怎么被用(比如一个intent是否可以被用多次)作为参数。

更多关于使用pending intents的信息请参照Notifications和App widgets.


Intent Resolution

但系统接收一个隐式的intent来启动一个activity是,它会通过比较intent的内容和组件中intent-filter中的action,data, category来找到最合适的activity:

下面内容描述了一个intent怎么匹配到合适的组件

Action测试:

为了指定接收的intent的动作,一个intent-filter可以声明多个<action>,例如:

<intent-filter><action android:name="anroid.intent.action.EDIT" /><action android:name="android.intent.action.VIEW" />....</intent-filter>

为了通过这个filter,在intent声明的action必须匹配filter <action>其中的一个actions

如果filter中没有包含action,那么将没有intent可以匹配。但是如果一个intent没有声明action,那么这个intent将通过所有filter的action部分(但filter至少含有一个<action>.


Category test

组件通过在intent-filter中的<category>声明适合的category。例如:

<intent-filter><category android:name=“android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" />...</intent-filter>

对于想要通过category测试的intent,它里面的每个category必须匹配filter的category。反过来则不是,在filter可以声明多个categories, 这些可以在intent中没有。因此,一个没有包含category的intent可以通过所有的category测试。

注意:android自动在每个传给函数startActivity()和startActivityForResult()的intent添加CATEGORY_DEFAULT。所以如果你想让你的组件接收隐式intents,这个组件必须包含CATEGORY_DEFAULT这个category,即在intent-filter中的category声明android.intent.category.DEFAULT


Data test

通过在intent-filter中<data>声明data属性,比如

<intent-filter><data android:mimeType="video/mpeg" adnroid:scheme="http" .../><data android:mimeType="audio/mpeg" android:scheme="http" .. />...</intent-filter>

每个<data>指定一个URI结构和数据类型(MIME类型).对于URI,有多个部分:

<scheme>://<host>:<port>/<path>

比如:

content://com.example.project:200/folder/subfolder/etc

在这个URI中,content是scheme,com.example.project是host,200是port, folder/subfolder/etc是path。

这些元素在<data>都是可选的。但是有线性相关性:

如果scheme没有指定,那么host忽略

如果host没有指定,那么port忽略

如果scheme和host都没有指定,那么path忽略

当一个Intent的URI与filter的URI进行比较时,只是进行部分比较:

如果在filter只指定scheme,那么包含这个scheme都匹配这个filter。

如果在filter指定一个scheme和一个authority,但是不包含path,那么所有跟这个scheme和authority都符合这个filter,不管path。

如果在filter指定一个scheme,一个authority和一个path,那么只有相同的scheme,authority,path才符合这个filter。

注意:在path中可以包含*来部分匹配。

data测试将intent中的URI和MIME与filter中对应项进行比对,规则如下:

一个不包含URI和MIME类型的intent只能通过没有指定URI和MIME的filter

一个只含有URI的intent只能通过匹配的URI的filter,并且这个filter没有指定MIME类型。

一个只含有MIME的intent只能通过相同的MIME的filter,并且这个filter没有指定URI。

一个既包含URI和MIME的intent,其中的MIME类型必须是filter中MIME类型的一种,其中的URI要么跟filter中的URI匹配,要么intent的URI中有content或者file元素,而且filtr没有指定URI。换句话说,只有MIME的组件默认支持content::和file:类型的URI。

最后一个规则表明组件可以从一个file或者content provider获取数据。因此,它们的filter可以仅仅列出MIME类型,而不用特别的指定content和file元素。下面例子表明这个组件可以从一个content provider获取并显示图片:

<intent-filter><data android:mimeType="image/*" />...</intent-filter>


因为大多数可用的数据都是有content provider提供,所以filter仅仅指定data类型是最常见的一种。

另外一种常见的配置是filter中包含一个scheme和data类型。比如下面的例子组件可以通过网络接收视频来执行相应的动作。

<intent-filter><data android:scheme="http" android:type="video/*" /><intent-filter>


Intent 匹配

Intent不仅用来发现目标组件以便激活,而且可以用来发现设备上的组件信息。比如,Home app通过找出在组件的filter包含action有ACTION_MAIN和category有CATEGORY_LAUNCHER的组件来启动。

同样,你的app也可以使用类似的组件匹配方式。PackageManager有一系列的query...()方法来返回符合一个特定intent的组件,也有一系列类似的resolve...()函数来决定哪个组件最适合响应对应的intent。例如,queryIntentActivities()返回符合特定intent的组件列表,类似queryIntentServices()返回service列表。这两个函数都没有启动组件,仅仅是列出符合的组件,类似,queryBroadcastReceiver()列出broadcast receiver列表。








0 0