Intents and Intent Filters

Intent是一个我们可以用来请求操作其它组件的消息体。虽然intent使用不同的方式在组件间通讯,但是主要是三个基本的用法:
1.启动activity
app中一个activity代表着一个单独的屏幕。我们可以传递intent给startActivity方法来启动一个activity的实例。intent事有要启动activity的信息和需要的数据。如果我们想要在activity finish的时候再接收一个结果,请使用startActivityForResult.我们的activity会在自己的onActivityResult回调方法中收到一个单独的intent。想了解更多,请参阅activity.
2.启动一个service
Service是一个没有用户界面的后台运行处理任务的组件。我们可以传递intent给startService来启动一个service来执行一次性的任务(比如下载文件)。Intent带有要启动service的信息和必要的数据。
如果Service有一个client-server的接口,我们可以传递intent给bindService来从其它组件中绑定服务。想了解更多,请参阅服务。
3.传递一个广播
广播是任何app都可以接收的消息。系统会为系统事件(比如,开机,充电)发送各种各样的广播。我们可以传递intent给sendBroadcast(), sendOrderedBroadcast(),或者sendStickyBroadcast()来发送广播。

Intent类型
两种intent类型:
1.显式intent是指定需要启动组件的name(包括包名的全名称)。通常启动自己app的组件都使用显式intent,因为我们知道要启动activity或者service的名字。比如,响应用户的操作去启动一个activity或者在后台下载一个文件。
2.隐式intent不需要指定组件的name,但是需要声明一些要执行的操作来让其它app的组件去执行。比如,想给用户展示一个地图上的位置,我们可以使用隐式intent来请求另外一个有这种功能的app去展示位置。

Intents and Intent Filters_第1张图片

图片中展示了隐式intent是如何在系统中传递并启动另外一个activity.
1.Activity A 创建了一个带有action的intent传递给startActivity。
2.系统搜索所有的app的intent filter来匹配这个intent.
3.当找一个匹配的,系统会启动这个ActivityB并调用它的onCreate传递intent作为参数。

当我们创建一个隐式intent,系统会通过比较intent的内容和设备上其它app在清单文件中声明的intent filters来找到一个合适的组件去启动。如果找到了一个,系统会启动这个组件并传递这个intent.如果找到多个匹配的intent filters,系统会弹一个对话框来让用户选择。

intent filters是在清单文件中用于指定组件想要接收的intent类型的描述。比如,声明一个activity的intent filters, 这样其它app就可以直接使用一定的intent来启动我们的activity.同样的,如果我们没有为activity声明intent filters,它就只能通过显式intent启动。

注意:为了保证app的安全,使用显式intent来启动一个service,不要为service声明intent filters. 使用隐式intent来启动service是有安全漏洞的,因为我们不能确定哪个service会响应这个intent,同时用户也无法看到是哪个service启动了。API21开始,如果使用隐式intent来绑定服务系统会抛异常。

创建一个Intent

intent包含了两部分信息,一是系统用来决定启动哪个组件的信息(比如组件的名称或者接收intent的组件类别),另外就是接收组件执行操作需要使用到的信息(比如操作的类型和需要操作的数据)。

intent包含的主要信息是下边的:
1.Component name
要启动组件的名称。
这个是可选的,但是对于显式intent来说它是非常重要的,它表明了intent要传递给哪个组件。如果没有名称,intent就是隐式的,系统通过intent的其它信息(比如,action,data或者category)来决定哪个组件接收.所以,如果需要望到一个自己app的组件,一定要指定名称。

注意:当启动一个service时,我们应该总是指定名称,否则,我们不能确定哪个service会来响应intent,并且用户无法感知哪个service启动了。

intent的这个属性是一个ComponentName对象,我们可以使用目标组件的类名全称,包括app的包名。比如com.example.ExampleActivity. 我们可以通过setComponent(), setClass(), setClassName(),或者构造来设置component name。

2.Action
指定需要进行的操作(比如查看或者选择)的字符串。
在广播的intent中,Action是已经发生并且正在被报道的操作。Action主要决定了其它的部分(主要是数据和extra)的结构。

我们也可以在应用内自定义intent来让自己的app或者其它app来调用我们的组件,但是我们应该使用Intent类或者其它framework类的静态常量。下面是一些启动activity的常用Action:

a.ACTION_VIEW
当我们有信息需要展示给用户时可以使用这个action.比如图库app的查看相片,或者地图app查看地址。
b.ACTION_SEND
也可以叫做分享intent,当用户需要把一些数据通过其它app分享出去的时候,使用startActivity和带有这个action的intent.比如一个邮件app或者一个社交分享app.

查看Intent类可以看到更多的可以用来定义action的常量。其它的action在framework中定义,比如一些设置的action可以打开系统设置app的某个界面。

我们可以通过intent的setAction或者构造来设置action.

如果自定义acction,一定要使用包名作为前缀,比如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";

3.数据
要操作的数据或者数据的类型的URI。数据类型通常是由action来决定的。比如,如果是ACTION_EDIT,那么数据就一定是一个需要编辑的文件的URI.
创建intent时,指定数据类型通常也是很重要的。比如,即使URI非常相似,一个可以展示图片的activity通常不能播放音频。所以指定数据类型可以帮助系统找到最合适的组件。但是数据类型有的时候是可以通过URI来推断的。特别是当数据的URI是content:,这表明了这个数据是在设备的一个contentProvider,所以它的数据类型对系统是可见的。

使用setData来设置数据,使用setType来设置数据类型。必要时,使用setDataAndType来明确它们两个。

注意:如果想要设置两个,不能分别调用setData和setType,因为它们会把对方重置,一定要使用setDataAndType。

4.类型

一个包含了有关哪种组件可以处理这个intent的字符串。一个intent可以包含任意数量的category,但是很我intent不需要category.下边是一些常用的category:
a.CATEGORY_BROWSABLE
目标activity允许自己被浏览器的链接启动,比如一个图片或者邮件的链接。
b.CATEGORY_LAUNCHER
一个任务栈的初始activity,同时也会被列入系统启动图标。
查看Intent类来了解更多。

可以使用addCategory来指定一个category。

上边列出的(component name, action, data, 和 category)是intent的主要组成部分。通过这些,系统可以解析出来启动哪个组件。
但是,intent还可以携带一些不影响解析的信息,比如:
Extras

键值对携带了一些完成操作需要的信息。和一些操作需要使用特定的数据URI一样,一些操作也需要使用特定的extras.

通过putExtra来添加extra数据,每次有两个参数,key和value。也可以把所有的extra data放到一个bundle对象中,然后使用putExtras来传递这个bundle.

比如,创建intent来发送邮件时,我们会使用ACTION_SEND,可以使用EXTRA_EMAIL来指定接收者,EXTRA_SUBJECT来指定主题。

Intent类有很多EXTRA_常量来给那些标准的数据类型服务。如果需要自定义key,请使用包名作为前缀。比如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

Flags
在intent类中定义,充当了intent的元数据。falg可以指导系统如何启动一个activity(比如,activity应该在哪个任务栈),启动后如何把处理(比如,是否要把它放到最近启动的任务列表)

参考setFlags来了解更多。

显式intent示例
显式intent必须指定一个启动的组件。比如,一个应用中的activity或者service.定义显式intent时,必须定义组件名称,其它的属性都是可选的。

比如,应用中有一个叫做DownloadService的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和组件的Class对象.这样就可以显式的启动应用的DownloadService.

参阅Services了解更多关于创建和启动service.

隐式intent示例
隐式intent可以启动设备上任何app,如果app可以执行intent指定的action.
当我们的app不能执行某个操作时,其它app很能可以执行,我们可能希望用户来选择哪个app去执行。这时使用隐式intent非常有用,

比如,我们想让用户分享一些东西给其它人,创建一个带有ACTION_SEND的action和那些需要分享的内容的intent。然后使用startActivity调用刚才的intent,用户可以选择一个app来分享内容。

注意:用户可能没有任何一个app可以处理我们发送给startActivity的intent。这时,调用失败,app会崩溃。为了校验是否有一个activity可以接收这个intent,可以使用resolveActivity来验证。如果返回值非空,那么至少有一个app可以处理intent,调用startActivity就是安全的。如果返回值为空,我们就不应该使用这个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);
}

备注:示例中,没有使用URI,但是使用了数据类型来限定extras的内容。

当startActivity调用时,系统会检查所有已安装的app来决定使用哪个来处理当前的intent(带有ACTION_SEND的action和数据类型为"text/plain").如果只有一个app可以处理,这个app就会启动,然后接收到这个intent。如果有多个activity可以接收这个intent,系统会弹出一个对话框来让用户选择一个。

强制使用一个app选择器

当有多个app可以响应我们的隐式intent时,用户可以选择一个app使用,并且设置它为默认处理这个action的app.这是很方便的,因为对于同一个action,用户很可能想使用同一个app来处理,比如打开一个网页(用户通常使用一个浏览器)。

但是,如果有多个app可以响应intent,用户也可能想每次都使用不同的app,这时我们需要明确的弹出一个选择对话框。对话框来引导用户每次选择一个应用(用户不能为这个action选择一个默认的app).比如,当我们的app使用ACTION_SEND的action来分享时,用户可能需要根据当前的情况来选一个不同的app,所以我们应该使用选择器。


Intents and Intent Filters_第2张图片

使用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
为了告知系统,我们的app可以接收哪些隐式intent,我们可以在清单文件中自己的组件节点里添加节点来声明一个或多个intent filters.每个intent filter根据intent的action,data和category指定它可以接收的intent类型。只有intent可以至少通过一个我们的intent filters时,系统才会把一个隐式intent传递给我们的app.

备注:显式intent总是直接传递给目标,不论组件是否声明了intent filters。

app组件应该为每个它可以做的工作都单独声明一个filter. 比如,图库app的一个activity可能有两个filter:一个可以查看相片,一个可以编辑相片。当activity启动时,它会检查intent,然后根据intent的信息(比如是否显示编辑器)来决定如何执行。

每个intent filter都是在app的清单文件中对应的组件节点(比如节点)下声明一个节点。在内部,我们可以使用一个或多个下边的三个节点来指定可以响应的intent:
1.
在name属性下声明可以接收的intent action。name的值必须是字符串,不可以是类里的静态常量。
2.
使用一个或多个属性来指定数据的URI(scheme, host, port, path等)和类型,声明可以接收的数据类型。
3.
在name属性下声明可以接收的intent category。name的值必须是字符串,不可以是类里的静态常量。

备注:为了可以接收隐式intent,必须在intent filter中声明 CATEGORY_DEFAULT category。startActivity和startActivityForResult会认为所有的intent都有 CATEGORY_DEFAULT category。如果没有为我们的intent filter声明 CATEGORY_DEFAULT category,我们的activity就不会响应任何隐式intent.

比如,下边是一个activity声明intent filter来接收ACTION_SEND和数据类型是text的intent:


    
        
        
        
    

一个filter包含多个, , or 。如果我们能够确定组件可以处理任何一个或者多个组合节点,那么就可以这么做。

当我们想要处理多种类型的intent时,但是只有特定的action,data和category组合才可以时,我们就需要创建多个intent filters.

限制访问组件

使用intent filter不是一个阻止其它应用启动我们的组件的安全方法。虽然intent filters可以限制我们组件只能响应一些固定类型的隐式intent,但是如果开发者知道我们的组件名称,那么他们就可以使用显式intent来启动。如果确实需要只让自己的app来启动组件,就需要把exported属性设置为false.

隐式intent是通过逐一比较intent和intent filter的三个节点来测试的。intent必须通过三个测试才能传递给组件。哪怕只有一个没有通过,系统也不会把Intent传递给组件。但是,因为组件可能有多个intent filters,所以intent没有通过一个intent filter不代表它不能通过另外一个。参阅下边的Intent Resolution来了解更多。

注意:为了避免无意中启动了另外一个app的service,总是使用显式intent来启动自己的service,不要给service设置intent filters.

备注:所有的activity都必须声明intent filters. 但是,广播接收者的intent filters可以通过registerReceiver来动态的添加。然后使用unregisterReceiver来取消订阅。这么做可以让我们的app在运行时的固定时间接收固定的广播。

filters示例

为了更好地理解intent filter,请看从一个社交app的清单文件中节选的一段代码:


    
    
        
        
    



    
    
        
        
        
    
    
    
        
        
        
        
        
        
    

第一个MainActivity是app的入口,用户点击桌面图标启动的就是它:

  1. ACTION_MAIN表明这是app的主入口,并且不要求intent数据。
  2. CATEGORY_LAUNCHER category表明这个activity的icon应该放到系统的启动器中,如果activity没有指定icon,系统会使用application的icon.
    两个必须一起配置才能让activity出现在app启动器中。

第二个ShareActivity是来方便分享文本和多媒体内容的。虽然用户可以从MainActivity进入到这个activity,用户也可能从其它app中使用一个可以匹配这两个intent filter的intent来直接进入。

备注:类型application/vnd.google.panorama360+jpg,是一个特殊的数据类型,我们可以使用google全景的api来处理它。

使用Pending Intent

Pending Intent是intent的包装器。使用pendingIntent的主要目的是授权其它应用处理intent,就好像在我们自己的进程处理一样。

使用pending intent的主要场景:
1.当用户需要使用Notification时(系统的NotificationManager来执行这个intent).
2.当用户需要使用App Widget(系统的screen app来执行这个intent).
3.当用户需要执行一个定时任务时(系统的AlarmManager来执行这个intent).

因为每个intent都是被特定的组件处理的(activity,service或者broadcastreceiver),所以pendingIntent创建时也需要考虑这些。使用pending intent时,我们的app是不会通过像startActivity这样的方式来调用的。所以我们应该在创建pending intent时声明哪些组件来处理:

  1. PendingIntent.getActivity() 是启动activity。
  2. PendingIntent.getService() 是启动service。
  3. PendingIntent.getBroadcast()是启动BroadcastReceiver.

除非我们的app接收其它app的pending intent,否则上边的方法就是我们在创建pendingintent时,唯一需要使用的方法。

每个方法都会取得当前app的context,intent和一个或多个指定intent使用方式(比如一个intent是否可以重复使用)的flag.

参阅Notification和app widgets可以了解更多。

Intent解析

当系统接收到一个隐式intent启动activity时,它会通过根据下列三个方面来比较intent和intent filters来搜索最合适的activity.
The intent action
The intent data (both URI and data type)
The intent category

下边分析intent filter是如何在清单文件中声明及intent是如何匹配到合适的组件。

Action 测试
要指定接收的actions,intent filter可以声明0个或多个节点,比如:


    
    
    ...

为了通过这个filter测试,intent的action必须匹配filter中的一个action.

如果filter没有列出任何一个action, 就没有东西给intent来匹配,所有的intent就会测试失败。但是如果intent也没有指定action,就会通过测试(只要filter包含至少一个action)。

Category 测试
要指定接收的categories,intent filter可以声明0个或多个节点,比如:


    
    
    ...

intent想要通过category测试,intent中的每个category都需要匹配filter中的一个category。反方向不是必须的,intent filter声明的category可以多intent指定的多。当然, 不论filter声明的是什么category,没有category的intent总是可以通过测试.

备注:系统会为所有传递给startActivity和startActivityForResult的隐式intent添加一个CATEGORY_DEFAULT category,所以如果想让我们的activity来接收隐式intent,必须在intent filter中声明一个"android.intent.category.DEFAULT"。

Data 测试
要指定接收的data,intent filter可以声明0个或多个节点,比如:


    
    
    ...

每个data节点都可以声明一个URI和数据类型(MIME).每个URI可以有不同的属性:scheme, host, port, and path.

://:/

比如:

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

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

每个属性都是可选的,但是却有线性依赖:
1.如没有指定scheme,host会被忽略。
2.如没有指定host,port会被忽略。
3.如没有指定scheme和host,path会被忽略。

intent和URI和filter的URI比较时,只比较filter中URI的一部分。比如:
1.如果filter只指定了scheme,所有带有这个scheme的URI都通过测试。
2.如果filter指定了scheme和authority,所有带有这个scheme和authority的URI都通过测试。
3.如果filter指定了scheme,authority和path,所有带有这个scheme,authority和path的URI都通过测试。
备注:path可以包含通配符(*)来匹配一部分的path名称。

数据测试会比较URI和MIME类型。规则如下:
1.只有当filter没有指定任何URI和MIME时,没有URI和MIME的intent才可以通过测试。
2.包含URI但是没有MIME(既没有显式声明,也不能根据URI推断出来)的intent,只能通过那些filter带有匹配URI并且也没有指定MIME的测试。
3.有MIME但没有URI的intent,只能通过那些filter列举相同MIME并且没有指定URI的测试。
4.包含URI和MIME(显式声明,或者根据URI推断出来)的intent,匹配filter的类型通过MIME测试,如果它的URI匹配filter的URI,或者filter没有URI,它有一个contet:或者file:的URI,这样它就通过了URI测试。也就是说,如果一个filter只有MIME类型,那么它默认支持content:和file:的data.

最后一条规则,说明了组件可以取得本地的文件和content provider数据。所以,它们的filter可以只列举data type,不需要显式声明content:和file:的scheme.下边是一个典型的示例,一个组件可以从content provider取得图片并展示:


    
    ...

因为大部分的数据都是由content provider提供的,所以filter只指定数据类型没有URI是非常常见的。

另外一个常见的filter配置,有scheme和data type,比如:一个组件可以从网络取得视频数据:


    
    ...

Intent 匹配

匹配intent不仅仅是为了找到一个可以启动的组件。也可以发现设备上组件的信息。比如,Home app是通过找到所有指定了ACTION_MAIN action 和CATEGORY_LAUNCHER category的filter的activity来填充界面的。

我们的app也可以使用类似的方式。PackageManager有一组query...()的方法可以返回所有接收固定intent的组件和类似的resolve...()方法来返回最合适的组件。比如queryIntentActivities返回可以执行intent参数的activity.queryIntentServices返回类似的一组service.这些方法都不会启动组件。他们只是列出可以响应的组件。对于broadcast receivers也有对应的queryBroadcastReceivers.

你可能感兴趣的:(Intents and Intent Filters)