Static Shared Library
本文承接 SharedLibrary 管理,只讨论 Static Shared Library。它模拟“客户端绑定某个固定版本”的静态链接语义,但代码仍以独立包存在并可被多个客户端复用。文章沿安装解析、版本/证书匹配、内部包名、依赖用户同步和 prune 删除走完整调用链。
1. 精确版本语义
源码文件:frameworks/base/core/java/android/content/pm/SharedLibraryInfo.java,符号:TYPE_STATIC
/**
* 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
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
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
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
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 循环
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
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,从而支持签名轮换能力。
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
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
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
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 只筛选缓存时间足够长、非系统的版本作为候选。候选并不等于可以删除。
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
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 library | name/version 精确索引、依赖安装器 |
| 报 bad certificate digest | manifest 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 候选仍可能删除失败?
