Skip to content

共享库依赖解析

追踪四类 Manifest 库依赖如何进入包模型,并在安装时完成版本、证书、可选性和运行时库路径解析。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageParserShared LibraryClassLoader源码阅读

共享库依赖解析 ​

本文承接 Manifest 解析入口、Split APK 解析 和 meta-data 解析。主题是 Manifest 中的库依赖声明如何被解析和消费,重点覆盖 <uses-library>、<uses-native-library>、<uses-static-library> 和 <uses-sdk-library>。

要先划清一个边界:解析阶段只把声明、版本、证书摘要和 required/optional 状态写入 ParsingPackage;真正查找提供者、构建 SharedLibraryInfo、更新 PackageSetting 和形成运行时库文件列表发生在 SharedLibrariesImpl。因此“Manifest 解析成功”并不等于“依赖库已经找到并加入 class path”。

1. 总体主线 ​

1.1 声明到运行时 ​

四类标签共享“声明被保存、安装时再解析”的两阶段模型,但保存的数据形状不同:动态库是名称列表,静态库和 SDK 库还要维护版本与证书数组,native 库则有独立的 required/optional 列表。

1.2 四种声明 ​

标签保存字段解析阶段必须提供安装期用途
uses-libraryusesLibraries / usesOptionalLibrariesname,required 默认 true查找动态共享库
uses-native-librarynative required/optional 列表name,required 默认 true查找 native shared library
uses-static-libraryname、version、cert digests三者都必须存在精确版本/证书匹配
uses-sdk-libraryname、versionMajor、cert digests、optionalname/version/certSDK library 独立性策略

2. 包模型 ​

2.1 PackageImpl 字段 ​

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

java
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
protected List<String> usesLibraries = emptyList();
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
protected List<String> usesOptionalLibraries = emptyList();
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
protected List<String> usesNativeLibraries = emptyList();
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
protected List<String> usesOptionalNativeLibraries = emptyList();

private List<String> usesStaticLibraries = emptyList();
private long[] usesStaticLibrariesVersions;
private String[][] usesStaticLibrariesCertDigests;

private List<String> usesSdkLibraries = emptyList();
private long[] usesSdkLibrariesVersionsMajor;
private String[][] usesSdkLibrariesCertDigests;
private boolean[] usesSdkLibrariesOptional;

动态/native 库的 required 和 optional 依赖用两套列表表达;静态/SDK 库则用并行数组保存每项的版本和证书摘要。数组索引与对应名称列表的索引必须一致,后续安装期匹配会按同一个 index 读取三组数据。

2.2 去重与升级约束 ​

java
public PackageImpl addUsesLibrary(String libraryName) {
    libraryName = TextUtils.safeIntern(libraryName);
    if (!ArrayUtils.contains(this.usesLibraries, libraryName)) {
        this.usesLibraries = CollectionUtils.add(this.usesLibraries, libraryName);
    }
    return this;
}

public PackageImpl addUsesOptionalLibrary(String libraryName) {
    libraryName = TextUtils.safeIntern(libraryName);
    if (!ArrayUtils.contains(this.usesOptionalLibraries, libraryName)) {
        this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries,
                libraryName);
    }
    return this;
}

PackageImpl 的 add 方法本身只负责列表去重;“同名库先 optional 后 required 时 required 优先”由解析函数在调用 add 前通过 removeUsesOptionalLibrary() 实现。不要把所有语义都归到数据模型方法里。

2.3 静态与 SDK 追加 ​

java
public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor,
        String[] certSha256Digests, boolean usesSdkLibrariesOptional) {
    this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
            TextUtils.safeIntern(libraryName));
    this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
            this.usesSdkLibrariesVersionsMajor, versionMajor, true);
    this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
            this.usesSdkLibrariesCertDigests, certSha256Digests, true);
    this.usesSdkLibrariesOptional = ArrayUtils.appendBooleanDuplicatesAllowed(
            this.usesSdkLibrariesOptional, usesSdkLibrariesOptional);
    return this;
}

public PackageImpl addUsesStaticLibrary(String libraryName, long version,
        String[] certSha256Digests) {
    this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries,
            TextUtils.safeIntern(libraryName));
    this.usesStaticLibrariesVersions = ArrayUtils.appendLong(
            this.usesStaticLibrariesVersions, version, true);
    this.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
            this.usesStaticLibrariesCertDigests, certSha256Digests, true);
    return this;
}

静态/SDK 库的证书摘要不是一个单字符串字段,而是 String[][]:外层对应依赖项,内层允许主证书加 additional certificates。版本和证书数组必须与名称列表同步追加。

3. 动态库解析 ​

3.1 uses-library ​

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

java
private static ParseResult<ParsingPackage> parseUsesLibrary(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser) {
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesLibrary);
    try {
        // Do not allow a resource reference that may change after parsing.
        String lname = sa.getNonResourceString(
                R.styleable.AndroidManifestUsesLibrary_name);
        boolean req = sa.getBoolean(
                R.styleable.AndroidManifestUsesLibrary_required, true);

        if (lname != null) {
            lname = lname.intern();
            if (req) {
                pkg.addUsesLibrary(lname).removeUsesOptionalLibrary(lname);
            } else if (!ArrayUtils.contains(pkg.getUsesLibraries(), lname)) {
                pkg.addUsesOptionalLibrary(lname);
            }
        }
        return input.success(pkg);
    } finally {
        sa.recycle();
    }
}

required 默认 true;名称为空时源码不会在这个函数中抛错,而是保持成功且不加入列表。required 声明会把同名 optional 声明升级为 required;反过来 optional 不会降级已有 required。

3.2 native library ​

java
private static ParseResult<ParsingPackage> parseUsesNativeLibrary(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser) {
    TypedArray sa = res.obtainAttributes(parser,
            R.styleable.AndroidManifestUsesNativeLibrary);
    try {
        String lname = sa.getNonResourceString(
                R.styleable.AndroidManifestUsesNativeLibrary_name);
        boolean req = sa.getBoolean(
                R.styleable.AndroidManifestUsesNativeLibrary_required, true);

        if (lname != null) {
            if (req) {
                pkg.addUsesNativeLibrary(lname)
                        .removeUsesOptionalNativeLibrary(lname);
            } else if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) {
                pkg.addUsesOptionalNativeLibrary(lname);
            }
        }
        return input.success(pkg);
    } finally {
        sa.recycle();
    }
}

native 库与 Java 动态库的 required/optional 规则相同,但存储列表独立。它是否在安装期被强制检查还受 ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES compatibility change 影响,解析阶段只记录声明。

4. 静态库解析 ​

4.1 必需字段 ​

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

java
TypedArray sa = res.obtainAttributes(parser,
        R.styleable.AndroidManifestUsesStaticLibrary);
try {
    String lname = sa.getNonResourceString(
            R.styleable.AndroidManifestUsesLibrary_name);
    final int version = sa.getInt(
            R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
    String certSha256Digest = sa.getNonResourceString(
            R.styleable.AndroidManifestUsesStaticLibrary_certDigest);

    if (lname == null || version < 0 || certSha256Digest == null) {
        return input.error("Bad uses-static-library declaration name: " + lname
                + " version: " + version + " certDigest" + certSha256Digest);
    }

    if (pkg.getUsesStaticLibraries().contains(lname)) {
        return input.error("Depending on multiple versions of static library " + lname);
    }

静态库名称、非负 version 和 certDigest 都是解析必需项;同一包依赖同一静态库多个版本会立即失败。这里的 version 是静态库自己的 android:version,不是 package version code。

4.2 证书数组 ​

java
    lname = lname.intern();
    certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();

    String[] additionalCertSha256Digests = EmptyArray.STRING;
    if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) {
        ParseResult<String[]> certResult =
                parseAdditionalCertificates(input, pkg, res, parser);
        if (certResult.isError()) {
            return input.error(certResult);
        }
        additionalCertSha256Digests = certResult.getResult();
    }

    String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
    certSha256Digests[0] = certSha256Digest;
    System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, 1,
            additionalCertSha256Digests.length);
    return input.success(pkg.addUsesStaticLibrary(lname, version, certSha256Digests));

证书摘要会移除冒号并转小写;面向 O-MR1 及以上 target SDK 的包还会读取 additional certificates。解析阶段不验证库提供者是否存在,只把规范化后的摘要交给安装期匹配。

5. SDK 库解析 ​

5.1 版本与可选性 ​

java
private static ParseResult<ParsingPackage> parseUsesSdkLibrary(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser)
        throws XmlPullParserException, IOException {
    TypedArray sa = res.obtainAttributes(parser,
            R.styleable.AndroidManifestUsesSdkLibrary);
    try {
        String lname = sa.getNonResourceString(
                R.styleable.AndroidManifestUsesSdkLibrary_name);
        final int versionMajor = sa.getInt(
                R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1);
        String certSha256Digest = sa.getNonResourceString(
                R.styleable.AndroidManifestUsesSdkLibrary_certDigest);
        boolean optional = sa.getBoolean(
                R.styleable.AndroidManifestUsesSdkLibrary_optional, false);

        if (lname == null || versionMajor < 0 || certSha256Digest == null) {
            return input.error("Bad uses-sdk-library declaration name: " + lname
                    + " version: " + versionMajor + " certDigest" + certSha256Digest);
        }
        if (pkg.getUsesSdkLibraries().contains(lname)) {
            return input.error("Depending on multiple versions of SDK library " + lname);
        }

SDK 库用 versionMajor 而不是普通 version;optional 默认 false。源码明确拒绝同一包依赖同名 SDK 库的多个版本。

5.2 摘要与追加证书 ​

java
        lname = lname.intern();
        certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
        if ("".equals(certSha256Digest)) {
            // Test-only default digest override.
            certSha256Digest = SystemProperties.get(
                    "debug.pm.uses_sdk_library_default_cert_digest", "");
            try {
                HexEncoding.decode(certSha256Digest, false);
            } catch (IllegalArgumentException e) {
                certSha256Digest = "";
            }
        }

        ParseResult<String[]> certResult = parseAdditionalCertificates(input, pkg, res, parser);
        if (certResult.isError()) {
            return input.error(certResult);
        }
        String[] additional = certResult.getResult();
        String[] certs = new String[additional.length + 1];
        certs[0] = certSha256Digest;
        System.arraycopy(additional, 0, certs, 1, additional.length);
        return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certs, optional));

空 certDigest 的系统属性覆盖是测试专用兼容路径,不应理解为生产环境可以省略证书。SDK 库额外证书始终走 parseAdditionalCertificates,最后以名称、versionMajor、certs、optional 四组并行数据保存。

6. 轻量解析同步 ​

6.1 ApkLite 字段 ​

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

java
List<String> usesSdkLibraries = new ArrayList<>();
long[] usesSdkLibrariesVersionsMajor = new long[0];
String[][] usesSdkLibrariesCertDigests = new String[0][0];
List<String> usesStaticLibraries = new ArrayList<>();
long[] usesStaticLibrariesVersions = new long[0];
String[][] usesStaticLibrariesCertDigests = new String[0][0];

轻量解析也必须记录静态/SDK 库,因为安装阶段可能在尚未完整解析所有 APK 时先检查缺失依赖。它与完整解析使用相同的并行数组原则,最终由 PackageLite 携带。

6.2 SDK 轻量分支 ​

java
if (usesSdkLibName == null || usesSdkLibName.isBlank()
        || usesSdkLibVersionMajor < 0) {
    return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
            "Bad uses-sdk-library declaration name: " + usesSdkLibName
                    + " version: " + usesSdkLibVersionMajor);
}
if (usesSdkLibraries.contains(usesSdkLibName)) {
    return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
            "Depending on multiple versions of SDK library: " + usesSdkLibName);
}
usesSdkLibraries.add(usesSdkLibName);
usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
        usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor, true);

轻量分支直接读取 Manifest 属性并做结构校验,保留了 bundletool 将 versionMajor 写成字符串的兼容转换。完整解析和轻量解析的错误边界必须分别阅读,不能只看其中一条路径。

7. 安装期解析 ​

7.1 收集四类库 ​

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

java
ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
if (!pkg.getUsesLibraries().isEmpty()) {
    usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
            pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
            availablePackages, newLibraries, null);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
    usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
            pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
            null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
            pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries, null);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
    usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, null,
            pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
            usesLibraryInfos, availablePackages, newLibraries, null);
}

收集顺序是 required dynamic、required static、optional dynamic;collectSharedLibraryInfos 负责在 available packages 中查找、验证并形成 SharedLibraryInfo。顺序会影响后续搜索顺序,因此不是任意的集合遍历。

7.2 Native/SDK 策略 ​

java
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
        pkg.getPackageName(), pkg.getTargetSdkVersion())) {
    if (!pkg.getUsesNativeLibraries().isEmpty()) {
        usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, null, null,
                pkg.getPackageName(), "native shared", true, pkg.getTargetSdkVersion(),
                usesLibraryInfos, availablePackages, newLibraries, null);
    }
    if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
        usesLibraryInfos = collectSharedLibraryInfos(
                pkg.getUsesOptionalNativeLibraries(), null, null, null,
                pkg.getPackageName(), "native shared", false, pkg.getTargetSdkVersion(),
                usesLibraryInfos, availablePackages, newLibraries, null);
    }
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
    boolean required = !Flags.sdkLibIndependence();
    usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
            pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
            pkg.getUsesSdkLibrariesOptional(), pkg.getPackageName(), LIBRARY_TYPE_SDK,
            required, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
            newLibraries, null);
}

native 依赖是否强制检查由 compatibility change 控制;SDK 库整体 required 与 sdkLibIndependence flag 有关,同时每项仍保留 optional 数组。也就是说,Manifest 的 optional 值不是安装期唯一决定因素。

7.3 缺失依赖检查 ​

java
ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
        pkgLite.getUsesSdkLibrariesVersionsMajor(),
        pkgLite.getUsesSdkLibrariesCertDigests(),
        /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
        /*required=*/ true, pkgLite.getTargetSdk(),
        /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
        missingSharedLibrary);
collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
        pkgLite.getUsesStaticLibrariesVersions(),
        pkgLite.getUsesStaticLibrariesCertDigests(),
        /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
        /*required=*/ true, pkgLite.getTargetSdk(),
        /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
        missingSharedLibrary);

PackageLite 可在完整包对象之外参与 SDK/static 缺失库检查。这里把两类库按 required 检查,得到的 missing 列表供安装流程决定是否继续;它不是对完整解析对象的替代。

8. 对外与运行时 ​

8.1 Info 输出 ​

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

java
PackageStateUnserialized pkgState = pkgSetting.getTransientState();
List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
var usesLibraries = pkgState.getUsesLibraryInfos();
var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
for (int index = 0; index < usesLibraries.size(); index++) {
    usesLibraryInfos.add(usesLibraries.get(index).getInfo());
}
info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
        ? null : usesLibraryFiles.toArray(new String[0]);
info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;

ApplicationInfo 使用的是 PackageSetting transient state,而不是直接读取 pkg.getUsesLibraries()。这说明安装期解析已经把声明解析成实际库信息和文件路径;PackageInfo 只是复制最终状态。

8.2 sorted accessor ​

java
public String[] getUsesLibrariesSorted() {
    if (mUsesLibrariesSorted == null) {
        // Lazy sorting preserves immutable content.
        mUsesLibrariesSorted = sortLibraries(usesLibraries);
    }
    return mUsesLibrariesSorted;
}

包模型保留 Manifest 声明顺序,同时提供懒排序数组给需要稳定顺序的消费者。排序副本不是依赖解析本身,也不会改变原始 required/optional 列表。

9. 状态与失败 ​

9.1 状态机 ​

9.2 失败边界 ​

阶段条件结果
Manifest 解析static/SDK 缺 name、version 或 cert立即 parse error
Manifest 解析同名 static/SDK 多版本立即 parse error
Manifest 解析动态/native name 缺失成功但不加入列表
安装期查找required provider 不存在安装失败
安装期查找版本或证书不匹配安装失败
安装期查找optional provider 不存在继续,省略该库
compatibilitynative enforcement 未启用native 依赖可能不强制检查

解析失败不会产生可提交的完整包;安装期依赖失败则发生在包对象已经构建之后,由 shared-library 更新流程决定是否回滚安装。

9.3 清理路径 ​

包替换或卸载时,shared-library 更新逻辑会重新计算依赖关系并更新 PackageSetting transient state;对外 ApplicationInfo 下次生成时读取新状态。旧的 sharedLibraryFiles 不应被理解为永久绑定在 Manifest 对象上,它们属于安装期状态。

10. 测试与诊断 ​

10.1 输入与断言 ​

场景断言覆盖范围
required dynamic library进入 usesLibraries默认 required
optional 后 requiredoptional 列表移除,required 保留强约束升级
static libraryname/version/certs 三组索引对齐并行数组
SDK library optionaloptional 数组对应名称索引为 trueSDK 可选性
同名 static 两版本ParseResult errorManifest 约束
非法 cert digest安装期匹配失败证书路径
optional provider 缺失继续安装且不出现在 transient state可选恢复
native enforcement 开关同一声明在不同 target/change 下结果不同compatibility
ApplicationInfo 输出files/infos 来自 PackageSetting对外消费

测试必须分开验证三种对象:解析后的 ParsingPackage 声明、安装期 SharedLibraryInfo 结果、最终 ApplicationInfo 文件/信息列表。只断言其中一个层次,无法定位失败发生在声明、查找还是输出。

10.2 现场排查顺序 ​

  1. 先确认标签类型和 required/optional 属性是否进入正确列表。
  2. 对 static/SDK 库打印名称、版本数组和证书摘要数组的同一 index。
  3. 检查 provider 包是否已进入 availablePackages,版本和签名是否满足匹配。
  4. 查看 native enforcement 与 sdkLibIndependence 的当前策略。
  5. 检查 PackageSetting.getTransientState() 是否有实际 SharedLibraryInfo 和文件路径。
  6. 最后查看 ApplicationInfo flags 是否请求了 shared library files,以及运行时 class loader 使用的路径是否来自最新 transient state。

11. 源码路线 ​

建议按以下顺序阅读:

  1. ParsingPackageUtils.parseUsesLibrary() / parseUsesNativeLibrary():动态和 native 列表。
  2. parseUsesStaticLibrary() / parseUsesSdkLibrary():版本、证书和并行数组。
  3. ApkLiteParseUtils 中对应分支:轻量解析为何也保存 static/SDK 依赖。
  4. PackageImpl 的 add/get/makeImmutable:声明如何去重、排序和冻结。
  5. SharedLibrariesImpl.collectSharedLibraryInfos():安装期提供者查找和 required 语义。
  6. PackageInfoUtils.generateApplicationInfo():最终 files/infos 的来源。
  7. compatibility change 与 SDK independence 分支:为什么同一 Manifest 在不同策略下结果不同。

12. 设计收束 ​

共享库依赖的真实状态链是:

text
Manifest
  -> 名称列表、版本数组、证书数组、可选性数组
  -> PackageLite/PackageImpl 保存声明
  -> SharedLibrariesImpl 查找并校验 provider
  -> PackageSetting 保存实际 SharedLibraryInfo 与文件路径
  -> ApplicationInfo 和运行时 ClassLoader 消费最终状态

解析器负责“声明是否合法、如何保存”;shared-library 子系统负责“声明指向谁、是否满足版本/证书/策略”;PackageInfo 负责“把已解析的安装期结果暴露出去”。把三层分开,才能正确解释 optional 库为何可以缺失、static/SDK 库为何必须携带版本和证书,以及为什么仅看到 uses-library 字符串并不能证明类路径已经建立。