Skip to content

IntentFilter 解析

追踪 intent-filter 的属性、action、category、data 和 URI 相对规则如何形成可匹配对象,并进入组件解析器。

基于android-17.0.0_r1
AndroidPackageManagerServiceAndroidManifestIntentFilterComponentResolver源码阅读

IntentFilter 解析 ​

本文承接 Manifest 解析入口、四大组件解析 和 uses-permission 解析。前两篇已经说明了 XML 如何进入 ParsingPackage 以及组件对象如何创建;这里只追踪一个 <intent-filter> 从 XML 子树到可查询索引的完整生命周期。

本文使用 Android 17.0.0_r1 源码。重点不是复述 Intent 的概念,而是回答几个读源码时必须弄清的问题:谁创建 IntentFilter,每个属性写入哪个字段,什么错误会立即失败,什么错误会延迟,四类组件为什么传入不同的解析开关,以及解析结果什么时候才真正影响 Intent 查询。

1. Filter 的边界 ​

1.1 Filter 所有者 ​

Manifest 中的 <intent-filter> 永远属于一个组件。解析器并不把它直接登记到全局表,而是先创建 ParsedIntentInfoImpl,再由当前组件把对象追加到自己的 intent 列表:

这里有两个容易混淆的边界:ParsedIntentInfoImpl 是解析阶段的包装对象,真正执行 action/category/data 匹配的是它内部的 IntentFilter;另外,组件列表已经包含 filter,也不代表全局 ComponentResolver 已经可以查询到它,后者发生在扫描阶段的注册操作。

1.2 解析结果的形状 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java

java
public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable {

    // CATEGORY_DEFAULT 是否存在,由 parseIntentInfo 在子标签遍历结束后计算。
    private boolean mHasDefault;

    // <intent-filter android:label="..."> 的资源标签。
    private int mLabelRes;

    // label 不是资源引用时保存的直接文本。
    @Nullable
    private CharSequence mNonLocalizedLabel;

    // roundIcon 优先,否则使用 icon。
    private int mIcon;

    // action/category/data/priority/order 等匹配状态的真正载体。
    @NonNull
    private IntentFilter mIntentFilter = new IntentFilter();
}

mHasDefault 是一个派生缓存,不是另一份 category 列表;它的来源仍然是 mIntentFilter.hasCategory(Intent.CATEGORY_DEFAULT)。mLabelRes、mNonLocalizedLabel 和 mIcon 只描述 filter 的展示信息,查询算法依赖的是 mIntentFilter。

2. 组件调用 ​

2.1 公共入口 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java

java
static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(
        ParsedMainComponent mainComponent,
        ParsingPackage pkg, Resources resources, XmlResourceParser parser,
        boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify,
        boolean allowImplicitEphemeralVisibility, boolean failOnNoActions,
        ParseInput input) throws IOException, XmlPullParserException {
    ParseResult<ParsedIntentInfoImpl> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
            mainComponent.getName(), pkg, resources, parser, allowGlobs,
            allowAutoVerify, input);
    if (intentResult.isError()) {
        return input.error(intentResult);
    }

    ParsedIntentInfo intent = intentResult.getResult();
    IntentFilter intentFilter = intent.getIntentFilter();
    int actionCount = intentFilter.countActions();
    if (actionCount == 0 && failOnNoActions) {
        Slog.w(TAG, "No actions in " + parser.getName() + " at " + pkg.getBaseApkPath() + " "
                + parser.getPositionDescription());
        // Backward-compat, do not actually fail
        return input.success(null);
    }

    int intentVisibility;
    if (visibleToEphemeral) {
        intentVisibility = IntentFilter.VISIBILITY_EXPLICIT;
    } else if (allowImplicitEphemeralVisibility
            && ComponentParseUtils.isImplicitlyExposedIntent(intent)) {
        intentVisibility = IntentFilter.VISIBILITY_IMPLICIT;
    } else {
        intentVisibility = IntentFilter.VISIBILITY_NONE;
    }
    intentFilter.setVisibilityToInstantApp(intentVisibility);
    return input.success(intentResult.getResult());
}

这段代码决定了“解析成功”的边界:底层解析出错会向上传递;Activity/Receiver 传入 failOnNoActions=true 时,无 action 的 filter 会打印警告并返回 success(null),调用方因此不会把它加入组件列表。注释明确说明这是兼容行为,不能把 failOnNoActions 误读成当前版本一定抛错。

2.2 四类组件的开关 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsedActivityUtils.java、ParsedServiceUtils.java、ParsedProviderUtils.java

java
// Activity 和 Receiver 共用 ParsedActivityUtils;allowImplicitEphemeralVisibility
// 由调用方区分普通 intent-filter 与 <preferred>。
ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(
        activity, pkg, resources, parser, visibleToEphemeral,
        true /*allowGlobs*/, true /*allowAutoVerify*/,
        allowImplicitEphemeralVisibility,
        true /*failOnNoActions*/, input);

下面是 Service 的调用点,参数由其组件语义决定。

java
// Service:允许 glob,但不读取 autoVerify,也不启用隐式 instant-app 可见性;
// 无 action 不走 Activity/Receiver 的兼容分支。
ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils.parseIntentFilter(
        service, pkg, res, parser, visibleToEphemeral,
        true /*allowGlobs*/, false /*allowAutoVerify*/,
        false /*allowImplicitEphemeralVisibility*/,
        false /*failOnNoActions*/, input);

Provider 沿用同一组开关,区别只在于结果追加到 ParsedProvider。

java
// Provider 使用与 Service 相同的 filter 开关。
ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils.parseIntentFilter(
        provider, pkg, res, parser, visibleToEphemeral,
        true /*allowGlobs*/, false /*allowAutoVerify*/,
        false /*allowImplicitEphemeralVisibility*/,
        false /*failOnNoActions*/, input);

因此“IntentFilter 对所有组件完全相同”是不准确的。差异来自调用方:Activity/Receiver 可以解析 autoVerify,并对无 action 做兼容性丢弃;Service/Provider 不读取该属性,也不启用这两个 Activity 专属分支。allowGlobs 在当前四类组件调用中都为 true,但公共入口仍保留该参数,后续组件或策略可以限制 glob。

3. 属性与子标签 ​

3.1 filter 属性 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java

java
ParsedIntentInfoImpl intentInfo = new ParsedIntentInfoImpl();
IntentFilter intentFilter = intentInfo.getIntentFilter();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter);
try {
    intentFilter.setPriority(
            sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
    intentFilter.setOrder(
            sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));

    TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label);
    if (v != null) {
        intentInfo.setLabelRes(v.resourceId);
        if (v.resourceId == 0) {
            intentInfo.setNonLocalizedLabel(v.coerceToString());
        }
    }

    if (ParsingPackageUtils.sUseRoundIcon) {
        intentInfo.setIcon(sa.getResourceId(
                R.styleable.AndroidManifestIntentFilter_roundIcon, 0));
    }
    if (intentInfo.getIcon() == 0) {
        intentInfo.setIcon(sa.getResourceId(
                R.styleable.AndroidManifestIntentFilter_icon, 0));
    }

    if (allowAutoVerify) {
        intentFilter.setAutoVerify(sa.getBoolean(
                R.styleable.AndroidManifestIntentFilter_autoVerify, false));
    }
} finally {
    sa.recycle();
}

默认值也属于语义:priority/order 默认都是 0;roundIcon 只有系统开关允许时才尝试读取,读不到才回退到 icon;autoVerify 只有调用方传入 allowAutoVerify=true 才写入 IntentFilter。所以 Service/Provider 即使在 XML 中写了 autoVerify,也不会通过这段公共代码设置自动验证状态。

3.2 子标签路由 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java

java
final ParseResult result;
String nodeName = parser.getName();
switch (nodeName) {
    case "action": {
        String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
        if (value == null) {
            result = input.error("No value supplied for <android:name>");
        } else if (value.isEmpty()) {
            intentFilter.addAction(value);
            result = input.deferError("No value supplied for <android:name>",
                    ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
        } else {
            intentFilter.addAction(value);
            result = input.success(null);
        }
        break;
    }
    case "category": {
        String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
        if (value == null) {
            result = input.error("No value supplied for <android:name>");
        } else if (value.isEmpty()) {
            intentFilter.addCategory(value);
            result = input.deferError("No value supplied for <android:name>",
                    ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
        } else {
            intentFilter.addCategory(value);
            result = input.success(null);
        }
        break;
    }
    case "data":
        result = parseData(intentInfo, res, parser, allowGlobs, input);
        break;
    case "uri-relative-filter-group":
        if (Flags.relativeReferenceIntentFilters()) {
            result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
            break;
        }
    default:
        result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
        break;
}

if (result.isError()) {
    return input.error(result);
}

关键控制流是:AConfig 的 skipCurrentElement 先跳过被配置关闭的元素;未识别标签进入 unknownTag;任一分支返回 error,整个 filter 立即失败。

3.3 计算 DEFAULT ​

java
intentInfo.setHasDefault(intentFilter.hasCategory(Intent.CATEGORY_DEFAULT));
return input.success(intentInfo);

mHasDefault 只在所有子标签处理完成后计算,因此它反映的是最终 category 集合,而不是遍历过程中的中间值。IntentFilter 本身仍保留 category,后续匹配和调试输出都以它为准。

4. Action/Category ​

4.1 三种值路径 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java

java
case "action": {
    String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
    if (value == null) {
        result = input.error("No value supplied for <android:name>");
    } else if (value.isEmpty()) {
        intentFilter.addAction(value);
        // Prior to R, this was not a failure
        result = input.deferError("No value supplied for <android:name>",
                ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
    } else {
        intentFilter.addAction(value);
        result = input.success(null);
    }
    break;
}

case "category": {
    String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
    if (value == null) {
        result = input.error("No value supplied for <android:name>");
    } else if (value.isEmpty()) {
        intentFilter.addCategory(value);
        // Prior to R, this was not a failure
        result = input.deferError("No value supplied for <android:name>",
                ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
    } else {
        intentFilter.addCategory(value);
        result = input.success(null);
    }
    break;
}

三条路径必须分开理解:没有 android:name 是立即错误;属性存在但为空时,空字符串仍先写入 IntentFilter,同时登记 deferred error;非空字符串立即加入集合并继续。延迟错误机制服务于旧版本兼容,不能据此认为空 action/category 永远合法。

4.2 数据归属 ​

ParsedIntentInfoImpl 没有单独的 actions 或 categories 字段。解析器直接调用 IntentFilter.addAction/addCategory,这样后续代码无需把一份中间模型再转换一次:组件列表中的 ParsedIntentInfo、ComponentResolver 中的 pair、IntentResolver 的索引最终都读取同一个 IntentFilter。

5. data 规则 ​

5.1 MIME 与主机 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java

java
TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData);
try {
    String str = sa.getNonConfigurationString(
            R.styleable.AndroidManifestData_mimeType, 0);
    if (str != null) {
        try {
            intentFilter.addDataType(str);
        } catch (IntentFilter.MalformedMimeTypeException e) {
            return input.error(e.toString());
        }
    }

    str = sa.getNonConfigurationString(
            R.styleable.AndroidManifestData_mimeGroup, 0);
    if (str != null) {
        intentFilter.addMimeGroup(str);
    }

    str = sa.getNonConfigurationString(
            R.styleable.AndroidManifestData_scheme, 0);
    if (str != null) {
        intentFilter.addDataScheme(str);
    }

    String host = sa.getNonConfigurationString(
            R.styleable.AndroidManifestData_host, 0);
    String port = sa.getNonConfigurationString(
            R.styleable.AndroidManifestData_port, 0);
    if (host != null) {
        intentFilter.addDataAuthority(host, port);
    }
} finally {
    sa.recycle();
}

这里的异常边界很具体:MIME 类型由 IntentFilter.addDataType 校验,格式错误被转换成 ParseResult error;mime group、scheme、authority 则写入对应集合。port 只有在 host 存在时才交给 addDataAuthority,因此不能把 <data android:port="443"/> 理解成一个完整 authority 规则。

5.2 SSP 模式 ​

java
str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_ssp, 0);
if (str != null) {
    intentFilter.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_LITERAL);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_sspPrefix, 0);
if (str != null) {
    intentFilter.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_PREFIX);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_sspPattern, 0);
if (str != null) {
    if (!allowGlobs) {
        return input.error("sspPattern not allowed here; ssp must be literal");
    }
    intentFilter.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_sspAdvancedPattern, 0);
if (str != null) {
    if (!allowGlobs) {
        return input.error("sspAdvancedPattern not allowed here; ssp must be literal");
    }
    intentFilter.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_sspSuffix, 0);
if (str != null) {
    intentFilter.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SUFFIX);
}

literal、prefix、simple glob、advanced glob、suffix 分别对应不同的 PatternMatcher 类型。只有带 Pattern 的 glob 形式受 allowGlobs 约束;suffix 本身不是 glob,因此不会被这个条件拒绝。

5.3 path 规则 ​

java
str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_path, 0);
if (str != null) {
    intentFilter.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_pathPrefix, 0);
if (str != null) {
    intentFilter.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_pathPattern, 0);
if (str != null) {
    if (!allowGlobs) {
        return input.error("pathPattern not allowed here; path must be literal");
    }
    intentFilter.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
if (str != null) {
    if (!allowGlobs) {
        return input.error("pathAdvancedPattern not allowed here; path must be literal");
    }
    intentFilter.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
}

str = sa.getNonConfigurationString(R.styleable.AndroidManifestData_pathSuffix, 0);
if (str != null) {
    intentFilter.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
}

Path 与 ssp 的映射完全一致,但 owner 都是同一个 IntentFilter。源码没有在解析阶段计算“最终是否匹配”,只是把字符串和模式类型保存下来;实际匹配由 IntentFilter.matchData 在查询时完成。

5.4 相对 URI 组 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java

java
if (Flags.relativeReferenceIntentFilters()) {
    result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
}

group 创建后,解析器再读取 allow 属性决定 allow/block 动作。

java
int action = UriRelativeFilterGroup.ACTION_ALLOW;
if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) {
    action = UriRelativeFilterGroup.ACTION_BLOCK;
}
group = new UriRelativeFilterGroup(action);

group 只处理其内部的 <data>;每个 path、fragment、query 及其 prefix/pattern/advancedPattern/suffix 都转换为 UriRelativeFilter。glob 受同一个 allowGlobs 检查。解析结束后,只有 group 中至少有一个 URI relative filter 才调用 intentFilter.addUriRelativeFilterGroup(group);空 group 不会污染 filter。

6. 派生状态 ​

6.1 Instant 可见性 ​

visibleToEphemeral、allowImplicitEphemeralVisibility 不是 XML 属性的简单复制,而是组件策略传给公共入口的上下文:

Activity/Receiver 的组件解析器还会把 filter 的结果同步到 Activity flags:

java
if (intent != null) {
    final IntentFilter intentFilter = intent.getIntentFilter();
    if (intentFilter.isVisibleToInstantApp()) {
        activity.setFlags(activity.getFlags()
                | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
    }
    if (intentFilter.isImplicitlyVisibleToInstantApp()) {
        activity.setFlags(activity.getFlags()
                | ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
    }
}

这说明 visibility 有两个 owner:即时规则保存在 IntentFilter,Activity 对外的组件标志由 ParsedActivityUtils 派生。Service/Provider 不执行这段 Activity flag 同步。

6.2 component.order ​

四类组件在成功取得 ParsedIntentInfoImpl 后,都会读取 filter order,并保留组件当前 order 与 filter order 的较大值:

java
ParsedIntentInfoImpl intent = intentResult.getResult();
IntentFilter intentFilter = intent.getIntentFilter();
service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
service.addIntent(intent);

Activity/Receiver、Service、Provider 都采用同样的 max 逻辑。order 因而是组件级聚合值,而不是某一个 filter 的别名;多个 filter 中最大的 order 会影响组件后续排序。

7. 存储与注册 ​

7.1 追加到组件列表 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsedActivityUtils.java

组件侧只有在 intentResult.isSuccess() 且结果非 null 时才追加:

java
if (intentResult.isSuccess()) {
    ParsedIntentInfoImpl intentInfo = intentResult.getResult();
    if (intentInfo != null) {
        IntentFilter intentFilter = intentInfo.getIntentFilter();
        activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
        activity.addIntent(intentInfo);
    }
}

因此三种情况不同:解析 error 会终止组件解析;无 action 的 Activity/Receiver filter 可能返回 success(null) 并被丢弃;普通成功结果才进入 component.getIntents()。

7.2 Resolver 注册 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/resolution/ComponentResolver.java

java
public void addAllComponents(AndroidPackage pkg, boolean chatty,
        @Nullable String setupWizardPackage, @NonNull Computer computer) {
    final ArrayList<Pair<ParsedActivity, ParsedIntentInfo>> newIntents = new ArrayList<>();
    synchronized (mLock) {
        addActivitiesLocked(computer, pkg, newIntents, chatty);
        addReceiversLocked(computer, pkg, chatty);
        addProvidersLocked(computer, pkg, chatty);
        addServicesLocked(computer, pkg, chatty);
        onChanged();
    }
    // Activity priority 的后处理在锁外完成。
    for (int i = newIntents.size() - 1; i >= 0; --i) {
        final Pair<ParsedActivity, ParsedIntentInfo> pair = newIntents.get(i);
        final PackageStateInternal disabledPkgSetting = computer
                .getDisabledSystemPackage(pair.first.getPackageName());
        final AndroidPackage disabledPkg = disabledPkgSetting == null
                ? null : disabledPkgSetting.getPkg();
        final List<ParsedActivity> systemActivities =
                disabledPkg != null ? disabledPkg.getActivities() : null;
        adjustPriority(computer, systemActivities, pair.first, pair.second, setupWizardPackage);
    }
}

addAllComponents 只是批量调度;真正逐个把 filter 放进索引的是下面的 addActivity。

java
protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
        List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
    mActivities.put(a.getComponentName(), a);
    final int intentsSize = a.getIntents().size();
    for (int j = 0; j < intentsSize; j++) {
        ParsedIntentInfo intent = a.getIntents().get(j);
        IntentFilter intentFilter = intent.getIntentFilter();
        if (newIntents != null && "activity".equals(type)) {
            newIntents.add(Pair.create(a, intent));
        }
        if (!intentFilter.debugCheck()) {
            Log.w(TAG, "==> For Activity " + a.getName());
        }
        addFilter(computer, Pair.create(a, intent));
    }
}

注册阶段把 (组件, ParsedIntentInfo) pair 交给继承自 IntentResolver 的 activity/receiver/service/provider resolver。addFilter 才把 IntentFilter 放入 action、MIME、scheme 等查询索引;removeAllComponents 会对每个组件调用 removeFilter,卸载或替换时清理旧索引。

8. 消费者与生效时机 ​

8.1 解析期消费者 ​

ParsingPackageUtils 会遍历 Activity filter,判断包是否包含 HTTP/HTTPS 的 domain URL;InstallPackageHelper.hasLauncherEntry 会检查启用且导出的 Activity 是否有 CATEGORY_LAUNCHER。这些消费者直接读取 component.getIntents().get(i).getIntentFilter(),不经过全局 resolver。

java
for (ParsedActivity activity : pkg.getActivities()) {
    for (ParsedIntentInfo info : activity.getIntents()) {
        IntentFilter filter = info.getIntentFilter();
        if (!filter.hasAction(Intent.ACTION_VIEW)) continue;
        if (!filter.hasAction(Intent.ACTION_DEFAULT)) continue;
        if (filter.hasDataScheme(IntentFilter.SCHEME_HTTP)
                || filter.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
            return true;
        }
    }
}

8.2 查询阶段 ​

IntentFilter.matchData 的匹配输入包括 MIME type、scheme 和 URI;matchCategories 检查调用方 Intent 的 category 是否被 filter 覆盖。Android 17 的 IntentFilter 还会在满足条件时检查 URI relative filter group。解析代码只负责建立这些集合和模式对象,排序、用户状态过滤、可见性过滤和最终 ResolveInfo 构造属于 ComponentResolver/IntentResolver 查询路径。

9. 错误与清理 ​

9.1 失败路径 ​

可以按错误发生位置定位行为:

位置例子结果
action/category 读取缺少 android:name当前 filter 立即 error
action/category 兼容校验空字符串写入 filter,同时 deferred error
data 类型转换非法 MIME typeMalformedMimeTypeException 转 error
glob 策略allowGlobs=false 仍写 pattern返回 error,不加入组件
子标签分发未知标签或关闭的 relative groupunknownTag 或按配置跳过
Activity/Receiver 无 actionfailOnNoActions=truewarning,返回 success(null)

9.2 清理路径 ​

解析对象本身没有全局注册表,也没有在 parseData 失败时做“半成功回滚”;因为调用方看到 error 后不会把组件提交到最终包。已经注册的包在卸载或更新时,由 ComponentResolver.removeAllComponents 遍历同一份组件 intent 列表并调用 removeFilter,同时清理 MIME group 映射和 provider authority 索引。理解这一点,可以避免把“解析失败”和“已注册 filter 清理”混成同一阶段。

10. 测试与诊断 ​

10.1 输入与断言 ​

阅读或补测试时,至少覆盖下面几类输入:

输入关键断言证明范围
一个 action、DEFAULT、scheme/host/pathPrefixgetIntentFilter() 的 action/category/data 集合完整基础字段写入
缺少 action android:name解析结果为 error,组件不提交立即失败路径
空 actionfilter 曾加入空 action,随后按 deferred error 规则处理兼容分支
非法 MIME type返回包含 MalformedMimeTypeException 文本的 error类型校验
pathPattern 且 allowGlobs=falseerror 文本为 “pathPattern not allowed here”策略开关
Activity filter 无 action返回 success(null),组件 intent 列表不增加failOnNoActions 兼容行为
autoVerify 放在不同组件Activity/Receiver 可设置,Service/Provider 不设置调用方参数差异
relative group 为空不增加 UriRelativeFilterGroup空 group 过滤
同一包注册后移除resolver 查询不到旧 filter注册/清理闭环

测试不应只断言“解析成功”。要同时检查 ParseResult、组件 intent 列表、IntentFilter 内部集合,以及注册后查询结果;否则无法区分“成功但被丢弃”“成功并保存”“成功但尚未注册”这三种状态。

10.2 现场排查路径 ​

遇到隐式 Intent 找不到组件时,可以按以下源码顺序排查:

  1. 检查 Manifest 中 action/category/data 是否进入 ParsedIntentInfoUtils 对应分支。
  2. 查看是否因缺失 name、非法 MIME、glob 限制或 unknown tag 返回 error。
  3. 对 Activity/Receiver 确认 failOnNoActions 是否导致 success(null)。
  4. 检查组件的 getIntents() 是否包含该对象,以及 order 和 instant-app flags 是否被派生。
  5. 再看 ComponentResolver.addAllComponents 是否执行 addFilter,最后才分析查询时的用户、可见性和排序过滤。

11. 源码路线 ​

建议沿以下顺序阅读 Android 17 源码:

  1. ParsedActivityUtils.parseActivityOrReceiver:看 Activity/Receiver 如何传入开关并接收结果。
  2. ParsedServiceUtils.parseService 与 ParsedProviderUtils.parseProvider:对比调用参数和 order 聚合。
  3. ParsedMainComponentUtils.parseIntentFilter:理解无 action、instant-app visibility 和错误传播。
  4. ParsedIntentInfoUtils.parseIntentInfo:逐个跟踪属性、action、category、data、relative group。
  5. ParsedIntentInfoImpl:确认解析结果字段和 Parcelable 顺序。
  6. ComponentResolver.addAllComponents:确认何时从组件列表进入全局 IntentResolver。
  7. IntentFilter.matchData / matchCategories:把保存下来的模式映射回查询时的匹配规则。

12. 设计收束 ​

IntentFilter 解析的核心不是“把 XML 读成几个字符串”,而是建立一条可追踪的状态链:

text
XML 子标签
  -> IntentFilter 内部集合和 PatternMatcher
  -> ParsedIntentInfoImpl(展示字段 + hasDefault)
  -> 组件 intent 列表(同时聚合 order、派生 visibility)
  -> ComponentResolver 的 IntentResolver 索引
  -> 查询时的 action/category/data 匹配与策略过滤

只要把 owner、状态和生效时机分开,许多看似矛盾的现象就能解释:解析成功不等于 resolver 已注册;空 action 可能 success 但没有加入组件;autoVerify 是否生效取决于组件调用方;pathPattern 的可用性取决于 allowGlobs;mHasDefault 是派生值而不是第二份数据。后续阅读 Intent 匹配算法和 Domain Verification 时,可以直接沿这条链进入查询阶段。