Skip to content

IntentFilter 匹配算法

从匹配入口、Data 层级和结果编码理解 IntentFilter 的成功与失败判定。

AndroidPMSIntentFilterIntent

IntentFilter 匹配算法 ​

IntentFilter.match() 返回的不是简单 boolean。成功值同时携带匹配层级和质量调整,失败值指出 action、type、data、category 或 extras 哪一层短路。理解这些返回值,才能解释多个过滤器为何排序不同,也能从解析日志直接定位失败维度。

1. 总体顺序 ​

源码文件:frameworks/base/core/java/android/content/IntentFilter.java

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

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

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

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 ​

java
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 分支,而不是立即结束。

java
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

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 匹配 ​

java
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 匹配层级。

FilterIntent type结果
image/jpegimage/jpegTYPE
image/*image/pngTYPE
*/*text/plainTYPE
无 typetext/plainNO_MATCH_TYPE
image/*nullNO_MATCH_TYPE

8. Authority 匹配 ​

源码文件:frameworks/base/core/java/android/content/IntentFilter.java

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

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. 可验证示例 ​

xml
<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. 源码阅读路线 ​

  1. IntentFilter.match:掌握短路顺序和失败码。
  2. matchAction:理解通配与 ignoreActions 只服务内部查询。
  3. matchData:按 empty、scheme、SSP、authority、path、type 顺序阅读。
  4. AuthorityEntry.match:确认 host 通配和 port 匹配。
  5. matchCategories:确认 Intent categories 是待验证集合。
  6. IntentResolver.buildResolveList:观察 match 分数如何进入 ResolveInfo,以及 DEFAULT category 的额外过滤。

IntentFilter 匹配不是各字段独立相等,而是有前置依赖的层级算法:authority 依赖 scheme,path 依赖 authority,MIME type 最后提升匹配类别,category 再做反向包含检查。沿着返回码读取,才能快速定位隐式 Intent 为什么没有候选。