以下内容摘抄自作者:任玉刚所著的《Android开发艺术探索》
IntentFilter介绍
Activity启动分为两种,显示启动和隐式启动。显示启动需要明确指定被启动对象的组件信息:包名、类名等,而隐式启动则不需要。
原则上,显示与隐式是不会同时存在的,如果存在,那么以显示调用为主。
隐式调用需要匹配IntentFilter中所有设置的过滤信息如:action、category、data。如果不匹配,将无法启动目标Activity。
IntentFilter添加方式需要在AndroidManifest.xml中,配置如下信息:
<activity android:name=".MainActivity4">
<intent-filter>
<action android:name="com.example.a" />
<action android:name="com.example.b" />
<category android:name="com.example.category.a" />
<category android:name="com.example.category.b" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
为了匹配过滤列表,需要同时匹配action、category、data信息,否则匹配失败。一个过滤列表中action、category和data可以有多个,所有的action、category、data构成不同类别,同一类别的信息共同约束当前类别的匹配过程。只有一个Intent同时匹配这三个类别,才算完全匹配,才可以成功启动目标Activity。
另外,一个Activity可以有多个IntentFilter,匹配时只需要与一组相符,即可成功启动。下面将详细介绍每种类别。
action
action是一个字符串,系统已经预设定了一些action,同时我们也可以自定义自己的action。Intent的中的action和IntentFilter中设定好的action字符串完全一致,即符合匹配规则。一组匹配规则中可以包含多个action,只需要匹配任意一个即可。如上示例中,我们只需将Intent的action的值设定为“com.example.a”或者“com.example.b”都可以完成匹配。
需要注意:Intent中如果不指定action值,将匹配失败!还需要注意,action是区分字母大小写的,大小写不同的字符串也会导致匹配失败!
category
category也是一个字符串,系统中也预定义了一些category,同时我们也可以自定义一些category。
category与action不同,Intent中可以不设置category,也会匹配成功,但如果设置了category,必须与过滤规则中的一个相同。如上例子中,我们在Intent中可以给category设置"com.example.category.a"或者"com.example.category.b"或者什么都不设置。
为什么什么都不设置也可以呢,因为系统在调用startActivity()或者startActivityForResult()的时候会默认为Intent加上“android.intent.category.DEFAULT”这个值。
所以在上面的例子中,为了使我们的Activity被隐式调用到,我们在匹配规则的category中也添加了“android.intent.category.DEFAULT”,以保证Intent中调用我们的Activity的时候,即使不添加category也可以匹配成功。
data
data的基本语法包含几个属性,如下所示:
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string"/>
data如上所示,有两部分组成,第一部分是由:scheme、host、port、path、pathPattern、pathPrefix组成的URI部分,第二部分是mimeType,指媒体类型,可以比表示文本、图片、视频等不同的媒体格式。
URI部分的数据结构如下:
<scheme>://<host>:<prot>/[<path>|<pathPattern>|<pathPrefix>]
接下来介绍下每个数据的含义:
Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其它参数无效,即该URI无效。
Host:URI的主机名,比如www.baidu.com,如果Host未指定,URI的其它参数也无效,即该URI也无效。
Port:URI中的端口号,比如8080,仅当URI中指定了scheme和host时,它才有效。
Path、PathPattern和PathPrefix:表示路径信息。
(1)其中path表示完整的路径信息
(2)pathPrefix表示路径的前缀信息
(3)pathPattern也表示完整的路径信息,但是它里面可以包含通配符“”。“”表示0或多个任意字符串。需要注意的是,由于正则表达式 的规范,在Intent中添加时,需要将 *
写为\\*
。而\
要写成\\\\
。
data中的匹配规则与action一样,Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。这里的完全匹配指的是过滤规则中的data内容也出现在了Intent中,几种情况如下:
(1)第一种情况,只包含了mimeType属性:
<intent-filter>
<data android:mimeType="image/*" />
...
</intent-filter>
这种规则指定了媒体类型为所有类型图片,那么Intent中的mimeType属性必须为“image/*”才能匹配,这种情况下并没有指定URI,但实际上有默认值:content和file,也就是说在Intent设定的时候,必须设置scheme为content或file才能匹配。比如我们想要匹配以上规则,可以将Intent写成如下格式:
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
注意:如果要指定完整的data,必须使用setDataAndType()方法,不可以分别调用setData()和setType(),因为这两个方法会互相清除彼此的值,比如源码中这样写到:
public @NonNull Intent setData(@Nullable Uri data) {
mData = data;
mType = null;
return this;
}
public @NonNull Intent setType(@Nullable String type) {
mData = null;
mType = type;
return this;
}
可以发现setData()将mimetype设置为null,而setType()将URI也设置为了null。
(2)接下来说说第二种情况:
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" .../>
<data android:mimeType="audio/mpeg" android:scheme="http" .../>
</intent-filter>
以上匹配规则中定义了两组data,且每组data都有完整的属性值,既有URI又有mimeType,想要匹配以上规则,可如下调用:
intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg");
或者
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg");
另外在AndroidManifest中,data可以有两种写法,如下所示:
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http"/>
</intent-filter>
<intent-filter>
<data android:scheme="http"/>
<data android:mimeType="video/mpeg"/>
</intent-filter>
学到这里后,我们在本篇文章开始所写的示例中,我们可以给Intent添加如下配置,就可以调用了:
Intent intent = new Intent("com.example.a");
intent.addCategory("com.example.category.a");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);
另外Intent-filter的匹配规则其实对Service和BroadcastReceiver也是同样的道理,不过系统建议Service还是使用显示调用来启动。
最后,但我们隐式调用Activity时,可以做一下判断,看看是否有Activity符合我们的匹配,如果不做判断会有报错ActivityNotFound,提示无法找到可匹配的Activity。
判断方法有两种:
(1)采用PackageManager的resolveActivity()方法或者Intent的resolveActivity方法,如果找不到对应的Activity会返回null,这时判断返回值即可。
(2)packageManager还提供了queryIntentActivities()方法,它与第一种里的两个方法不同的是,它会返回所有成功匹配的Activity信息,而不是返回最佳匹配的Activity信息。
resolveActivity()和queryIntentActivities()源码如下:
@Nullable
public abstract ResolveInfo resolveActivity(@NonNull Intent intent, @ResolveInfoFlags int flags);
@NonNull
public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, @ResolveInfoFlags int flags);
注意:上面第二个参数flags,我们需要设定标记位为:MATCH_DEFAULT_ONLY,这个标记位的含义是仅仅匹配到那些在intent-filter中声明了<category android:name="android.intent.category.DEFAULT" />
这个category的Activity。使用这个标记位的意义在于,只要上述两个方法返回值不为null,那么startActivity一定可以成功的。如果不用这个标记位,就会把不包含DEFAULT属性的Activity也匹配出来,而这些匹配出来的Activity无法支持隐式调用,从而导致startActivity失败。
学完以上这些,聪明的同学肯定已经发现了,我们Android的入口MainActivity在AndroidManifest中有这样两行配置:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
这两个过滤条件是在表明:这是一个入口Activity,并且会出现在系统的应用列表中,缺少任一一个,都将没有实际意义,也将无法出现在系统应用列表中。
另外针对Service和BroadcastReceiver,PackageManager也提供了类似的方法去获取成功匹配的组件信息。