前言
我们都知道,启动Activity有两种方式,即隐式启动和显式启动。从优先级来说,显式启动要优先于隐式启动。隐式启动的优点是使用上的灵活性。因此,掌握隐式启动中IntentFilter的匹配规则就至关重要了。
对一个Activity而言,可以在AndroidManifest文件中指定多个IntentFilter,Intent只要能够匹配其中任意一个就算匹配成功。在一个IntentFilter中,可以有多个action、category和data。所有的action组成action类别。同理,所有的category和data分别组成category类别和data类别。Intent需要和action类别、category类别和data类别全部匹配成功,才算是和这个IntentFilter匹配成功。针对不同的类别,有着不同的匹配规则,具体细节见下文。
action的匹配规则
直观来说,action是一个字符串。Android系统提供了一些预定义的action,它们都有着自己的含义,我们可以直接使用。当然,我们也可以定义自己的action,以便满足我们自己的业务需求。在一个IntentFilter中,可以有多个action。Intent只要能和其中任意一个 action匹配就算是匹配成功。否则,匹配失败。
category的匹配规则
category也是一个字符串,Android系统同样为我们提供了一些预定义的category。当然,我们也可以定义自己的category。category的匹配规则和action有所不同。一个IntentFilter中可以有多个category,并不要求Intent全部匹配。但是,要求Intent中添加的任意一个category,都必须已经包含在IntentFilter中了,否则匹配失败。
需要注意的是,即使不为Intent添加category,在调用startActivity
或者startActivityForResult
方法后,Android系统都会为Intent添加一个预定义的category,即android.intent.category.DEFAULT
。因此,我们需要为Activity的IntentFilter指定这个category,否则会造成匹配失败。
data的匹配规则
相比action和category,data的匹配规则要稍微复杂一点。首先,要明白data分为URI和mimeType两部分。一个典型的data结构如下所示:
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string">
</data>
android:mimeType
属性指定了mimeType部分,主要是指定媒体类型。如text/plain、video/*等。URI部分则由剩余的属性共同组成。一个URI的结构如下:
scheme://host:port/[path|pathPrefix|pathPattern]
具体的属性含义如下:
- scheme:URI的协议名,如http、file、content等,必须指定。
- host:URI的主机名,如www.coding.com,必须指定。
- port:URI的端口名,可选。
- path、pathPrefix和pathPattern:共同组成URI的路径内容。path和pathPattern都表示完整路径。不同之处在于pathPattern可以指定通配符
*
。pathPrefix则表示路径前缀。
一个IntentFilter可以包含多个data,Intent只要能够匹配其中任何一个data就算是匹配成功。对于单个data而言,Intent需要匹配所有内容才算是匹配成功。
需要注意的是,如果一个data没有指定URI部分,而只是指定了mimeType部分,这个data也是有默认URI的。URI部分会被Android系统指定为file和content。例如一个data如下:
<data android:mimeType="text/plain"></data>
如果我们要匹配这个data,就需要在Intent中指定URI为file或者content才行。如下所示:
Intent intent=new Intent();
intent.setDataAndType(Uri.parse("file://"),"text/plain");
需要注意的是,应该使用setDataAndType
方法为Intent设置URI和mimeType。因为如果单独使用setData
和setType
,它们会相互清除对方设置的值。
一个完整的匹配示例
IntentFilter如下所示:
<intent-filter>
<action android:name="com.codingending.action.test_1"></action>
<action android:name="com.codingending.action.test_2"></action>
<category android:name="com.codingending.category.test_1"></category>
<category android:name="com.codingending.category.test_2"></category>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:mimeType="text/plain"></data>
</intent-filter>
如果想要匹配上述IntentFilter,可以采取下面的方式:
Intent intent=new Intent();
intent.setAction("com.codingending.action.test_1");
intent.addCategory("com.codingending.category.test_1");
intent.setDataAndType(Uri.parse("file://"),"text/plain");
或者:
Intent intent=new Intent();
intent.setAction("com.codingending.action.test_2");
intent.addCategory("com.codingending.category.test_1");
intent.addCategory("com.codingending.category.test_2");
intent.setDataAndType(Uri.parse("file://"),"text/plain");
特殊的IntentFilter
在Android存在一个特殊的IntentFilter,如下所示:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
这个IntentFilter的作用是标识其所在的Activity是应用的入口Activity,即主Activity。这是应用启动后呈现给用户的第一个Activity。
最佳实践
使用隐式启动方式最大的缺点是可能匹配失败造成应用出现ForceClose错误,这样会给用户不好的使用体验。因此,在使用隐式启动方式时,最好先查询一下是否存在能够匹配的Activity。只有存在能够匹配的Activity时,才真正地去启动它。要达到这一目的,可以使用Intent的resolveActivity
方法。或者,也可以使用PackageManager的resolveActivity
或queryIntentActivities
方法。它们的方法原型如下所示:
Intent:
public ComponentName resolveActivity(PackageManager pm);
packageManager:
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
resolveActivity
和queryIntentActivities
的不同之处在于,前者会返回最佳的匹配结果,而后者将返回所有匹配成功的结果。
对于这两个方法中的flags参数,可以选择MATCH_DEFAULT_ONLY
或者MATCH_ALL
,一般选择前者。如果使用MATCH_DEFAULT_ONLY
参数,匹配过程中将会忽略category中不含android.intent.category.DEFAULT
的Activity,因为这些Activity无法被启动。原因前文已经说过。
通过判断这些方法的返回值是否为null,可以知道是否能够匹配成功。这样处理后,可以避免应用出现ForceClose错误,也优化了用户体验。