静态共享库
本文面向已经读过 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.javaframeworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.javaframeworks/base/core/java/android/content/pm/SharedLibraryInfo.java
| 对象 | 角色 | 关键字段/索引 | 主要消费者 |
|---|---|---|---|
声明库的 ParsedPackage | provider APK 的解析结果 | name、version、Manifest 限制 | scan policy、库对象创建 |
synthetic PackageSetting | PMS 持久化包状态 | synthetic package name、path、signing details | 签名、版本和安装状态 |
SharedLibraryInfo | 库的运行时描述 | library name、version、declaring package、code paths | SharedLibrariesImpl、依赖方 |
mSharedLibraries | 全局库索引 | library name → version map | 精确版本查找 |
mStaticLibsByDeclaringPackage | provider 反向索引 | declaring package → version map | 更新/删除 provider |
PackageState.pkgState | 使用方依赖缓存 | uses library infos/files | classloader 路径、重新计算 |
静态共享库的“库名”与“包名”有意分离:Manifest 中的 static-library android:name 是供依赖方声明的逻辑名称;PMS 内部包名加入版本后可同时保存多个版本。SharedLibraryInfo.getDeclaringPackage() 则指向 synthetic package 的版本化 provider。
1.2 三个阶段
静态库的一次安装/启动可以拆成三个阶段:
- 包扫描:解析 Manifest、重写 synthetic package name、创建/复制
PackageSetting、生成SharedLibraryInfo。 - 一致性检查:检查同名库版本、provider 包名冲突、签名、KeySet、依赖版本和证书 digest。
- 提交与传播:把库写入全局索引,重建 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
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
// 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
// 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
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
// 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
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
@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
@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
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
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
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
@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
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
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
@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 设备诊断
adb shell dumpsys package <synthetic-package-name>
adb shell dumpsys package libraries
adb shell logcat -s PackageManager SharedLibrariesImpl诊断一个静态库版本时,按以下顺序对照:
- Manifest 逻辑库名与版本是否正确;
- PMS package name 是否已经变成 synthetic name;
SharedLibraryInfo的 declaring package、版本和 code paths 是否一致;mSharedLibraries是否存在该 name/version;- 使用方的
uses-static-library版本与 cert digest 是否匹配; - 使用方
usesLibraryFiles是否包含 provider 的 base/split code paths; - 删除/更新后是否仍有 user 依赖和旧 synthetic setting。
7. 源码路线
定位静态共享库问题时,可以沿下面的路径阅读:
InstallPackageHelper的 static library 分支:确认 synthetic package name、external storage 和普通包名冲突。ScanPackageUtils.assertStaticSharedLibraryIsValid():逐项核对 target SDK、组件、权限、shared user、rename 和 overlay target 限制。ScanPackageUtils.scanPackageOnly():确认staticSharedLibraryInfo的创建以及结果设置。ReconcilePackageUtils:确认 static provider 的签名基线、KeySet 和ReconcileFailure。SharedLibrariesImpl.getStaticSharedLibLatestVersionSetting():确认版本比较对象和 fallback。SharedLibrariesImpl.getAllowedSharedLibInfos()/commitSharedLibraryInfoLPw():确认库何时进入全局索引。collectSharedLibraryInfos():确认使用方精确版本、证书 digest、required/optional 分支。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 如何选择保留对象、隐藏旧包并清理不再有效的状态。
