Skip to content

SharedLibrary 管理

追踪 Android 17 SharedLibrariesImpl 的版本索引、依赖收集、安装更新和删除影响。

基于android-17.0.0_r1
AndroidPMSSharedLibraryPackageManager

SharedLibrary 管理 ​

本文承接 PackageManager 实践,聚焦 PMS 如何管理共享库依赖。重点不是罗列 <uses-library> 语法,而是解释 SharedLibraryInfo 的四种类型、版本选择、依赖路径如何写回 PackageStateUnserialized,以及删除/更新共享库时哪些依赖包会被影响。

1. 共享库的四种类型 ​

源码文件:frameworks/base/core/java/android/content/pm/SharedLibraryInfo.java,符号:类型常量

java
public static final int TYPE_BUILTIN = 0;
public static final int TYPE_DYNAMIC = 1;
public static final int TYPE_STATIC = 2;
public static final int TYPE_SDK_PACKAGE = 3;
public static final int VERSION_UNDEFINED = -1;

Builtin 是系统不可更新库;dynamic 是向后兼容并使用最新版本的动态库;static 是按指定版本链接的库;SDK package 是不向后兼容、按版本依赖的 SDK 包库。类型决定版本查找、删除策略和客户端依赖是否能继续运行。

2. LibraryInfo 数据 ​

源码文件:frameworks/base/core/java/android/content/pm/SharedLibraryInfo.java,符号:字段与依赖方法

java
private final String mPath;
private final String mPackageName;
private final String mName;
private List<String> mCodePaths;
private final long mVersion;
private final @Type int mType;
private final boolean mIsNative;
private final VersionedPackage mDeclaringPackage;
private final List<VersionedPackage> mDependentPackages;
private List<SharedLibraryInfo> mDependencies;

public void addDependency(
        @Nullable SharedLibraryInfo info) {
    if (info == null) return;
    if (mDependencies == null) {
        mDependencies = new ArrayList<>();
    }
    mDependencies.add(info);
}

库记录同时保存代码路径、声明包、版本、native 标志、依赖包和自身依赖。mDependencies 是库依赖图的一条边;它在重新计算时会先清空再建立,避免沿用旧版本关系。

3. 版本索引 ​

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

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

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

第一张表按 library name 索引所有版本;第二张表按声明包名索引 static library 版本,解决多个 static library 共享同一个 declaring package name 时的冲突。两张表都可观察并支持 snapshot。

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

java
@GuardedBy("mPm.mLock")
public SharedLibraryInfo getSharedLibraryInfo(
        @NonNull String libName, long version) {
    WatchedLongSparseArray<SharedLibraryInfo> versions =
            mSharedLibraries.get(libName);
    if (versions == null) {
        return null;
    }
    return versions.get(version);
}

调用方必须在 PMS lock 下访问 live shared library map;并发查询使用 SharedLibrariesRead snapshot。库名存在但版本不存在时返回 null,后续安装解析会把它当作未满足依赖。

4. 依赖收集 ​

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

java
void updateSharedLibraries(
        @NonNull AndroidPackage pkg,
        @NonNull PackageSetting pkgSetting,
        @Nullable AndroidPackage changingLib,
        @Nullable PackageSetting changingLibSetting,
        @NonNull Map<String, AndroidPackage> availablePackages)
        throws PackageManagerException {
    ArrayList<SharedLibraryInfo> infos =
            collectSharedLibraryInfos(
                    pkg, availablePackages,
                    null /* newLibraries */);
    synchronized (mPm.mLock) {
        executeSharedLibrariesUpdateLPw(
                pkg, pkgSetting, changingLib,
                changingLibSetting, infos,
                mPm.mUserManager.getUserIds());
    }
}

依赖收集可以看到正在更新的 library package(changingLib),因此新库尚未提交到 PMS map 时也能计算依赖。收集完成后才在 mPm.mLock 下更新 package state。

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

java
applyDefiningSharedLibraryUpdateLPr(
        pkg, null, (definingLibrary, dependency) ->
                definingLibrary.clearDependencies());

pkgSetting.getPkgState().setUsesLibraryInfos(infos);
Set<String> usesLibraryFiles =
        new LinkedHashSet<>();
for (SharedLibraryInfo info : infos) {
    addSharedLibraryLPr(pkg, usesLibraryFiles,
            info, changingLib, changingLibSetting);
}
pkgSetting.setPkgStateLibraryFiles(
        usesLibraryFiles);

更新时先清空由目标包提供的旧依赖,再写入新 SharedLibraryInfo 列表和去重后的 code paths。LinkedHashSet 保留添加顺序并消除重复路径,供 classloader/代码路径消费者使用。

5. Static 用户状态 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java,符号:static library 安装状态

java
int[] installedUsers = new int[allUsers.length];
int count = 0;
for (int userId : allUsers) {
    if (pkgSetting.getInstalled(userId)) {
        installedUsers[count++] = userId;
    }
}

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

静态共享库会被标记为与依赖包相同的 installed users。这样依赖包在某用户已安装时,static library 的 per-user state 也能满足解析;它不是把 static library 代码复制到依赖包目录。

6. builtin 库注册 ​

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

java
if (getSharedLibraryInfo(entry.name,
        SharedLibraryInfo.VERSION_UNDEFINED) != null) {
    return;
}

SharedLibraryInfo info = new SharedLibraryInfo(
        entry.filename, null, null, entry.name,
        SharedLibraryInfo.VERSION_UNDEFINED,
        SharedLibraryInfo.TYPE_BUILTIN,
        new VersionedPackage(
                PLATFORM_PACKAGE_NAME, 0L),
        null, null, entry.isNative);
commitSharedLibraryInfoLPw(info);

builtin 库由 SystemConfig 注册,声明包是平台包,版本使用 VERSION_UNDEFINED。重复名称直接跳过,避免启动时覆盖已有 builtin 定义。

7. 删除影响 ​

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

java
boolean removeSharedLibrary(
        @NonNull String libName, long version) {
    synchronized (mPm.mLock) {
        WatchedLongSparseArray<SharedLibraryInfo> versions =
                mSharedLibraries.get(libName);
        if (versions == null
                || versions.indexOfKey(version) < 0) {
            return false;
        }
        SharedLibraryInfo info =
                versions.valueAt(versions.indexOfKey(version));
        Computer snapshot = mPm.snapshotComputer();
        for (int userId : mPm.mUserManager.getUserIds()) {
            Pair<List<VersionedPackage>, ?> pair =
                    snapshot.getPackagesUsingSharedLibrary(
                            info, 0, Process.SYSTEM_UID,
                            userId);
            if (pair.first == null) continue;
            for (VersionedPackage dependent : pair.first) {
                PackageSetting ps = mPm.mSettings
                        .getPackageLPr(
                                dependent.getPackageName());
                if (ps != null) {
                    ps.setOverlayPathsForLibrary(
                            info.getName(), null, userId);
                }
            }
        }

删除库前,PMS 按 user 查询依赖包并清理 shared-library overlay paths。真正能否删除还取决于库类型、版本和后续 package deletion/reconcile;删除一个仍被强依赖的库不能简单当作文件删除。

8. 版本与依赖选择 ​

动态库客户端使用 VERSION_UNDEFINED,由 PMS 指向可用动态版本;static/SDK package 依赖在 manifest 中携带明确版本。版本不兼容或 required dependency 缺失时,安装流程拒绝包;optional dependency 缺失则允许继续,但 SharedLibraryInfo 的 optional dependent 语义会保留。

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

java
if (libInfo.isStatic()) {
    String internalName = computer
            .resolveInternalPackageName(
                    declaringPackage.getPackageName(),
                    declaringPackage.getLongVersionCode());
    return computer.getPackageStateInternal(
            internalName);
}
if (libInfo.isSdk()) {
    return computer.getPackageStateInternal(
            declaringPackage.getPackageName());
}

static library 可能使用 synthetic internal package name,SDK package 则直接按 declaring package 查询。两种类型的版本/包定位不能用同一个字符串规则替代。

9. Snapshot 一致性 ​

SharedLibrariesImpl 实现 Watchable/Snappable,其 map 和 SharedLibraryInfo 变化会使 PMS Computer snapshot 失效。安装线程在 mPm.mLock 下提交依赖状态,查询线程读取旧 snapshot 时仍可完成一致的旧视图;下一次查询再看到新依赖。

10. 失败定位 ​

现象检查点
required library 缺失collectSharedLibraryInfos、name/version 索引
依赖包加载旧路径pkgState.usesLibraryFiles、changingLib 分支
static library 用户不可见依赖包 installed users 与 static library user state
删除库后应用异常dependent packages、overlay paths、reconcile
同名库版本冲突mSharedLibraries 与 mStaticLibsByDeclaringPackage
查询读到旧依赖snapshot version/invalidation 是否完成

11. 阅读检查 ​

复述这条链:解析包声明 → collectSharedLibraryInfos 选择版本 → executeSharedLibrariesUpdateLPw 重建依赖和 code paths → 写入 package state → 查询/类加载器消费;删除时反向找依赖包并清理关联状态。然后回答:static library 为什么需要第二张索引表?依赖库更新时为什么要传 changingLib?删除 shared library 是否只需删除一个 APK?