Skip to content

静态共享库

追踪 Android 17 静态共享库的 synthetic package name、版本索引、签名校验、依赖收集和清理边界。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描静态共享库SharedLibraryInfoSharedLibrariesImpl源码阅读

静态共享库 ​

本文面向已经读过 ScanRequest 与 Result 和 扫描签名校验 的读者,继续追踪 static shared library 从 Manifest 声明到 PMS 全局库表的路径。前文解释了 ScanResult 如何携带 SharedLibraryInfo,本文进一步回答:为什么同一个库可以同时存在多个版本,PMS 如何用 synthetic package name 保存它们,依赖方如何按版本和证书查找,以及卸载一个库版本时谁负责保护依赖方。

Android 17 中,静态共享库不是普通 APK 加上一个 TYPE_STATIC 常量。它同时受到包名重写、安装位置、target SDK、组件声明、shared user、签名、版本顺序和依赖传播约束。扫描阶段生成的库对象只是候选结果;SharedLibrariesImpl 还要在 reconcile/commit 阶段决定它能否加入全局索引,以及哪些依赖方需要重新计算 library files。

读完后,读者应能从 InstallPackageHelper 找到 static library 的 synthetic name 和早期签名检查;从 ScanPackageUtils.assertStaticSharedLibraryIsValid() 解释每条 Manifest 限制;从 ScanPackageUtils 追到 ScanResult.mStaticSharedLibraryInfo;从 SharedLibrariesImpl 看懂 name→version→SharedLibraryInfo 两级索引、签名基线选择、依赖收集、commit 和 prune;还能区分“声明库成功扫描”“库进入全局表”和“依赖方 classpath 已更新”这三个状态。

1. 对象边界 ​

1.1 提供方与使用方 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
  • frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
  • frameworks/base/core/java/android/content/pm/SharedLibraryInfo.java
对象角色关键字段/索引主要消费者
声明库的 ParsedPackageprovider APK 的解析结果name、version、Manifest 限制scan policy、库对象创建
synthetic PackageSettingPMS 持久化包状态synthetic package name、path、signing details签名、版本和安装状态
SharedLibraryInfo库的运行时描述library name、version、declaring package、code pathsSharedLibrariesImpl、依赖方
mSharedLibraries全局库索引library name → version map精确版本查找
mStaticLibsByDeclaringPackageprovider 反向索引declaring package → version map更新/删除 provider
PackageState.pkgState使用方依赖缓存uses library infos/filesclassloader 路径、重新计算

静态共享库的“库名”与“包名”有意分离:Manifest 中的 static-library android:name 是供依赖方声明的逻辑名称;PMS 内部包名加入版本后可同时保存多个版本。SharedLibraryInfo.getDeclaringPackage() 则指向 synthetic package 的版本化 provider。

1.2 三个阶段 ​

静态库的一次安装/启动可以拆成三个阶段:

  1. 包扫描:解析 Manifest、重写 synthetic package name、创建/复制 PackageSetting、生成 SharedLibraryInfo。
  2. 一致性检查:检查同名库版本、provider 包名冲突、签名、KeySet、依赖版本和证书 digest。
  3. 提交与传播:把库写入全局索引,重建 provider/使用方依赖,必要时删除或更新受影响包。

第 1 阶段成功不代表第 2 阶段成功;第 2 阶段成功也不代表使用方已经拿到新的 usesLibraryFiles。这也是为什么本文把 ScanPackageUtils 与 SharedLibrariesImpl 放在同一条主线中,而不是只讲 SharedLibraryInfo 的字段。

2. synthetic 包名 ​

2.1 重写入口 ​

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

java
if (parsedPackage.isStaticSharedLibrary()) {
    // Static shared libraries have synthetic package names
    PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);

    // No static shared libs on external storage
    if (onExternal) {
        Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
        throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                "Static shared libs can only be installed on internal storage.");
    }
}

安装准备阶段先重写包名,再检查 external storage。顺序很重要:之后的 request.setName(pkgName)、Settings 查询和库索引都使用 synthetic name。外部存储直接失败,错误码是 INSTALL_FAILED_INVALID_INSTALL_LOCATION,不会先把库写入全局表再回滚。

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

java
// TODO:(b/135203078): Move to parsing
static void renameStaticSharedLibraryPackage(ParsedPackage parsedPackage) {
    // Derive the new package synthetic package name
    parsedPackage.setPackageName(toStaticSharedLibraryPackageName(
            parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibraryVersion()));
}

private static String toStaticSharedLibraryPackageName(
        String packageName, long libraryVersion) {
    return packageName + STATIC_SHARED_LIB_DELIMITER + libraryVersion;
}

synthetic name 由原始 Manifest package name 和 static library version 拼成。逻辑库名没有直接拼进包名,因此同一个 provider package 可以声明多个版本时,版本号是 PMS 区分设置和路径的关键。

2.2 冲突检查 ​

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

java
// To prevent a new package from being installed if its package name is
// already in use by an existing static library on the system.
final WatchedLongSparseArray<SharedLibraryInfo> staticLibInfos =
        mSharedLibraries.getStaticLibraryInfos(parsedPackage.getPackageName());
if (staticLibInfos != null && staticLibInfos.size() > 0) {
    SharedLibraryInfo anyStaticLib = staticLibInfos.valueAt(0);
    if (!parsedPackage.isStaticSharedLibrary()) {
        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE,
                "The package name is same as an existing static library");
    }
    if (!anyStaticLib.getName().equals(parsedPackage.getStaticSharedLibraryName())) {
        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE,
                "The static library being installed has a different name than existing"
                        + " static library with the same package name");
    }
}

provider 原始包名已经被某个 static library 版本占用时,普通 APK 不能再使用这个包名;新的 static library 也必须保持相同逻辑库名。版本不同不是冲突,因为索引按 version 保存;逻辑库名不同才是 INSTALL_FAILED_DUPLICATE_PACKAGE。

3. 扫描约束 ​

3.1 基本限制 ​

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

java
public static void assertStaticSharedLibraryIsValid(AndroidPackage pkg,
        @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
    // Static shared libraries should have at least O target SDK
    if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
        throw PackageManagerException.ofInternalError(
                "Packages declaring static-shared libs must target O SDK or higher",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_LOW_SDK);
    }

    // Package declaring a static a shared lib cannot be instant apps
    if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
        throw PackageManagerException.ofInternalError(
                "Packages declaring static-shared libs cannot be instant apps",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_INSTANT);
    }

    // Package declaring static a shared lib cannot be renamed
    if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
        throw PackageManagerException.ofInternalError(
                "Packages declaring static-shared libs cannot be renamed",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_RENAMED);
    }

    // Package declaring static a shared lib cannot declare dynamic libs
    if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
        throw PackageManagerException.ofInternalError(
                "Packages declaring static-shared libs cannot declare dynamic libs",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_DYNAMIC);
    }

    // Package declaring static a shared lib cannot declare shared users
    if (pkg.getSharedUserId() != null) {
        throw PackageManagerException.ofInternalError(
                "Packages declaring static-shared libs cannot declare shared users",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_SHARED_USER);
    }

静态库 provider 必须 target O 及以上,不能是 instant app、不能使用 original package rename、不能同时声明 dynamic library,也不能加入 shared user。这里的限制发生在扫描 policy,不是依赖方查找时才检查。

3.2 组件限制 ​

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

java
    // Static shared libs cannot declare activities
    if (!pkg.getActivities().isEmpty()) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot declare activities",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_ACTIVITY);
    }
    // Static shared libs cannot declare services
    if (!pkg.getServices().isEmpty()) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot declare services",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_SERVICE);
    }
    // Static shared libs cannot declare providers
    if (!pkg.getProviders().isEmpty()) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot declare content providers",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_CONTENT_PROVIDER);
    }
    // Static shared libs cannot declare receivers
    if (!pkg.getReceivers().isEmpty()) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot declare broadcast receivers",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_BROADCAST_RECEIVER);
    }

    // Static shared libs cannot declare permissions
    if (!pkg.getPermissions().isEmpty()) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot declare permissions",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_PERMISSION);
    }

    // Static shared libs cannot be overlay targets
    if (pkg.getOverlayTarget() != null) {
        throw PackageManagerException.ofInternalError(
                "Static shared libs cannot be overlay targets",
                PackageManagerException.INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS);
    }
}

provider 不能声明 Activity、Service、Provider、Receiver、permission,也不能成为 overlay target。其余限制还包括 permission group、protected broadcast、feature/attribution 等,源码为每类限制定义独立 internal error 常量。静态库是 classpath provider,不是可启动的应用组件集合。

3.3 扫描结果 ​

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

java
SharedLibraryInfo staticSharedLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibraryName())) {
    staticSharedLibraryInfo =
            AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
}

return new ScanResult(request, pkgSetting, !createNewPackage,
        Process.INVALID_UID /* previousAppId */, sdkLibraryInfo, staticSharedLibraryInfo,
        dynamicSharedLibraryInfos);

扫描方法只在 Manifest static library name 非空时创建 SharedLibraryInfo。在当前 Android 17 中,ScanResult 仍把 previousAppId 固定成 Process.INVALID_UID;静态库的版本管理不依赖这个 AppId 迁移字段,而依赖 synthetic package name 和 shared library version。

4. 版本索引 ​

4.1 两级容器 ​

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

java
@GuardedBy("mPm.mLock")
void commitSharedLibraryInfoLPw(@NonNull SharedLibraryInfo libraryInfo) {
    final String name = libraryInfo.getName();
    WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
    if (versionedLib == null) {
        versionedLib = new WatchedLongSparseArray<>();
        mSharedLibraries.put(name, versionedLib);
    }
    versionedLib.put(libraryInfo.getLongVersion(), libraryInfo);

    if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) {
        final String declaringPackageName = libraryInfo.getDeclaringPackage().getPackageName();
        WatchedLongSparseArray<SharedLibraryInfo> staticLibInfos =
                mStaticLibsByDeclaringPackage.get(declaringPackageName);
        if (staticLibInfos == null) {
            staticLibInfos = new WatchedLongSparseArray<>();
            mStaticLibsByDeclaringPackage.put(declaringPackageName, staticLibInfos);
        }
        staticLibInfos.put(libraryInfo.getLongVersion(), libraryInfo);
    }
}

主索引是 libraryName -> version -> SharedLibraryInfo;静态库还按 declaring synthetic package name 建立反向索引。所有写入受 mPm.mLock 保护,版本 key 可以让多个版本共存,反向索引则让 provider 更新/删除不必扫描所有库名。

4.2 签名基线版本 ​

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

java
@Nullable
PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull InstallRequest installRequest) {
    final ParsedPackage parsedPackage = installRequest.getParsedPackage();
    if (parsedPackage == null || !parsedPackage.isStaticSharedLibrary()) {
        return null;
    }
    PackageSetting sharedLibPackage = null;
    synchronized (mPm.mLock) {
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                parsedPackage.getStaticSharedLibraryName());
        if (versionedLib != null) {
            long previousLibVersion = -1;
            for (int i = 0; i < versionedLib.size(); i++) {
                final long libVersion = versionedLib.keyAt(i);
                if (libVersion < parsedPackage.getStaticSharedLibraryVersion()) {
                    previousLibVersion = Math.max(previousLibVersion, libVersion);
                }
            }
            if (previousLibVersion >= 0) {
                final SharedLibraryInfo previous = versionedLib.get(previousLibVersion);
                sharedLibPackage = mPm.mSettings.getPackageLPr(
                        previous.getPackageName());
            }
        }

        if (sharedLibPackage == null) {
            // Fall back to any existing version for signature consistency.
            final WatchedLongSparseArray<SharedLibraryInfo> staticLibInfos =
                    getStaticLibraryInfos(parsedPackage.getPackageName());
            if (staticLibInfos != null && staticLibInfos.size() > 0) {
                sharedLibPackage = mPm.mSettings.getPackageLPr(
                        staticLibInfos.valueAt(0).getPackageName());
            }
        }
    }
    return sharedLibPackage;
}

安装新版本时,签名基线优先选择“低于新版本的最高已安装版本”。例如已有 1、3,正在安装 4,则比较版本 3;若正在安装 0,找不到更低版本,就 fallback 到任意已存在版本,以保证同名库所有版本的签名一致。这个方法返回的是 provider 的 PackageSetting,不是 SharedLibraryInfo 本身。

4.3 依赖查找 ​

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

java
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
        @NonNull List<String> requestedLibraries,
        @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
        @Nullable boolean[] libsOptional, @NonNull String packageName,
        @NonNull String libraryType, boolean required, int targetSdk,
        @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
        @NonNull final Map<String, AndroidPackage> availablePackages,
        @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
        @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
        throws PackageManagerException {
    for (int i = 0; i < requestedLibraries.size(); i++) {
        final String libName = requestedLibraries.get(i);
        final long libVersion = requiredVersions != null
                ? requiredVersions[i] : SharedLibraryInfo.VERSION_UNDEFINED;
        final SharedLibraryInfo libraryInfo;
        synchronized (mPm.mLock) {
            libraryInfo = SharedLibraryUtils.getSharedLibraryInfo(
                    libName, libVersion, mSharedLibraries, newLibraries);
        }
        if (libraryInfo == null) {
            if (required) {
                throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                        "Package " + packageName + " requires unavailable " + libraryType
                                + " library " + libName + "; failing!");
            }
            continue;
        }
        if (requiredVersions != null && requiredCertDigests != null) {
            if (libraryInfo.getLongVersion() != requiredVersions[i]) {
                throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                        "Package " + packageName + " requires unavailable " + libraryType
                                + " library " + libName + " version "
                                + libraryInfo.getLongVersion() + "; failing!");
            }
        }
        if (outUsedLibraries != null) {
            outUsedLibraries.add(libraryInfo);
        }
    }
    return outUsedLibraries;
}

使用方声明 static library 时,requiredVersions 指定精确版本;版本不匹配和库不存在都属于 INSTALL_FAILED_MISSING_SHARED_LIBRARY。可选依赖可以继续安装,但 static library 通常作为 required 依赖处理。newLibraries 允许同一批安装请求中先看到尚未 commit 的新库,避免必须等待全局表写入后才能解析依赖。

5. 提交与传播 ​

5.1 允许进入全局表 ​

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

java
List<SharedLibraryInfo> getAllowedSharedLibInfos(InstallRequest installRequest) {
    // Use the parsed package as scanResult.pkgSetting may be null
    final ParsedPackage parsedPackage = installRequest.getParsedPackage();
    if (installRequest.getSdkSharedLibraryInfo() == null
            && installRequest.getStaticSharedLibraryInfo() == null
            && installRequest.getDynamicSharedLibraryInfos() == null) {
        return null;
    }

    // Any app can add new SDKs and static shared libraries.
    if (installRequest.getSdkSharedLibraryInfo() != null) {
        return Collections.singletonList(installRequest.getSdkSharedLibraryInfo());
    }
    if (installRequest.getStaticSharedLibraryInfo() != null) {
        return Collections.singletonList(installRequest.getStaticSharedLibraryInfo());
    }

    boolean isSystemApp = installRequest.getScannedPackageSetting() != null
            && installRequest.getScannedPackageSetting().isSystem();
    final boolean hasDynamicLibraries = parsedPackage != null && isSystemApp
            && installRequest.getDynamicSharedLibraryInfos() != null;
    if (!hasDynamicLibraries) {
        return null;
    }
    return installRequest.getDynamicSharedLibraryInfos();
}

静态库和 SDK library 的 SharedLibraryInfo 可以直接作为允许列表返回;普通动态库则要额外受 system app 条件和已有库冲突规则约束。静态库因此比动态库拥有更明确的 provider 身份和版本索引。

5.2 commit 及使用方 ​

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

java
ArrayList<AndroidPackage> commitSharedLibraryChanges(@NonNull AndroidPackage pkg,
        @NonNull PackageSetting pkgSetting,
        List<SharedLibraryInfo> allowedSharedLibraryInfos,
        @NonNull Map<String, AndroidPackage> combinedSigningDetails, int scanFlags) {
    if (ArrayUtils.isEmpty(allowedSharedLibraryInfos)) {
        return null;
    }
    synchronized (mPm.mLock) {
        for (SharedLibraryInfo info : allowedSharedLibraryInfos) {
            commitSharedLibraryInfoLPw(info);
        }
        try {
            updateSharedLibraries(pkg, pkgSetting, null, null, combinedSigningDetails);
        } catch (PackageManagerException e) {
            Slog.e(TAG, "updateSharedLibraries failed: ", e);
        }
        // Update all applications that use this library. Skip when booting.
        if ((scanFlags & SCAN_BOOTING) == 0) {
            return updateAllSharedLibrariesLPw(pkg, pkgSetting, combinedSigningDetails);
        }
    }
    return null;
}

commit 先写全局库表,再重建当前 provider 的 uses-library 状态;非 booting 场景还会立即更新所有依赖方。booting 时跳过逐包传播,启动流程会在所有包扫描完成后统一处理,避免依赖方在 provider 尚未全部出现时反复重算。

5.3 code paths ​

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

java
@GuardedBy("mPm.mLock")
private void addSharedLibraryLPr(@NonNull AndroidPackage pkg,
        @NonNull Set<String> usesLibraryFiles, @NonNull SharedLibraryInfo libInfo,
        @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting) {
    if (libInfo.getPath() != null) {
        usesLibraryFiles.add(libInfo.getPath());
        return;
    }
    AndroidPackage pkgForCodePaths = mPm.mPackages.get(libInfo.getPackageName());
    PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(libInfo.getPackageName());
    if (changingLib != null && changingLib.getPackageName().equals(libInfo.getPackageName())) {
        // Use the new library while its commit is still in progress.
        if (pkgForCodePaths == null
                || pkgForCodePaths.getPackageName().equals(changingLib.getPackageName())) {
            pkgForCodePaths = changingLib;
            pkgSetting = changingLibSetting;
        }
    }
    if (pkgForCodePaths != null) {
        usesLibraryFiles.addAll(AndroidPackageUtils.getAllCodePaths(pkgForCodePaths));
        applyDefiningSharedLibraryUpdateLPr(pkg, libInfo, SharedLibraryInfo::addDependency);
        if (pkgSetting != null) {
            usesLibraryFiles.addAll(pkgSetting.getPkgState().getUsesLibraryFiles());
        }
    }
}

使用方最终需要的是 provider base/split 的 code paths,不是 library name。若 provider 正在更新但尚未写入 mPm.mPackages,changingLib/changingLibSetting 让依赖方临时使用新包;否则从现有包表和 Settings 取得路径。库依赖还会反向写入 provider 的 dependency 列表。

6. 清理与恢复 ​

6.1 未使用版本 ​

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

java
boolean pruneUnusedStaticSharedLibraries(@NonNull Computer computer, long neededSpace,
        long maxCachePeriod) throws IOException {
    final StorageManager storage = mInjector.getSystemService(StorageManager.class);
    final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
    final ArrayList<VersionedPackage> packagesToDelete = new ArrayList<>();
    final long now = System.currentTimeMillis();

    final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibraries =
            computer.getSharedLibraries();
    for (int i = 0; i < sharedLibraries.size(); i++) {
        final WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
                sharedLibraries.valueAt(i);
        if (versionedLib == null) {
            continue;
        }
        for (int j = 0; j < versionedLib.size(); j++) {
            SharedLibraryInfo libInfo = versionedLib.valueAt(j);
            // Unused and old versions are added to packagesToDelete.
        }
    }

    for (VersionedPackage pkgToDelete : packagesToDelete) {
        if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(),
                pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM,
                PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/,
                Process.SYSTEM_UID) == PackageManager.DELETE_SUCCEEDED) {
            if (volume.getUsableSpace() >= neededSpace) {
                return true;
            }
        }
    }
    return false;
}

源码会跳过仍被任意 user 使用的 shared library,再按缓存时间和空间需求选择删除对象;删除使用 synthetic package name + version。这个清理入口不直接删除任意 APK 文件,而是走包删除流程,让依赖检查和 Settings 清理继续生效。

6.2 删除 provider ​

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

java
boolean removeSharedLibrary(@NonNull String libName, long version) {
    synchronized (mPm.mLock) {
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
        if (versionedLib == null) {
            return false;
        }
        final int libIdx = versionedLib.indexOfKey(version);
        if (libIdx < 0) {
            return false;
        }
        SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
        final Computer snapshot = mPm.snapshotComputer();
        for (int currentUserId : mPm.mUserManager.getUserIds()) {
            var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(
                    libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
            final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
            if (dependents == null) {
                continue;
            }
            // Dependent packages are handled before the index entry is removed.
        }
        versionedLib.remove(version);
        return true;
    }
}

删除前会按 user 查询依赖方;版本索引移除不是第一步。若依赖方仍使用该版本,包删除流程必须先处理依赖关系,否则 classpath 或 Settings 会留下悬空引用。静态库的 provider synthetic 包名和逻辑库名必须同时从正向/反向索引中清理。

6.3 测试边界 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

java
@Test
public void installStaticSharedLibrary() throws Exception {
    final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
            .setStaticSharedLibraryName("static.lib")
            .setStaticSharedLibraryVersion(123L)
            .hideAsParsed())
            .setPackageName("static.lib.pkg.123")
            .setVersionCodeMajor(1)
            .setVersionCode(234)
            .setBaseApkPath("/some/path.apk")
            .setSplitCodePaths(new String[] {"/some/other/path.apk"});

    final ScanResult scanResult = executeScan(
            new ScanRequestBuilder(pkg).setUser(UserHandle.of(0)).build());

    assertThat(scanResult.mStaticSharedLibraryInfo.getPackageName(),
            is("static.lib.pkg.123"));
    assertThat(scanResult.mStaticSharedLibraryInfo.getName(), is("static.lib"));
    assertThat(scanResult.mStaticSharedLibraryInfo.getLongVersion(), is(123L));
    assertThat(scanResult.mStaticSharedLibraryInfo.getType(), is(TYPE_STATIC));
    assertThat(scanResult.mStaticSharedLibraryInfo.getDeclaringPackage().getPackageName(),
            is("static.lib.pkg"));
    assertThat(scanResult.mStaticSharedLibraryInfo.getAllCodePaths(),
            hasItems("/some/path.apk", "/some/other/path.apk"));
    assertThat(scanResult.mStaticSharedLibraryInfo.getDependencies(), nullValue());
}

这个测试的 arrange 是 synthetic package name、逻辑库名、版本和 base/split paths;action 是直接扫描;assert 覆盖 provider package name、library name、版本、类型、declaring package 和 code paths。它证明 ScanPackageUtils/AndroidPackageUtils 生成的结果形态,不证明库已经进入 mSharedLibraries 或依赖方已经更新。

6.4 设备诊断 ​

text
adb shell dumpsys package <synthetic-package-name>
adb shell dumpsys package libraries
adb shell logcat -s PackageManager SharedLibrariesImpl

诊断一个静态库版本时,按以下顺序对照:

  1. Manifest 逻辑库名与版本是否正确;
  2. PMS package name 是否已经变成 synthetic name;
  3. SharedLibraryInfo 的 declaring package、版本和 code paths 是否一致;
  4. mSharedLibraries 是否存在该 name/version;
  5. 使用方的 uses-static-library 版本与 cert digest 是否匹配;
  6. 使用方 usesLibraryFiles 是否包含 provider 的 base/split code paths;
  7. 删除/更新后是否仍有 user 依赖和旧 synthetic setting。

7. 源码路线 ​

定位静态共享库问题时,可以沿下面的路径阅读:

  1. InstallPackageHelper 的 static library 分支:确认 synthetic package name、external storage 和普通包名冲突。
  2. ScanPackageUtils.assertStaticSharedLibraryIsValid():逐项核对 target SDK、组件、权限、shared user、rename 和 overlay target 限制。
  3. ScanPackageUtils.scanPackageOnly():确认 staticSharedLibraryInfo 的创建以及结果设置。
  4. ReconcilePackageUtils:确认 static provider 的签名基线、KeySet 和 ReconcileFailure。
  5. SharedLibrariesImpl.getStaticSharedLibLatestVersionSetting():确认版本比较对象和 fallback。
  6. SharedLibrariesImpl.getAllowedSharedLibInfos()/commitSharedLibraryInfoLPw():确认库何时进入全局索引。
  7. collectSharedLibraryInfos():确认使用方精确版本、证书 digest、required/optional 分支。
  8. updateSharedLibraries()/updateAllSharedLibrariesLPw():确认 code paths、依赖传播和删除边界。

一个实用练习是:系统已有 lib.foo 版本 1 和 3,正在扫描版本 4;另一个 APK 声明使用版本 3,但 cert digest 不匹配。请分别判断 provider 的 synthetic package name、签名基线会选择哪个版本、使用方在哪个函数失败、错误码是什么,以及版本 4 在失败后是否会进入全局索引。

8. 设计收束 ​

Android 17 的静态共享库扫描可以归纳为:

  • 安装准备把 provider 包名改成包含版本的 synthetic name,允许同一逻辑库多版本共存,并禁止 external storage。
  • 扫描 policy 强制 O+ target SDK,禁止组件、权限、shared user、重命名、dynamic library 和 overlay target。
  • ScanResult 只携带 TYPE_STATIC 的候选 SharedLibraryInfo;reconcile 仍要用已有版本和签名/KeySet 验证它。
  • SharedLibrariesImpl 用 name→version 正向索引和 declaring package 反向索引保存库;commit 后才成为全局可查对象。
  • 使用方按精确版本和 cert digest 查找库,随后把 provider 的 base/split code paths 写入 usesLibraryFiles;booting 时批量延迟传播。
  • 删除和空间回收必须先考虑 user 依赖,再移除 synthetic setting、版本索引和相关 code paths。
  • 测试可以证明扫描结果字段和版本形态,不能单独证明全局注册、依赖 classpath 或删除完成;这些必须继续追 commit 和运行时状态。

后续专题会进入扫描冲突处理,分析 system、data、APEX 和多个 provider 版本同时出现时,PMS 如何选择保留对象、隐藏旧包并清理不再有效的状态。