Skip to content

Manifest 解析入口

追踪 Android 17 Manifest 从轻量预解析到 base/split 完整解析的入口、分支、资源加载和错误传播。

基于android-17.0.0_r1
AndroidPackageManagerServiceAndroidManifestPackageParser2Split APK源码阅读

Manifest 解析入口 ​

本文面向已经读过 PackageParser2 架构 和 增量扫描 的读者。前文解释了 parser 门面、ThreadLocal 和 cache;本文把镜头推进到 Android 17 的 Manifest 入口,追踪一个文件或目录如何经过 PackageLite、AssetLoader、base/split XML parser,最终生成 ParsingPackage。

Android 17 把入口拆成三层:ApkLiteParseUtils 做轻量结构检查,ParsingPackageUtils 创建资源加载器并解析完整 Manifest,PMS 再负责签名、ABI、scan policy、PackageSetting 和注册。本文不重复四大组件的全部属性,而是解释入口选择、base/split 汇合、flags 传递、资源释放和错误返回。

读完后,读者应能从 PackageParser2.parsePackage 进入 parseMonolithicPackage 或 parseClusterPackage;判断目录为什么被视为 cluster、缺少 base APK 在哪一层失败、isolated split 依赖树由谁构造、PARSE_COLLECT_CERTIFICATES 为什么不属于 Lite 阶段;还能区分轻量解析、完整 Manifest、split 资源和签名收集的失败边界。

1. 入口分层 ​

1.1 文件与目录 ​

源码文件:

  • frameworks/base/core/java/com/android/internal/pm/parsing/PackageParser2.java
  • frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
  • frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
输入Lite 入口完整入口结果
单个 APK 文件parseMonolithicPackageLiteparseMonolithicPackage一个 base package
包目录parseClusterPackageLiteparseClusterPackagebase + 多个 split
已有 PackageLite不重新读 APKparsePackageFromPackageLite默认字段的 package

文件/目录形态只决定第一层分发,不决定签名是否已经验证。PackageParser2 的 parsePackage 返回 ParsedPackage 前会调用 hideAsParsed;证书兼容性仍由 PMS 扫描/安装阶段负责。

1.2 Lite 与完整对象 ​

PackageLite 保存 package path、base path、split names、split paths、revision codes、split dependency 元数据和少量 Manifest 属性。完整 ParsingPackage/ParsedPackage 才承载 application、权限、组件、库依赖和派生字段。Lite 成功只能证明入口信息可读,不能证明 Manifest 的所有标签合法。

1.3 资源所有权 ​

SplitAssetLoader、AssetManager、XmlResourceParser 和 TypedArray 都是一次 parse 的局部资源。ParsingPackageUtils 在对应方法的 finally 或 try-with-resources 中关闭它们;PackageParser2.close 只清理 ThreadLocal。

2. Lite 预解析 ​

2.1 统一分发 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
        File packageFile, int flags) {
    if (packageFile.isDirectory()) {
        return parseClusterPackageLite(input, packageFile, flags);
    } else {
        return parseMonolithicPackageLite(input, packageFile, flags);
    }
}

Lite 分发与完整 parser 分发保持一致,但返回类型是 PackageLite。它让后续代码在打开完整资源前确认包形态和 split 元数据。

2.2 单体 APK ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> parseMonolithicPackageLite(
        ParseInput input, File packageFile, int flags) {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
    try {
        final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
        if (result.isError()) {
            return input.error(result);
        }
        final ApkLite baseApk = result.getResult();
        final String packagePath = packageFile.getAbsolutePath();
        return input.success(new PackageLite(packagePath, baseApk.getPath(), baseApk,
                null, null, null, null, null, null,
                baseApk.getTargetSdkVersion(), null, null));
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

单体结果的 split 数组为空,base APK 同时是输入文件和 base code path。Lite 阶段可能读取 Manifest 的轻量字段,但不会替代完整 parseBaseApk。

2.3 Cluster 校验 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> parseClusterPackageLite(
        ParseInput input, File packageDir, int flags) {
    final File[] files = packageDir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
        return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
                "No packages found in split");
    }
    if (files.length == 1 && files[0].isDirectory()) {
        return parseClusterPackageLite(input, files[0], flags);
    }

    String packageName = null;
    int versionCode = 0;
    ApkLite baseApk = null;
    final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
    try {
        for (File file : files) {
            if (!isApkFile(file)) {
                continue;
            }
            final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
            if (result.isError()) {
                return input.error(result);
            }
            final ApkLite lite = result.getResult();
            if (packageName == null) {
                packageName = lite.getPackageName();
                versionCode = lite.getVersionCode();
            } else {
                if (!packageName.equals(lite.getPackageName())) {
                    return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                            "Inconsistent package " + lite.getPackageName()
                                    + " in " + file + "; expected " + packageName);
                }
                if (versionCode != lite.getVersionCode()) {
                    return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                            "Inconsistent version " + lite.getVersionCode()
                                    + " in " + file + "; expected " + versionCode);
                }
            }
            ApkLite prev = apks.put(lite.getSplitName(), lite);
            if (prev != null) {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Split name " + lite.getSplitName()
                                + " defined more than once; most recent was " + file);
            }
        }
        baseApk = apks.remove(null);
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
    return composePackageLiteFromApks(input, packageDir, baseApk, apks);
}

cluster Lite 的校验顺序是空目录、嵌套目录归一化、非 APK 过滤、包名一致性、versionCode 一致性、split name 唯一性,最后由 composePackageLiteFromApks 检查 base 是否存在。

2.4 split 排序 ​

源码文件:frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java

java
public static ParseResult<PackageLite> composePackageLiteFromApks(
        ParseInput input, File packageDir, ApkLite baseApk,
        ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
    if (baseApk == null) {
        return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Missing base APK in " + packageDir);
    }
    final int size = ArrayUtils.size(splitApks);
    String[] splitNames = null;
    String[] splitCodePaths = null;
    int[] splitRevisionCodes = null;
    if (size > 0) {
        splitNames = new String[size];
        splitCodePaths = new String[size];
        splitRevisionCodes = new int[size];
        splitNames = splitApks.keySet().toArray(splitNames);
        Arrays.sort(splitNames, sSplitNameComparator);
        for (int i = 0; i < size; i++) {
            final ApkLite apk = splitApks.get(splitNames[i]);
            splitCodePaths[i] = apkRenamed
                    ? new File(packageDir, splitNameToFileName(apk)).getAbsolutePath()
                    : apk.getPath();
            splitRevisionCodes[i] = apk.getRevisionCode();
        }
    }
    return input.success(/* PackageLite with sorted split arrays */);
}

真实实现还填充 feature split、uses split、config split、required split types 和 split types。所有并行数组使用同一排序后的 split index,后续 loader 和 parseSplitApk 都依赖这个不变量。

3. 完整路径 ​

3.1 monolithic 路径 ​

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

java
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input,
        File apkFile, int flags) {
    int liteParseFlags = flags & ~PARSE_COLLECT_CERTIFICATES;
    final ParseResult<PackageLite> liteResult =
            ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, liteParseFlags);
    if (liteResult.isError()) {
        return input.error(liteResult);
    }
    final PackageLite lite = liteResult.getResult();
    final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
    try {
        boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
        final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile,
                apkFile.getCanonicalPath(), assetLoader, flags, shouldSkipComponents);
        if (result.isError()) {
            return input.error(result);
        }
        return input.success(result.getResult().set32BitAbiPreferred(lite.isUse32bitAbi()));
    } catch (IOException e) {
        return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + apkFile, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

Lite 阶段不收集证书,完整 base parse 才进入资源和 Manifest 标签。canonical path、loader 和 ParseResult 都有独立失败/清理路径。

3.2 cluster 路径 ​

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

java
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input,
        File packageDir, int flags) {
    int liteParseFlags = 0;
    if ((flags & PARSE_APK_IN_APEX) != 0) {
        liteParseFlags |= PARSE_APK_IN_APEX;
    }
    if ((flags & PARSE_APEX) != 0) {
        liteParseFlags |= PARSE_APEX;
    }
    final ParseResult<PackageLite> liteResult =
            ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
    if (liteResult.isError()) {
        return input.error(liteResult);
    }
    final PackageLite lite = liteResult.getResult();
    SparseArray<int[]> splitDependencies = null;
    final SplitAssetLoader assetLoader;
    if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
        try {
            splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
        } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
            return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
        }
    } else {
        assetLoader = new DefaultSplitAssetLoader(lite, flags);
    }

    try {
        final File baseApk = new File(lite.getBaseApkPath());
        boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
        final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                lite.getPath(), assetLoader, flags, shouldSkipComponents);
        if (result.isError()) {
            return input.error(result);
        }
        ParsingPackage pkg = result.getResult();
        if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
            pkg.asSplit(lite.getSplitNames(), lite.getSplitApkPaths(),
                    lite.getSplitRevisionCodes(), splitDependencies);
            for (int i = 0; i < lite.getSplitNames().length; i++) {
                final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                final ParseResult<ParsingPackage> split =
                        parseSplitApk(input, pkg, i, splitAssets, flags);
                if (split.isError()) {
                    return input.error(split);
                }
            }
        }
        pkg.set32BitAbiPreferred(lite.isUse32bitAbi());
        return input.success(pkg);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

cluster 的 isolated split dependency 非法会在 loader 创建前失败;base 成功后 split 逐个写入同一个 ParsingPackage,任一 split 失败都会使整个 cluster 失败。

3.3 base Manifest ​

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

java
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
        String codePath, Resources res, XmlResourceParser parser, int flags,
        boolean shouldSkipComponents)
        throws XmlPullParserException, IOException {
    ParseResult<Pair<String, String>> packageSplitResult =
            ApkLiteParseUtils.parsePackageSplitNames(input, parser);
    if (packageSplitResult.isError()) {
        return input.error(packageSplitResult);
    }
    Pair<String, String> packageSplit = packageSplitResult.getResult();
    final String pkgName = packageSplit.first;
    final String splitName = packageSplit.second;
    if (!TextUtils.isEmpty(splitName)) {
        return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                "Expected base APK, but found split " + splitName);
    }

    final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
    try {
        final boolean isCoreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
        final ParsingPackage pkg = mCallback.startParsingPackage(
                pkgName, apkPath, codePath, manifestArray, isCoreApp);
        final ParseResult<ParsingPackage> result = parseBaseApkTags(
                input, pkg, manifestArray, res, parser, flags, shouldSkipComponents);
        if (result.isError()) {
            return input.error(result);
        }
        return input.success(pkg);
    } finally {
        manifestArray.recycle();
    }
}

base 入口先确认 package/split name,再由 callback 创建 ParsingPackage,之后进入顶层标签循环。base APK 错带 split name 会返回 BAD_PACKAGE_NAME。

3.4 split Manifest ​

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

java
private ParseResult<ParsingPackage> parseSplitApk(ParseInput input,
        ParsingPackage pkg, int splitIndex, AssetManager assets,
        XmlResourceParser parser, int flags) {
    final String apkPath = pkg.getSplitCodePaths()[splitIndex];
    final int cookie = assets.findCookieForPath(apkPath);
    if (cookie == 0) {
        return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Failed adding asset path: " + apkPath);
    }
    try (XmlResourceParser splitParser = assets.openXmlResourceParser(
            cookie, ANDROID_MANIFEST_FILENAME, false)) {
        Resources res = new Resources(assets, mDisplayMetrics, null);
        ParseResult<ParsingPackage> parseResult = parseSplitApk(
                input, pkg, res, splitParser, flags, splitIndex);
        if (parseResult.isError()) {
            return input.error(parseResult.getErrorCode(),
                    apkPath + " (at " + splitParser.getPositionDescription() + "): "
                            + parseResult.getErrorMessage());
        }
        return parseResult;
    } catch (Exception e) {
        return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    }
}

split parser 不创建新的 package 对象,而是把 split application/classloader/components 写入 base package。cookie、XML 和标签错误都会附加 split path。

4. 标签分发 ​

4.1 base 标签循环 ​

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

java
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
        TypedArray sa, Resources res, XmlResourceParser parser, int flags,
        boolean shouldSkipComponents) throws XmlPullParserException, IOException {
    ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
    if (sharedUserResult.isError()) {
        return sharedUserResult;
    }
    pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
            R.styleable.AndroidManifest_installLocation, sa))
            .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                    R.styleable.AndroidManifest_targetSandboxVersion, sa))
            .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);

    int type;
    boolean foundApp = false;
    final int outerDepth = parser.getDepth();
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }
        final String tagName = parser.getName();
        final ParseResult result;
        if (TAG_APPLICATION.equals(tagName)) {
            if (foundApp) {
                result = RIGID_PARSER
                        ? input.error("<manifest> has more than one <application>")
                        : input.success(null);
            } else {
                foundApp = true;
                result = parseBaseApplication(input, pkg, res, parser, flags,
                        shouldSkipComponents);
            }
        } else {
            result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
        }
        if (result.isError()) {
            return input.error(result);
        }
    }

    if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
        ParseResult<?> deferResult = input.deferError(
                "<manifest> does not contain an <application> or <instrumentation>",
                DeferredError.MISSING_APP_TAG);
        if (deferResult.isError()) {
            return input.error(deferResult);
        }
    }
    return validateBaseApkTags(input, pkg, flags);
}

base 循环把 application 交给专用 parser,把其他顶层标签交给 parseBaseApkTag。缺 application 时先创建 deferred error,最后由 validateBaseApkTags 决定是否失败。

4.2 顶层路由 ​

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

java
private ParseResult parseBaseApkTag(String tag, ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
        throws IOException, XmlPullParserException {
    switch (tag) {
        case TAG_OVERLAY:
            return parseOverlay(input, pkg, res, parser);
        case TAG_KEY_SETS:
            return parseKeySets(input, pkg, res, parser);
        case TAG_PERMISSION_GROUP:
            return parsePermissionGroup(input, pkg, res, parser);
        case TAG_PERMISSION:
            return parsePermission(input, pkg, res, parser, flags);
        case TAG_PERMISSION_TREE:
            return parsePermissionTree(input, pkg, res, parser);
        case TAG_USES_PERMISSION:
        case TAG_USES_PERMISSION_SDK_M:
            return parseUsesPermission(input, pkg, res, parser);
        case TAG_USES_FEATURE:
            return parseUsesFeature(input, pkg, res, parser);
        case TAG_USES_SDK_LIBRARY:
            return parseUsesSdkLibrary(input, pkg, res, parser);
        case TAG_USES_STATIC_LIBRARY:
            return parseUsesStaticLibrary(input, pkg, res, parser);
        case TAG_USES_LIBRARY:
            return parseUsesLibrary(input, pkg, res, parser);
        default:
            return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
    }
}

未知标签由 ParsingUtils.unknownTag 处理,具体是 warning、跳过还是 error 取决于 parser 配置和标签上下文。

4.3 application 分支 ​

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

java
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
        boolean shouldSkipComponents) throws XmlPullParserException, IOException {
    final String pkgName = pkg.getPackageName();
    int targetSdk = pkg.getTargetSdkVersion();
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
    try {
        if (sa == null) {
            return input.error("<application> does not contain any attributes");
        }
        String name = sa.getNonConfigurationString(
                R.styleable.AndroidManifestApplication_name, 0);
        if (name != null) {
            String className = buildClassName(pkgName, name, input);
            if (className == null) {
                return input.error("Invalid application class name " + name);
            }
            pkg.setClassName(className);
        }
        parseBaseAppBasicFlags(pkg, sa, flags, targetSdk);
        // child tags and main components are parsed here
        return input.success(pkg);
    } finally {
        sa.recycle();
    }
}

application parser 负责 application class、基础 flags、child tags 和组件入口。shouldSkipComponents 只影响特定 SDK library 条件,不代表跳过 application 基础属性。

4.4 split app ​

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

java
private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser,
        int flags, int splitIndex) throws XmlPullParserException, IOException {
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
    try {
        pkg.setSplitHasCode(splitIndex, sa.getBoolean(
                R.styleable.AndroidManifestApplication_hasCode, true));
        final String classLoaderName = sa.getString(
                R.styleable.AndroidManifestApplication_classLoader);
        if (classLoaderName == null
                || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
            pkg.setSplitClassLoaderName(splitIndex, classLoaderName);
        } else {
            return input.error("Invalid class loader name: " + classLoaderName);
        }
    } finally {
        sa.recycle();
    }
    String defaultSplitName = pkg.getSplitNames()[splitIndex];
    // main components and split child tags use defaultSplitName
    return input.success(pkg);
}

split application 的字段集合比 base 窄,重点是 hasCode、class loader 和组件默认 split。非法 class loader 会使整个 cluster parse 失败。

5. flags 与证书 ​

5.1 Lite flags ​

parseMonolithicPackage 会清除 PARSE_COLLECT_CERTIFICATES;cluster Lite 只保留 PARSE_APK_IN_APEX 和 PARSE_APEX。这使结构识别可以快速完成,而签名收集留给独立的 getSigningDetails。

5.2 证书入口 ​

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

java
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
        ParsedPackage pkg, boolean skipVerify) {
    return getSigningDetails(input, pkg.getBaseApkPath(), pkg.isStaticSharedLibrary(),
            pkg.getTargetSdkVersion(), pkg.getSplitCodePaths(), skipVerify);
}

parseBaseApk 成功不等于签名兼容;PMS 仍会基于 PackageSetting、disabled setting 和 shared user 比较证书。

5.3 APEX 条件 ​

APEX flags 会改变 Lite parser 读取的字段和完整 Manifest 校验;split dependency、APEX permission 和 code path 有专门分支。APEX 内 APK 的签名、分区 flags 和最终注册由 PMS/APEX owner 补充。

6. 错误与清理 ​

6.1 错误表 ​

层条件错误
cluster Lite空目录INSTALL_PARSE_FAILED_NOT_APK
cluster Lite包名/versionCode 不一致INSTALL_PARSE_FAILED_BAD_MANIFEST
compose缺少 base APKINSTALL_PARSE_FAILED_BAD_MANIFEST
split dependencyisolated split 非法INSTALL_PARSE_FAILED_BAD_MANIFEST
basebase 带 split nameINSTALL_PARSE_FAILED_BAD_PACKAGE_NAME
split assetcookie 为 0INSTALL_PARSE_FAILED_BAD_MANIFEST
pathcanonical path 失败INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION
resource/XML未预期异常INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION

6.2 失败对象 ​

base 或任一 split 失败时,loader、XML parser、TypedArray 都应关闭;ParsingPackage 不会进入 hideAsParsed,也不会交给 PMS 的签名/ABI/注册流程。

6.3 cache 与重试 ​

cache hit 不会进入完整 parser;cache miss 后完整 parser 失败,PMS 回到 ParseResult error。cache 损坏由服务端 PackageCacher 删除 entry 并返回 miss,入口 parser 本身不负责修复 cache 文件。

7. 测试与诊断 ​

7.1 结构测试 ​

源码文件:

  • frameworks/base/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
  • frameworks/base/core/tests/coretests/src/com/android/internal/pm/parsing/PackageParser2Test.java

测试输入和断言应覆盖:单 APK 的 base path 与空 split 数组;cluster 的 package/version 一致和 split 排序;缺 base、重复 split、空目录、一层嵌套目录;isolated split 非法 dependency;cache hit 跳过完整解析、cache miss 成功写回。它们证明入口结构和错误边界,不证明 PMS 后续签名、ABI 或组件注册。

7.2 并行入口 ​

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

java
pr.parsedPackage = parsePackage(scanFile, scanParams.parseFlags);

并行 parser 共享 PackageParser2 实例,但每个线程通过 ThreadLocal 获得自己的 ParseTypeImpl。测试应断言并发任务的 ParseResult 文件和异常彼此独立,不应跨线程复用一个 ParseInput。

7.3 Trace 与命令 ​

text
adb shell atrace --async_start package_manager sched freq idle
adb shell logcat -s PackageParser PackageManager PackageCacher
adb shell atrace --async_stop -z -o /data/local/tmp/manifest-parse.html
adb shell dumpsys package <package-name>

搜索 parseApkLite、parsePackage、parseBaseApk、parseSplitApk。只有 Lite 区段可能表示 Lite 失败或 cache hit;base 成功、split 失败时应检查 split path、cookie 和 loader;parse 成功但 dumpsys 没有包时,要继续看签名/reconcile/冲突阶段。

8. 源码路线 ​

定位 Manifest 解析问题时,按以下顺序阅读:

  1. PackageParser2.parsePackage:cache、嵌套目录归一化和文件/目录分发。
  2. ApkLiteParseUtils.parseMonolithicPackageLite/parseClusterPackageLite:包名、version、split、base 校验。
  3. composePackageLiteFromApks:split 排序、路径和 revision code。
  4. ParsingPackageUtils.parseMonolithicPackage/parseClusterPackage:loader、APEX flags、isolated split 和 close。
  5. parseBaseApk:Manifest 根标签、package/split name 和 package 创建。
  6. parseSplitApk:cookie、split parser、错误 path/position。
  7. parseBaseApkTags/parseBaseApkTag:顶层标签路由和 deferred error。
  8. parseBaseApplication/parseSplitApplication:application 字段和 split 限制。
  9. getSigningDetails:证书收集与 Manifest parse 的边界。
  10. PackageParser2.hideAsParsed 与 PMS scan:解析结果何时交给 ScanRequest。

一个实用练习是:目录包含 base.apk 和两个 split,其中一个 split 的 package name 不同,且 cache 中存在旧结果。请判断 cache 是否可能命中、Lite parser 在哪一步失败、loader 是否创建、hideAsParsed 是否调用、哪些资源需要关闭,以及 PMS 是否有机会进入签名校验。

9. 设计收束 ​

Android 17 的 Manifest 入口是一条分层链:

  • PackageParser2 负责 cache、ThreadLocal 和异常外壳;ApkLiteParseUtils 负责轻量结构;ParsingPackageUtils 负责完整 Manifest。
  • monolithic 使用单 APK Lite、default loader 和 base parser;cluster 先校验 package/version/split/base,再选择 default 或 isolated split loader。
  • base 与 split 写入同一个 ParsingPackage,split index 由 Lite compose 的 comparator 确定。
  • PARSE_COLLECT_CERTIFICATES 不属于 Lite 结构阶段;签名收集由独立入口完成。
  • loader、XML parser、TypedArray 和 ThreadLocal 各自有关闭/回收 owner;PackageParser2.close 不能替代局部资源清理。
  • 空目录、缺 base、包名/version 不一致、重复 split、非法 dependency、错误 cookie 和 Manifest 异常会在不同层返回不同错误;失败对象不会进入 PMS 注册。

后续专题会深入四大组件解析,继续沿 parseBaseApplication 和 split child tag 路由,说明 Activity、Service、Provider、Receiver 如何进入 PackageImpl 数据模型。