IntentFilter的匹配规则

以下内容摘抄自作者:任玉刚所著的《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也提供了类似的方法去获取成功匹配的组件信息。

猜你喜欢

转载自blog.csdn.net/yanjianyang2021/article/details/115060834