Skip to content

Static Shared Library

追踪 Android 17 Static Shared Library 的精确版本、证书校验、内部包名、用户状态与清理流程。

基于android-17.0.0_r1
AndroidPMSSharedLibraryStaticLibrary

Static Shared Library ​

本文承接 SharedLibrary 管理,只讨论 Static Shared Library。它模拟“客户端绑定某个固定版本”的静态链接语义,但代码仍以独立包存在并可被多个客户端复用。文章沿安装解析、版本/证书匹配、内部包名、依赖用户同步和 prune 删除走完整调用链。

1. 精确版本语义 ​

源码文件:frameworks/base/core/java/android/content/pm/SharedLibraryInfo.java,符号:TYPE_STATIC

java
/**
 * Static shared libraries are not backwards-compatible.
 * Clients link against a specific version of the library.
 */
public static final int TYPE_STATIC = 2;

public static final int VERSION_UNDEFINED = -1;

public boolean isStatic() {
    return mType == TYPE_STATIC;
}

public long getLongVersion() {
    return mVersion;
}

Dynamic library 使用未定义版本并面向兼容更新;static library 客户端必须声明准确版本。PMS 以 library name + version 查找,不能自动把请求版本 3 升级到版本 4。

2. 双索引结构 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java,符号:library maps

java
private final WatchedArrayMap<String,
        WatchedLongSparseArray<SharedLibraryInfo>>
        mSharedLibraries;

private final WatchedArrayMap<String,
        WatchedLongSparseArray<SharedLibraryInfo>>
        mStaticLibsByDeclaringPackage;

第一张表按共享库公开名称和版本查询;第二张表按 manifest declaring package name 和版本查询。Static library 的内部安装包名可能是 synthetic name,因此需要两套索引同时维护。

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

java
void commitSharedLibraryInfoLPw(
        SharedLibraryInfo libraryInfo) {
    String name = libraryInfo.getName();
    WatchedLongSparseArray<SharedLibraryInfo> versions =
            mSharedLibraries.get(name);
    if (versions == null) {
        versions = new WatchedLongSparseArray<>();
        mSharedLibraries.put(name, versions);
    }
    versions.put(libraryInfo.getLongVersion(),
            libraryInfo);

    if (libraryInfo.isStatic()) {
        String declaringPackage = libraryInfo
                .getDeclaringPackage().getPackageName();
        WatchedLongSparseArray<SharedLibraryInfo> staticVersions =
                mStaticLibsByDeclaringPackage.get(
                        declaringPackage);
        if (staticVersions == null) {
            staticVersions = new WatchedLongSparseArray<>();
            mStaticLibsByDeclaringPackage.put(
                    declaringPackage, staticVersions);
        }
        staticVersions.put(libraryInfo.getLongVersion(),
                libraryInfo);
    }
}

提交 static library 时两个索引必须原子地更新;删除时也要同时移除,否则 name 查询和 declaring-package 查询会看到不同版本集合。

3. 内部包名解析 ​

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

java
private PackageStateInternal getLibraryPackage(
        Computer computer,
        SharedLibraryInfo libInfo) {
    VersionedPackage declaringPackage =
            libInfo.getDeclaringPackage();
    if (libInfo.isStatic()) {
        String internalPackageName =
                computer.resolveInternalPackageName(
                        declaringPackage.getPackageName(),
                        declaringPackage.getLongVersionCode());
        return computer.getPackageStateInternal(
                internalPackageName);
    }
    return computer.getPackageStateInternal(
            declaringPackage.getPackageName());
}

多版本共存要求每个 static library 版本有可区分的 PMS 内部 package identity。外部 declaring package name 用于 API/manifest,内部 synthetic name 用于 PackageSetting 和删除定位。

4. 依赖解析 ​

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

java
if (!pkg.getUsesStaticLibraries().isEmpty()) {
    usesLibraryInfos = collectSharedLibraryInfos(
            pkg.getUsesStaticLibraries(),
            pkg.getUsesStaticLibrariesVersions(),
            pkg.getUsesStaticLibrariesCertDigests(),
            null,
            pkg.getPackageName(),
            LIBRARY_TYPE_STATIC,
            true /* required */,
            pkg.getTargetSdkVersion(),
            usesLibraryInfos, availablePackages,
            newLibraries, null);
}

Static dependency 是 required,输入同时包含 name、versions 和 cert digests。任一版本或证书条件不满足,安装不能像 optional dynamic library 一样忽略。

5. 缺失版本 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java,符号:核心 collect 循环

java
String libName = requestedLibraries.get(i);
long libVersion = requiredVersions[i];
SharedLibraryInfo libraryInfo;
synchronized (mPm.mLock) {
    libraryInfo = SharedLibraryUtils
            .getSharedLibraryInfo(
                    libName, libVersion,
                    mSharedLibraries, newLibraries);
}

if (libraryInfo == null) {
    if (outMissingSharedLibraryInfos != null) {
        List<String> certs =
                Arrays.asList(requiredCertDigests[i]);
        outMissingSharedLibraryInfos.add(
                new SharedLibraryInfo(
                        libName, libVersion,
                        SharedLibraryInfo.TYPE_SDK_PACKAGE,
                        certs));
    } else {
        throw new PackageManagerException(
                INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                "requires unavailable static shared library");
    }
}

依赖安装功能可收集 missing library info;否则直接以 INSTALL_FAILED_MISSING_SHARED_LIBRARY 拒绝安装。缺少版本不是普通 warning。

6. 证书摘要校验 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java,符号:certificate validation

java
if (libraryInfo.getLongVersion()
        != requiredVersions[i]) {
    throw new PackageManagerException(
            INSTALL_FAILED_MISSING_SHARED_LIBRARY,
            "requires unavailable version");
}

AndroidPackage libraryPkg = availablePackages.get(
        libraryInfo.getPackageName());
SigningDetails signing = libraryPkg == null
        ? null : libraryPkg.getSigningDetails();
if (signing == null) {
    throw new PackageManagerException(
            INSTALL_FAILED_MISSING_SHARED_LIBRARY,
            "requires unavailable library");
}

版本命中后还要取得提供包签名。多个 expected digests 时,PMS 根据 targetSdk 计算签名 digest、排序后逐项比较;单个 digest 则解码并调用 hasSha256Certificate,从而支持签名轮换能力。

java
try {
    byte[] digest = HexEncoding.decode(
            expectedCertDigests[0], false);
    if (!signing.hasSha256Certificate(digest)) {
        throw new PackageManagerException(
                INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                "requires differently signed library");
    }
} catch (IllegalArgumentException e) {
    throw new PackageManagerException(
            INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST,
            "declares bad certificate digest");
}

格式错误与签名不匹配是两个失败码:bad digest 说明客户端声明本身非法;missing/differently signed 说明设备上的提供库不满足信任要求。

7. 新版本签名基准 ​

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

java
WatchedLongSparseArray<SharedLibraryInfo> versions =
        mSharedLibraries.get(
                parsedPackage.getStaticSharedLibraryName());
long previousVersion = -1;
for (int i = 0; versions != null
        && i < versions.size(); i++) {
    long version = versions.keyAt(i);
    if (version < parsedPackage
            .getStaticSharedLibraryVersion()) {
        previousVersion = Math.max(
                previousVersion, version);
    }
}
if (previousVersion >= 0) {
    SharedLibraryInfo previous =
            versions.get(previousVersion);
    return mPm.mSettings.getPackageLPr(
            previous.getPackageName());
}

安装新版本时优先找低于当前版本的最新库作为签名一致性基准。如果当前安装版本比所有现有版本都旧,则 fallback 到 declaring package 索引中的任一版本,仍然保证同一 static library 家族签名一致。

8. 用户状态同步 ​

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

java
for (SharedLibraryInfo info : usesLibraryInfos) {
    if (!info.isStatic()) {
        continue;
    }
    PackageSetting libSetting =
            mPm.getPackageSettingForMutation(
                    info.getPackageName());
    if (libSetting == null) {
        continue;
    }
    for (int i = 0; i < installedUserCount; i++) {
        libSetting.setInstalled(
                true, installedUsers[i]);
    }
}

依赖包在哪些用户 installed,static library 版本也会在这些用户标为 installed。这确保 per-user 查询和类加载能找到精确版本,同时仍共享一份代码文件。

9. Prune 候选 ​

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

java
for (SharedLibraryInfo libInfo : allVersions) {
    PackageStateInternal ps =
            getLibraryPackage(computer, libInfo);
    if (ps == null) continue;
    if (now - ps.getLastUpdateTime()
            < maxCachePeriod) {
        continue;
    }
    if (ps.isSystem()) {
        continue;
    }
    packagesToDelete.add(new VersionedPackage(
            ps.getPkg().getPackageName(),
            libInfo.getDeclaringPackage()
                    .getLongVersionCode()));
}

prune 只筛选缓存时间足够长、非系统的版本作为候选。候选并不等于可以删除。

java
if (mDeletePackageHelper.deletePackageX(
        pkgToDelete.getPackageName(),
        pkgToDelete.getLongVersionCode(),
        UserHandle.USER_SYSTEM,
        PackageManager.DELETE_ALL_USERS,
        true, Process.SYSTEM_UID)
        == PackageManager.DELETE_SUCCEEDED) {
    if (volume.getUsableSpace() >= neededSpace) {
        return true;
    }
}

最终同步删除会再次检查是否被任何用户使用;正在被客户端依赖的版本删除失败。每成功删除一个版本后重新检查可用空间,达到目标就停止。

10. 删除索引 ​

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

java
versions.remove(version);
if (versions.size() == 0) {
    mSharedLibraries.remove(libName);
}

if (libraryInfo.isStatic()) {
    String declaring = libraryInfo
            .getDeclaringPackage().getPackageName();
    WatchedLongSparseArray<SharedLibraryInfo> staticVersions =
            mStaticLibsByDeclaringPackage.get(declaring);
    if (staticVersions != null) {
        staticVersions.remove(version);
        if (staticVersions.size() == 0) {
            mStaticLibsByDeclaringPackage.remove(declaring);
        }
    }
}

只有最后一个版本删除后才移除库名/declaring package 顶层 key,保证其他版本继续可查。

11. 失败定位 ​

现象检查点
客户端安装报 missing libraryname/version 精确索引、依赖安装器
报 bad certificate digestmanifest digest 格式
报 differently signed提供包 SigningDetails 与 rotation history
新版本安装签名检查异常previous/fallback static library setting
某用户无法使用库依赖包与库版本的 installed user state
prune 不释放空间max cache period、system package、仍有用户依赖
删除后索引异常两张 version map 是否同步清理

12. 阅读检查 ​

复述:uses-static-library name/version/cert → 版本索引 → 证书校验 → synthetic internal package → 依赖 code paths 和 per-user installed 状态;清理时先筛候选,再由 deletePackageX 反向验证依赖。然后回答:为什么 static library 不能自动使用最新版本?为什么低版本安装也要找现有版本做签名基准?为什么 prune 候选仍可能删除失败?