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
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
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
// 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 的调用点,参数由其组件语义决定。
// 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。
// 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
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
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
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
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
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 模式
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 规则
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
if (Flags.relativeReferenceIntentFilters()) {
result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
}group 创建后,解析器再读取 allow 属性决定 allow/block 动作。
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:
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 的较大值:
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 时才追加:
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
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。
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。
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 type | MalformedMimeTypeException 转 error |
| glob 策略 | allowGlobs=false 仍写 pattern | 返回 error,不加入组件 |
| 子标签分发 | 未知标签或关闭的 relative group | unknownTag 或按配置跳过 |
| Activity/Receiver 无 action | failOnNoActions=true | warning,返回 success(null) |
9.2 清理路径
解析对象本身没有全局注册表,也没有在 parseData 失败时做“半成功回滚”;因为调用方看到 error 后不会把组件提交到最终包。已经注册的包在卸载或更新时,由 ComponentResolver.removeAllComponents 遍历同一份组件 intent 列表并调用 removeFilter,同时清理 MIME group 映射和 provider authority 索引。理解这一点,可以避免把“解析失败”和“已注册 filter 清理”混成同一阶段。
10. 测试与诊断
10.1 输入与断言
阅读或补测试时,至少覆盖下面几类输入:
| 输入 | 关键断言 | 证明范围 |
|---|---|---|
| 一个 action、DEFAULT、scheme/host/pathPrefix | getIntentFilter() 的 action/category/data 集合完整 | 基础字段写入 |
缺少 action android:name | 解析结果为 error,组件不提交 | 立即失败路径 |
| 空 action | filter 曾加入空 action,随后按 deferred error 规则处理 | 兼容分支 |
| 非法 MIME type | 返回包含 MalformedMimeTypeException 文本的 error | 类型校验 |
pathPattern 且 allowGlobs=false | error 文本为 “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 找不到组件时,可以按以下源码顺序排查:
- 检查 Manifest 中 action/category/data 是否进入
ParsedIntentInfoUtils对应分支。 - 查看是否因缺失 name、非法 MIME、glob 限制或 unknown tag 返回 error。
- 对 Activity/Receiver 确认
failOnNoActions是否导致success(null)。 - 检查组件的
getIntents()是否包含该对象,以及order和 instant-app flags 是否被派生。 - 再看
ComponentResolver.addAllComponents是否执行addFilter,最后才分析查询时的用户、可见性和排序过滤。
11. 源码路线
建议沿以下顺序阅读 Android 17 源码:
ParsedActivityUtils.parseActivityOrReceiver:看 Activity/Receiver 如何传入开关并接收结果。ParsedServiceUtils.parseService与ParsedProviderUtils.parseProvider:对比调用参数和 order 聚合。ParsedMainComponentUtils.parseIntentFilter:理解无 action、instant-app visibility 和错误传播。ParsedIntentInfoUtils.parseIntentInfo:逐个跟踪属性、action、category、data、relative group。ParsedIntentInfoImpl:确认解析结果字段和 Parcelable 顺序。ComponentResolver.addAllComponents:确认何时从组件列表进入全局IntentResolver。IntentFilter.matchData/matchCategories:把保存下来的模式映射回查询时的匹配规则。
12. 设计收束
IntentFilter 解析的核心不是“把 XML 读成几个字符串”,而是建立一条可追踪的状态链:
XML 子标签
-> IntentFilter 内部集合和 PatternMatcher
-> ParsedIntentInfoImpl(展示字段 + hasDefault)
-> 组件 intent 列表(同时聚合 order、派生 visibility)
-> ComponentResolver 的 IntentResolver 索引
-> 查询时的 action/category/data 匹配与策略过滤只要把 owner、状态和生效时机分开,许多看似矛盾的现象就能解释:解析成功不等于 resolver 已注册;空 action 可能 success 但没有加入组件;autoVerify 是否生效取决于组件调用方;pathPattern 的可用性取决于 allowGlobs;mHasDefault 是派生值而不是第二份数据。后续阅读 Intent 匹配算法和 Domain Verification 时,可以直接沿这条链进入查询阶段。
