我们都只知道隐式调用是去匹配相应的组件从而达到启动组件的目的,但是具体是怎么匹配的呢?我们就要详细来了解下IntentFilter。
IntentFilter中可以设置的过滤信息有三种,为action,category,data。
我们先看看一个简单的过滤规则
<activity android:name="SecondActivity">
<intent-filter>
<action android:name="android.intent.action.Second"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
这就是一个简单的Intent,我们可以通过如下规则来启动这个组件。
Intent intent = new Intent();
intent.setAction("android.intent.action.Second");
intent.addCategory("android.intent.category.DEFAULT");
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
startActivity(intent);
通过这个就可以隐式启动SecondActivity了。
这样我们对IntentFilter有了个基本的了解,下面我们在详细说说他的匹配规则。
1、action匹配规则
action:表示一个动作。显示调用中的所有action都要满足隐式里面的定义。可以不设置,默认使用DEFAULT。例如:
ACTION_MAIN:Android Application的人口。每一个application只且只能有一个该定义,表示应用的入口activity
ACTION_ANSWER:接听来电
ACTION_CALL:直接呼叫data总所带的电话号码
ACTION_PACKAGE_ADDED:Android系统安装了新的Application之后发出带有此Action的广播(Broadcast)
ACTION_VIEW:用于显示用户的数据。比较通用,会根据用户的数据类型打开相应的Activity。比如tel:13400010001打开拨号程序,www.g.cn则会打开浏览器等。
ACTION_SEND:由用户指定发送方式进行数据发送操作,比如微信分享,就是使用的该action,如下是一段图片分享的示例
public void shareImage() {
ComponentName comp = new ComponentName("目标app包名", "目标app类名");
Intent sendIntent = new Intent();
sendIntent.setComponent(comp);
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.setType("image/*");
sendIntent.putExtra(Intent.EXTRA_STREAM, "图片的uri");
startActivity(sendIntent);
}
action是一个字符串,系统定义了一些action,如拨打电话等等,我们也可以自定义action。action匹配规则是一个intent filter中可以有多个action,那么只要intent中的action能和intent filter中的任何一个action相同集合匹配成功。需要注意的是,intent中没有指定action,那么匹配失败。除此之外,action区分大小写。
2、category匹配规则
category:指定当前动作(action)被执行的环境。显示调用中的所有categroy都要满足隐式里面的定义。可以不设置,默认使用ACTION_DEFAULT。
CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。
CATEGORY_HOME:设置该组件为HomeActivity。
CATEGORY_PREFERENCE:设置该组件为Preference。
CATEGORY_LAUNCHER:设置该组件为在当前应用程序启动器中优先级最高的Activity,通常为入口ACTION_MAIN配合使用。
CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。
CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。
category是一个字符串,系统定义了一些category,我们也可以自定义category,category的匹配规则和action不同,intent中如果出现了category,不管有几个,都必须是intent filter中已经定义的category,也就是说intent可以没有category,一旦有,每个都必须和intent filter中定义的任意一个category相同。那为什么我们没在intent filter中加android.intent.category.DEFAULT这个category会报错呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认在intent中加上android.intent.category.DEFAULT这个category。
3、data匹配规则
data的匹配规则和action类似,如果intent filter中定义了data,那么intent中也必须要定义可匹配的data,否则匹配失败。 首先来了解一下data的结构,data由两部分组成,mimeType和URI。
mimeType
mimeType指媒体类型,比如image/jpeg、text/plain、video/*等,可以表示图片、文本、视频等不同的媒体格式。
URI
URI包括scheme、host、port 和path四个部分,host和port合起来也成authority(host:port)部分
<scheme>://<host>:<port>/<path>
1.Scheme: URI 的模式,比如 http,file,content,如果URI没有指定Scheme,那么整个URI的其他参数无效,也就意味着URI无效; 2.Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI的其他参数无效,也就意味着URI无效; 3.Port:URI的端口号,比如80,仅当URI中制定了scheme和host参数的时候port参数才有意义。 4.Path:路径。
例如:
content://192.168.10.1:8080/fold/etc
在这个URI中,scheme是content,host是192.168.10.1,port是8080,path是folder/etc。我们平时使用的网络url就是这种格式。
在URI中,每个组成部分都是可选的,但是有线性的依赖关系
当进行URI匹配时候,并不是比较全部,而是局部对比,以下是URI匹配规则。
1.如果一个URI仅声明了scheme部分,那么所有拥有与其相同的scheme的URI都会通过匹配,其他部分不做匹配
2.如果一个URI声明了scheme部分和authority部分,那么拥有与其相同scheme和authority的URI才能匹配成功,path部分不做匹配
3.如果一个URI所有的部分都声明了,那么只有所有部分都相同的URI才能匹配成功
data过滤规则
了解了data的结构,接下来开始介绍data的过滤规则,下面分情况说明
- 情况一:data规则不完整。如下所示
<intent-filter>
<data android:mimeType="image/*"/>
</intent-filter>
这种规则指定了媒体类型为所有类型的图片,那么intent中的mimeType属性必须为“image/*”才能匹配成功,这种情况虽然没有指定URI,但intent中的URL部分的schema默认值为content和file。也就是说虽然没有指定URI,但是intent中的URI部分的schema必须为content或者file才能匹配成功,这点尤其需要注意。
- 情况二:定义了多组data规则,并且每个data都定义了完整属性,既有URI又有mimeType。如下所示
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
</intent-filter>
为了匹配这种intent filter,我们也需要在intent中完整定义其中一组data规则才能匹配成功。另外,如果要为intent指定完整的data,必须调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值。
最后,但我们通过隐式方式启动一个Activity时,可以做一下判断,看是否有Activity能够匹配我们的intent,以免不必要的奔溃。判断方法有两种:一是采用PackageManager的resolveActivity方法,二是采用Intent的resolveActivity方法,如果他们找不到匹配的Activity就会返回null,根据其返回值我们就规避上述问题了。
首先我们来看看Intent的resolveActivity方法源码
public ComponentName resolveActivity(PackageManager pm) {
if (mComponent != null) {
return mComponent;
}
ResolveInfo info = pm.resolveActivity(
this, PackageManager.MATCH_DEFAULT_ONLY);
if (info != null) {
return new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
}
return null;
}
从中我们可以看出其内部也是直接调用了PackageManager的resolveActivity方法来判断匹配是否成功。 我们再来看看PackageManager的resolveActivity方法
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
第一个参数很好理解,就是我们要解析的intent,第二个参数源码中使用的是MATCH_DEFAULT_ONLY这个标记位,这个标记位的含义是仅仅匹配那些在intent filter中声明了这个category的Activity。使用它的意义在于只要此方法不返回null,那么startActivity一定可以成功。
最后是示例:
Intent intent = new Intent();
intent.setAction("com.dev.test");
intent.setType("image/*");
ComponentName componentName = intent.resolveActivity(getPackageManager());
if (componentName == null) {
//如果为null,表示匹配失败
Toast.makeText(MainActivity.this, "匹配失败", Toast.LENGTH_SHORT).show();
} else {
//如果不为null,启动Activity
startActivity(intent);
}