4.1.2 Intent对象的构成

Intent对象的这些作用,都是通过它的实现和设计体现出来的。从数据结构来看,Intent类的实现非常简单,它并没有包含复杂的逻辑功能,只是包含着若干个数据项。

❑Action项

在日常生活中描述自己的意愿或愿望时,总是用一个表达动作的词作为意愿的核心。比如:我要吃饭、我想写书、我要做俯卧撑,等等,其中的“吃”、“写”、“做”都是在说明整个意愿的动作,是整个意愿表达的核心之一。

在Intent中,Action就是用来表达动作的。当调用组件指明了一个Action,执行组件就会依照这个动作的指示,接受相关的输入,执行对应的操作,生成所期望的输出。在实现上,Action是一个字符串,可以调用Intent.setAction函数为Intent对象指定一个动作,也可以通过Intent.getAction函数读取Intent对象中的动作信息。为了保持动作的唯一性和实现的可扩展性,Action的命名格式遵循Java包的命名规范,比如可以这样自定义一个表示“吃”的动作:

com. duguhome.sample.action.ACTION_EAT

其中,com.duguhome.sample是应用的包名,action表示定义的是一个Action动作,而ACTION_EAT,则具体指明了该动作的含义。

在Android的Intent类中,定义了很多标准的动作,比如:Intent.ACTION_VIEW、Intent.ACTION_PICK等。这些标准动作约定了Android组件间的通信规范,保证了组件系统的可扩展性,使得那些相互独立的组件能够通过这些标准的Action信息彼此沟通。

如果系统预设的这些“标准动作”不能满足应用的需求,则各个应用都可以自行定义扩展。但与这些“标准动作”相比,自定义的Action不常用在跨应用的通信中,而仅应用于内部组件的通信[1]

❑Data项

继续从日常生活出发,来看看意愿的表达。除了作为动词的谓语,宾语对于意愿的表达也非常重要,比如说:“我吃宫保鸡丁”,其中主语是“我”,在意图机制下对应着调用组件;谓语“吃”,用Intent的Action对象来表达;宾语“宫保鸡丁”则对应着Intent的另一个数据对象:Data。

当发起请求时,调用组件如果有明确的数据对象,通常就会用Data项来存储表示。比如,在邮件应用中,需要第三方的图片浏览组件帮助展示附件中的图片信息,这时候,在请求查看图片的Intent对象中,就需要将图片的地址信息放在Data项中。

Data数据,可以通过Intent.setData或Intent.setDataAndType来进行设置,通过Intent.getData函数来读取。Data数据也是用字符串进行存储的,它的格式符合URI标准。URI具有丰富的表达能力,能够表达存储在任何地方的数据。比如,存放在本地文件系统中的数据可以通过URI来表示:


位于本地目录/sdcard/下的sample.data文件:

file:///sdcard/sample.data


同样,存放在数据源组件中的数据也可以通过URI来表示:


数据源组件com.duguhome.providers.smaple中id为1的数据:

content://com.duguhome.providers.sample/1


甚至,一个存放在Web上的数据,同样也可以通过URI来描述:


域名flyvenus.net中的sample.data文件:

http://flyvenus.net/sample.data


这样,通过URI来描述数据,使得Intent对象不仅能够用来请求本地的组件服务,同样还可以请求Web服务,将本地服务和Web服务有机地结合在一起。

❑Type项

如果说Data是用于描述“我吃宫保鸡丁”这样有具体操作目标的需求,那么Type就是用于表达“我吃菜”这样有特定类别信息的需求。也就是说,如果Data用于特指,那么Type用于泛指。

在Intent对象中,系统可以通过Intent.setType或Intent.setDataAndType函数设定Type信息,也可以调用Intent.getType函数获取该对象的Type信息。Type是MIME格式的字符串数据,用于描述组件能够处理的请求类型,或者补充说明Data数据的类型。它可以通过通配符*来表示整个类别的信息,比如:


用MIME表示图片类型的数据:

image/*


也可以更具体地指明特定子类别:


用MIME表示jpg图片类型的数据:

image/jpg


在Intent对象中,Data项和Type项很多时候是互斥使用的。当调用Intent.setType时,之前设定的Data信息就会被清空;反之调用Intent.setData时,Type信息也会被清除。一个Intent中,需要用Data表示数据还是用Type表示数据类型通常是和Action密切联系的。

比如Intent对象的Action项为Intent.ACTION_SEND,表示调用组件期望寻找一个组件来发送一些待选定的数据给指定用户。通常通过设置Type项来指明需要发送数据的类型,比如用text/plain表示发送文本类型的数据,用image/*表示发送图片类型的数据。

同样是发送数据,如果Intent对象的Action项为Intent.ACTION_SENDTO,则表示发送已选定的数据给用户。此时,就需要通过Data项来声明需发送数据的具体信息。

但在很多场景下,Data和Type也是相辅相成需要同时设定的,Data作为数据的载体,而Type则是对Data的一个补充说明。比如,如果想寻找一个图片组件来展示所需的图片,就需要将图片地址放入Data中,如下:


//构造查看图片的Intent

Intent intent=new Intent(Intent.ACTION_VIEW);

//添加图片地址信息

Intent.setData("file:///sdcard/image1.data");


但仅通过这个Data信息,组件管理服务并不能知晓这是一个指向图片的地址。它很可能会找出一个用于查看文本信息的组件来展示该图片,因此,就需要添加类似于image/jpg这样的Type信息,辅助说明Data指向的是一个图片地址,需要用图片浏览组件来查看。

但是如果按照如下方式进行设定:


//构造查看图片的Intent

Intent intent=new Intent(Intent.ACTION_VIEW);

//添加图片地址信息

intent.setData("file:///sdcard/image1.data");

//再添加Type信息,此时会清除原有的Data信息,导致请求失败

intent.setType("image/jpg");


就会导致之前设定的Data信息被清除,此时,就需要通过调用Intent.setDataAndType函数,同时指定Data和Type:


//构造查看图片的Intent

Intent intent=new Intent(Intent.ACTION_VIEW);

//同时添加图片的地址和类型信息

intent.setDataAndType("file:///sdcard/image1.data","image/jpg");


总而言之,Data与Type是一对“爱恨纠结”的朋友,需要在Action的指引下,用最合适的方式进行设置。

❑Category项

有了Action搭配着Data和Type,意愿描述的“主谓宾”就齐全了。但在某些场合下,表达的意图是有条件约束的,仅在条件满足的时候才能生效。比如如果一个意愿表达如下:“在吃撑了之后,我想去散步”,就说明“吃撑了”是“想去散步”这个意愿的条件,也就是说在没有“吃撑”的情况下,“想去散步”的意愿就不能够生效。

在Android中,Category便用于表示这类约束。每个Intent对象可包含多个Categroy项。使用Intent.addCategory方法可以为Intent对象添加Category项,通过Intent.getCategories方法可获取该Intent对象Category项的集合。同一个Intent中的多个Category项彼此间是“与”的关系,也就是说一个组件需要支持全部的Categroy项才可能处理该请求。

Intent对象中预定义了一些Category,包括Intent.CATEGORY_LAUNCHER和Intent.CATEGORY_ALTERNATIVE,等等。这些预设的Category常被用于附加一些特定的约束。例如,如果一个组件需要启动其他应用,且希望进入的是这个应用的入口组件,这时就需要添加Intent.CATEGORY_LAUNCHER作为约束。

开发者也可以自定义一些Category信息,其命名方式同样依赖于包名。比如,可以这样定义一个“吃撑了”的类别:


com.duguhome.sample.category.CATEGORY_FULL


其中,com.duguhome.sample是应用的包名,category定义的是一个Category项,而CATEGORY_FULL,则具体指明了该动作的含义。在应用中,如果有一个组件的实现过于简单,只希望在特定场合下使用,那么就可以为它增加Category的约束,只有明确了解这个Category定义的调用者,才能使用该组件。

❑Component项

Component指的是目标组件的类型信息,可以通过Intent.setComponent方法利用类名进行设定,也可以通过Intent.setClass方法利用类型对象信息进行设置。

当调用组件明确指定了Component信息,组件管理服务就不再需要根据Action、Data等信息去寻找满足其需求的实现组件了,只需要按照Component信息实例化对应的组件作为功能实现者即可。而对于实现组件而言,Component信息是完全透明的,它不需要关注自己是因为什么被构造的。

一旦指定了Component, Intent对象就变成了单纯的信息载体,只负责传递消息和数据。这种方式,通常用在应用内部组件的互联互通中。

❑Extras项

Extras是Intent中数据传输的载体,负责将数据从调用组件传递到实现组件。Extras项是一个android.os.Bundle对象,该对象按照键值对的方式存储数据,它实现了android.os.Parcelable接口,可以进行数据的序列化和反序列化,从而在进程间传递。

在Intent中,可以通过Intent.getExtras方法获取该对象并进行读写,而使用Intent.setExtra方法设定指定键值的数据对象。同时,Intent为特定数据类型的Extras对象读写提供一些便捷接口,可以通过Intent.getIntExtra等函数读取指定键值的数据值。

Extras使用的关键在于键值的定义,它需要调用组件和实现组件事先知晓每个键值的数据类型和含义。Adnroid中定义了很多标准的Extras数据键值,它们通常和标准的Action相辅相成,作为整个传输协议的一部分。Extras的键值定义与组件管理服务无关,因此,键值命名可以使用任意格式的字符串,而不需要考虑全局的唯一性。

❑Flags项

Flags是一个整形数,由一系列的标志位汇集而成。它对于实现组件而言完全透明,是调用组件指定组件管理服务构造实现组件的方式,常用于改变实现组件的任务模型和进程模型等。

比如,当Intent对象的Intent.FLAG_ACTIVITY_NEW_TASK标志位被设置时,组件管理服务会尝试在新的任务中构造目标组件,而不再是默认的原有任务。

Intent对象定义了所有的Flags标志位信息,可以通过Intent.setFlags方法对Intent对象的Flags信息进行全新地设置,也可以调用Intent.addFlags方法添加新的标志位信息。被设定的Flags标志位将会叠加生效。但需要注意的是,很多标志位之间是有相关联逻辑关系的。有的标志位需要同时设定,而有的标志位之间则具有一定的互斥性[2]

在实际使用中,每个Intent对象的各个数据项并不是孤立存在的,它们彼此配合彼此约束,共同构成组件间的通信协议。其中,Action的定义是整个协议的核心,每个特定的Action,都会有与之匹配的Data、Type、Extras等。

比如,Intent.ACTION_SEND动作用于指定使用某个组件发送信息,当使用Intent.ACTION_SEND时,Intent还需要指明对应数据的Type信息,并可以在Extras域中添加键值为Intent.EXTRA_TEXT的参数来添加需要发送的文本类型,添加键值Intent.EXTRA_SUBJECT来指明发送内容的主题,等等。这一系列的内容,共同构成了使用Intent.ACTION_SEND动作进行通信的协议标准。

在组件开发中,应该遵循SDK或第三方拟定的相关规范。当一个组件需要支持特定的Action时,就需要同时支持相关的Extras项和Data项等相关内容。协议的完整性和正确性是整个Android意图机制的根基,需要所有开发人员共同维护。

[1]有时候,如果一款应用做得足够出色,它也会定义一些Action供第三方应用使用,从而形成一个小范围的应用生态圈。

[2]Flags和组件任务模型有密切的联系,而与组件的连接和通信关系不大。因此,各个Flags标志位的含义和使用方式将放在第5章详细介绍。