IntentFilter 匹配算法
IntentFilter.match() 返回的不是简单 boolean。成功值同时携带匹配层级和质量调整,失败值指出 action、type、data、category 或 extras 哪一层短路。理解这些返回值,才能解释多个过滤器为何排序不同,也能从解析日志直接定位失败维度。
1. 总体顺序
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag,
boolean supportWildcards,
@Nullable Collection<String> ignoreActions,
@Nullable Bundle extras) {
if (action != null
&& !matchAction(action, supportWildcards, ignoreActions)) {
return NO_MATCH_ACTION;
}
int dataMatch = matchData(type, scheme, data, supportWildcards);
if (dataMatch < 0) {
return dataMatch;
}
String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {
return NO_MATCH_CATEGORY;
}
String extraMismatch = matchExtras(extras);
if (extraMismatch != null) {
return NO_MATCH_EXTRAS;
}
return dataMatch;
}执行顺序固定为 action → data → categories → extras。任何一步失败立即返回,后续维度不再检查。因此日志显示 NO_MATCH_ACTION 时,没有必要先排查 URI path。
2. 结果编码
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
public static final int MATCH_CATEGORY_MASK = 0xfff0000;
public static final int MATCH_ADJUSTMENT_MASK = 0x000ffff;
public static final int MATCH_ADJUSTMENT_NORMAL = 0x8000;
public static final int MATCH_CATEGORY_EMPTY = 0x0100000;
public static final int MATCH_CATEGORY_SCHEME = 0x0200000;
public static final int MATCH_CATEGORY_HOST = 0x0300000;
public static final int MATCH_CATEGORY_PORT = 0x0400000;
public static final int MATCH_CATEGORY_PATH = 0x0500000;
public static final int MATCH_CATEGORY_SCHEME_SPECIFIC_PART = 0x0580000;
public static final int MATCH_CATEGORY_TYPE = 0x0600000;
public static final int NO_MATCH_TYPE = -1;
public static final int NO_MATCH_DATA = -2;
public static final int NO_MATCH_ACTION = -3;
public static final int NO_MATCH_CATEGORY = -4;
public static final int NO_MATCH_EXTRAS = -5;高位 category 表示匹配深入到 EMPTY、SCHEME、HOST、PORT、PATH、SSP 或 TYPE;低位 adjustment 表示质量修正。成功时 matchData 通常返回 category + 0x8000。数值更大意味着 data 匹配更具体,但最终 ResolveInfo 排序还会考虑 priority、preferredOrder、default 和 system 等字段。
3. Action 匹配
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
private boolean matchAction(String action, boolean wildcardSupported,
@Nullable Collection<String> ignoreActions) {
if (wildcardSupported && WILDCARD.equals(action)) {
if (ignoreActions == null) {
return !mActions.isEmpty();
}
if (mActions.size() > ignoreActions.size()) {
return true;
}
for (int i = mActions.size() - 1; i >= 0; i--) {
if (!ignoreActions.contains(mActions.valueAt(i))) {
return true;
}
}
return false;
}
if (ignoreActions != null && ignoreActions.contains(action)) {
return false;
}
return hasAction(action);
}普通应用解析走精确 hasAction。通配符与 ignoreActions 是内部查询能力:通配 * 表示 filter 至少存在一个未被忽略的 action,不表示 manifest 可以用 * 声明任意 action。
一个容易误读的边界是外层 match() 只在 action != null 时调用 matchAction。Intent action 为 null 时不会在此处返回 NO_MATCH_ACTION;上层 SaferIntentUtils.blockNullAction 可能在解析结果阶段继续阻止它。
4. Data 空集规则
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
final boolean wildcardWithMimegroups =
wildcardSupported && countMimeGroups() != 0;
final List<String> types = mDataTypes;
final ArrayList<String> schemes = mDataSchemes;
int match = MATCH_CATEGORY_EMPTY;
if (!wildcardWithMimegroups && types == null && schemes == null) {
return ((type == null && data == null)
? MATCH_CATEGORY_EMPTY + MATCH_ADJUSTMENT_NORMAL
: NO_MATCH_DATA);
}filter 同时没有 MIME type 和 scheme 时,只匹配既没有 type 也没有 data URI 的 Intent。即使 filter 错误地保存了 authority/path,没有基础 scheme/type 时也不会靠它们匹配成功。
5. Scheme 与 URI
if (schemes != null) {
if (schemes.contains(scheme != null ? scheme : "")
|| wildcardSupported && WILDCARD.equals(scheme)) {
match = MATCH_CATEGORY_SCHEME;
} else {
return NO_MATCH_DATA;
}
final ArrayList<PatternMatcher> ssps = mDataSchemeSpecificParts;
if (ssps != null && data != null) {
match = hasDataSchemeSpecificPart(
data.getSchemeSpecificPart(), wildcardSupported)
? MATCH_CATEGORY_SCHEME_SPECIFIC_PART
: NO_MATCH_DATA;
}先匹配 scheme,再尝试 scheme-specific-part。SSP 适合 tel:、mailto: 等非层级 URI;一旦 SSP 成功,就不再走 authority/path。SSP 声明存在但不匹配时,代码仍会尝试 authority 分支,而不是立即结束。
if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {
final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
if (authorities != null) {
int authMatch = matchDataAuthority(data, wildcardSupported);
if (authMatch >= 0) {
final ArrayList<PatternMatcher> paths = mDataPaths;
final ArrayList<UriRelativeFilterGroup> groups =
mUriRelativeFilterGroups;
if (Flags.relativeReferenceIntentFilters()) {
if (paths == null && groups == null) {
match = authMatch;
} else if (hasDataPath(data.getPath(), wildcardSupported)
|| matchRelRefGroups(data)) {
match = MATCH_CATEGORY_PATH;
} else {
return NO_MATCH_DATA;
}
} else if (paths == null) {
match = authMatch;
} else if (hasDataPath(data.getPath(), wildcardSupported)) {
match = MATCH_CATEGORY_PATH;
} else {
return NO_MATCH_DATA;
}
} else {
return NO_MATCH_DATA;
}
}
}authority 必须在 scheme 成功之后检查;path 又必须在 authority 成功后检查。Android 17 还受 relativeReferenceIntentFilters flag 控制,可让 URI relative filter group 与传统 path 任一匹配成功。
6. 无 Scheme 规则
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
} else {
if (scheme != null && !"".equals(scheme)
&& !"content".equals(scheme)
&& !"file".equals(scheme)
&& !(wildcardSupported && WILDCARD.equals(scheme))) {
return NO_MATCH_DATA;
}
}filter 没声明 scheme 时,仍隐式接受空 scheme、content: 和 file:,方便仅按 MIME type 匹配 ContentProvider 数据。https: 不在这个例外中,必须由 filter 显式声明。
7. MIME Type 匹配
if (wildcardWithMimegroups) {
return MATCH_CATEGORY_TYPE;
} else if (types != null) {
if (findMimeType(type)) {
match = MATCH_CATEGORY_TYPE;
} else {
return NO_MATCH_TYPE;
}
} else if (type != null) {
return NO_MATCH_TYPE;
}
return match + MATCH_ADJUSTMENT_NORMAL;filter 有 types 时必须由 findMimeType 命中;filter 没有 type 而 Intent 有 type,同样失败。type 命中会把最终 category 提升到 TYPE,即使前面 authority/path 也成功,因此返回值反映最高 data 匹配层级。
| Filter | Intent type | 结果 |
|---|---|---|
image/jpeg | image/jpeg | TYPE |
image/* | image/png | TYPE |
*/* | text/plain | TYPE |
| 无 type | text/plain | NO_MATCH_TYPE |
image/* | null | NO_MATCH_TYPE |
8. Authority 匹配
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
public int match(Uri data, boolean wildcardSupported) {
String host = data.getHost();
if (host == null) {
if (wildcardSupported && mWild && mHost.isEmpty()) {
return MATCH_CATEGORY_HOST;
}
return NO_MATCH_DATA;
}
if (!wildcardSupported || !WILDCARD.equals(host)) {
if (mWild) {
if (host.length() < mHost.length()) return NO_MATCH_DATA;
host = host.substring(host.length() - mHost.length());
}
if (host.compareToIgnoreCase(mHost) != 0) {
return NO_MATCH_DATA;
}
}
if (!wildcardSupported && mPort >= 0) {
if (mPort != data.getPort()) return NO_MATCH_DATA;
return MATCH_CATEGORY_PORT;
}
return MATCH_CATEGORY_HOST;
}通配 host 使用后缀匹配;声明端口时必须精确相等,成功级别提升为 PORT。源码注释要求 URI scheme、host 和 MIME type 统一使用小写,外部输入应先规范化,不能依赖 RFC 意义上的大小写宽容。
9. Category 包含
源码文件:frameworks/base/core/java/android/content/IntentFilter.java
public final String matchCategories(Set<String> categories) {
if (categories == null) return null;
Iterator<String> it = categories.iterator();
if (mCategories == null) {
return it.hasNext() ? it.next() : null;
}
while (it.hasNext()) {
final String category = it.next();
if (!mCategories.contains(category)) {
return category;
}
}
return null;
}规则是 Intent 中每个 category 都必须出现在 filter 中;filter 可以声明额外 category。没有 categories 的 Intent 可以匹配声明了 categories 的 filter。CATEGORY_DEFAULT 是否必须存在不是此函数决定,而是 IntentResolver.buildResolveList(defaultOnly) 的后置条件。
10. 可验证示例
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="example.com"
android:pathPrefix="/docs"
android:mimeType="text/html" />
</intent-filter>VIEW https://example.com/docs/intro 且 type 为 text/html、categories 为 DEFAULT+BROWSABLE 时返回 TYPE 级成功。host 改成 other.com 返回 NO_MATCH_DATA;type 改成 image/png 返回 NO_MATCH_TYPE;Intent 添加 filter 未声明的 category 返回 NO_MATCH_CATEGORY。
11. 源码阅读路线
IntentFilter.match:掌握短路顺序和失败码。matchAction:理解通配与 ignoreActions 只服务内部查询。matchData:按 empty、scheme、SSP、authority、path、type 顺序阅读。AuthorityEntry.match:确认 host 通配和 port 匹配。matchCategories:确认 Intent categories 是待验证集合。IntentResolver.buildResolveList:观察 match 分数如何进入 ResolveInfo,以及 DEFAULT category 的额外过滤。
IntentFilter 匹配不是各字段独立相等,而是有前置依赖的层级算法:authority 依赖 scheme,path 依赖 authority,MIME type 最后提升匹配类别,category 再做反向包含检查。沿着返回码读取,才能快速定位隐式 Intent 为什么没有候选。
