4.2 意图匹配

上一节介绍了基于意图的组件调用流程及Intent与Intent Filter对象的结构。本节将在此基础上具体介绍组件管理服务的意图匹配算法以及构造实现组件的具体细节。

4.2.1 意图匹配的算法流程

当组件管理服务接收到请求组件的Intent对象后,会先查看Intent对象是否包含了目标实现组件的Component信息。如果不包含,则会从应用管理服务中获取所有组件的Intent Filter信息,并与Intent对象相比较,选择符合需求的实现组件。其中,Intent对象与Intent Filter对象的匹配过程,在本书中称为意图匹配算法。

图4-3描述了意图匹配算法的流程。算法输入的是进行比较的Intent对象和Intent Filter对象,输出的是一个32位的整数值,用于表征两者的匹配程度。

如果返回值为负数,表示匹配失败,该Intent Filter对应的组件无法处理给定的Intent对象;反之,如果返回值为正,则表示匹配成功,并且,正值越大意味着匹配程度越高(数值的具体含义,参见图4-4)。

整个匹配算法的流程可分为三个步骤[1]

1)Action的比较:每个Intent Filter对象都必须包含Action信息,如果没有,则对任何一个Intent对象都会匹配失败。

如果Intent对象中不包含Action信息,就可以直接跳过Action的比较,进入下一个比较环节。但只要Intent对象中包含了Action信息,就必须要求该Action项包含在Intent Filter对象的Action列表中,否则,匹配失败。

2)Data和Type的比较:Data和Type信息是Intent Filter中最复杂的数据项,其比较算法是决定Intent与Intent Filter对象匹配程度(即算法返回值)的关键。

Data和Type的匹配流程如图4-5所示。首先如果Intent对象中不包含任何Data或Type项,那么,Intent Filter中也不能包含相关的信息,否则匹配失败;其次,Intent的对象如果包含Type信息,就必须要求Intent Filter的Type信息与之对应(基于通配符*的比较下相等),否则,匹配也将以失败告终;最后,如果Intent对象中含有Data项,则会将该Data项的URI信息拆分成Scheme和Authority等部分(参见图4-2),逐一与Intent Filter对象中对应的部分进行比较,只要两者有任何不相符的部分,匹配都会失败。

4.2 意图匹配 - 图1

图 4-3 Intent与Intent Filter匹配算法流程

4.2 意图匹配 - 图2

图 4-4 Intent与Intent Filter的匹配程度表示

如果上述这些比较都成功了,则会计算出两者的匹配值,进入下一个比较步骤。

3)Category的比较:在大部分组件调用中,如果Intent对象不包含任何Category项,就可以直接通过Category的比较匹配成功。如果Intent对象中包含Category项,那么必须要求其中包含的所有Category项都出现在Intent Filter的Category列表中,否则,匹配失败。

换而言之,当且仅当Intent对象的Category项集合,是Intent Filter对象Category项集合的子集时,匹配才会成功。

但是在进行界面组件的调用时,情况略有不同。如果调用组件发出的Intent对象没有添加Category项,系统会为其添加上Intent.CATEGORY_DEFAULT类别。这就要求Intent Filter对象中必须包含Intent.CATEGORY_DEFAULT类别,才能匹配此类Intent对象。

这样的设计,是为了降低组件调用者的负担,避免其在不知情的情况下被其他Category的实现组件干扰。同时,这也要求在配置界面组件时,如果期望该组件能够作为通用的功能组件被调用,就必须显性地添加Intent.CATEGORY_DEFAULT这个分类。

4.2 意图匹配 - 图3

图 4-5 Intent与Intent Filter的匹配程度表示

单看流程描述会略显抽象,换个角度,从实际应用出发去体会一下整个匹配流程。以Android原生的邮件应用为例,它为发送邮件的界面组件配置了如下的Intent Filter信息:


<activity android:name=".activity.MessageCompose"

…>

给特定对象发送邮件的Intent Filter配置

<intent-filter>

<action android:name="android.intent.action.VIEW"/>

<action android:name="android.intent.action.SENDTO"/>

<data android:scheme="mailto"/>

<category

android:name="android.intent.category.DEFAULT"/>

<category

android:name="android.intent.category.BROWSABLE"/>

</intent-filter>

其他的Intent Filter定义

</activity>


当调用者发出如下请求时,就会触发一次意图匹配:


Intent intent=new Intent(Intent.ACTION_SENDTO);

intent.putExtra(Intent.EXTRA_TEXT,"");

intent.setData(Uri.parse("mailto:duguguiyu@wandoujia.com"));

startActivity(intent);


组件管理服务会依照发送来的Intent对象,逐一与各个组件的Intent Filter进行匹配。在该Intent对象中,包含了Action的信息Intent.ACTION_SENDTO,该Action信息定义在发送邮件界面组件Intent Filter中,因此,可以通过Action比较。如果在调用时不慎漏掉了Action信息,这个Intent对象就无法与发送邮件组件相匹配。

接下来,组件管理服务会比较Intent对象与发送邮件界面组件Intent Filter的Data信息。Intent对象中的URI,是基于mailto的协议,与Intent Filter对象期望的一致,可以顺利通过Data信息的匹配校验。而如果调用者输入的URI是:


intent.setData(Uri.parse("sms:13488810330"));


那就不可能与发送邮件组件相匹配,而很可能与发送短信的组件匹配上。

最后一关是进行Category的检查。由于调用者是通过Context.startActivity函数发出请求,组件管理服务会自动为其添加上Intent.CATEGORY_DEFAULT的分类信息,该信息正好包含在了邮件发送组件的Category定义中。开发中常犯的一个错误,是忘了为Intent Filter添加Intent.CATEGORY_DEFAULT的分类信息。这会导致通过Context.startActivity发出的Intent对象都无法匹配该组件。

从上例中可以看到,发送者还为Intent对象添加了Extra信息。组件管理服务并不会在匹配时对这些信息进行校验,无论调用者是否添加,都会匹配上发送邮件组件。因此,没有了系统底层机制的保护,发送邮件组件本身需要妥善校验所有的Extra信息,提升鲁棒性,从而被调用时都能够呈现出正确的交互状态。

小贴士 组件的权限问题,并不会在匹配流程中进行考虑。也就是说,即便调用组件没有权限去使用某个实现组件,该实现组件依然会作为匹配项被挑选出来,直至构造或调用实现组件对象时,才会由于权限问题而导致失败。

[1]匹配算法的具体实现在Intent Filter对象的match函数中,另可参考SDK文档:http://androidappdocs.appspot.com/android-sdk-windows/docs/guide/topics/intents/intents-filters.html。