data-app扫描
/data/app 扫描负责把用户安装的 APK、保留数据的已卸载包和覆盖系统包的更新版本重新接回 PMS 内存状态。它和 system partition 扫描有一个根本差异:系统分区文件是设备镜像的一部分,而 /data/app 内容可能来自安装、更新、卸载保留数据、OTA 中断或文件损坏,因此 PMS 不能看到一个 APK 就无条件注册。
Android 17 的 data/app 主线是:
- 根据首次启动/OTA 语境修复 app 目录权限;
- 以
SCAN_REQUIRE_KNOWN扫描 app install dir; - 解析结果在
processParseResult()中进入addForInitLI(); - 通过 Settings 中的 active/disabled package 校验路径、版本、签名和 system update 关系;
- 失败的普通 data package 删除 code path,系统 package 则交给恢复/cleanup 逻辑;
- 关闭 parser executor,修复旧 system update/stub/renamed package,并结束 data scan。
本文不重复 PMS016 的全局阶段和 PMS017 的目录筛选实现,而是解释 data/app 的身份恢复和一致性边界。PMS020 将继续展开 vendor/product/system_ext 分区,PMS021 再拆 ScanRequest/ScanResult 数据结构。
1. data/app入口
1.1 扫描入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
public void initNonSystemApps(PackageParser2 packageParser, @NonNull int[] userIds,
long startTime) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
if ((mScanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == SCAN_FIRST_BOOT_OR_UPGRADE) {
fixInstalledAppDirMode();
}
scanDirTracedLI(mPm.getAppInstallDir(), 0,
mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService, null);
List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
if (!unfinishedTasks.isEmpty()) {
throw new IllegalStateException("Not all tasks finished before calling close: "
+ unfinishedTasks);
}
fixSystemPackages(userIds);
logNonSystemAppScanningTime(startTime);
mExpectingBetter.clear();
mPm.mSettings.pruneRenamedPackagesLPw();
}initNonSystemApps() 的名字容易造成误解:它扫描的是非系统目录,不是说所有输入都一定是普通用户 app。/data/app 中可以有覆盖 system package 的 updated system app,SCAN_REQUIRE_KNOWN 让这些包必须和已有 PMS 状态协调。
1.2 data flags
data/app 调用传入 parseFlags = 0,不带 PARSE_IS_SYSTEM_DIR;scan flags 是 helper 初始化的 mScanFlags 再加 SCAN_REQUIRE_KNOWN。在普通启动中通常包含 SCAN_BOOTING | SCAN_INITIAL | SCAN_REQUIRE_KNOWN,首次启动/OTA 还包含 SCAN_FIRST_BOOT_OR_UPGRADE。
这组 flags 的含义不是“data 包永远不是 system”:updated system app 的历史身份会在 ScanPackageUtils.adjustScanFlagsWithPackageSetting() 中从 disabled system setting 恢复。
1.3 入口时序
2. 目录权限
2.1 目录mode
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
/**
* Fix up the previously-installed app directory mode - they can't be readable by non-system
* users to prevent them from listing the dir to discover installed package names.
*/
void fixInstalledAppDirMode() {
try (var files = Files.newDirectoryStream(mPm.getAppInstallDir().toPath())) {
files.forEach(dir -> {
try {
Os.chmod(dir.toString(), 0771);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fix an installed app dir mode", e);
}
});
} catch (Exception e) {
Slog.w(TAG, "Failed to walk the app install directory to fix the modes", e);
}
}目录 mode 修复只在首次启动/OTA flag 存在时执行。0771 的重点是其他用户不能列出目录内容,从而不能通过 /data/app 目录名侧信道发现包名。chmod 或目录遍历失败只打印 warning,扫描仍会继续;这是可降级的安全修复,不是 package identity 校验。
2.2 文件权限边界
该方法只遍历 app install dir 的直接子项并调用 Os.chmod(),不解析 APK、不验证签名,也不修改 PackageSetting。APK 内容、code path ownership 和 SELinux context 由安装/Installer/扫描其他阶段负责。
3. Known校验
3.1 校验目的
SCAN_REQUIRE_KNOWN 的目标是把 /data/app 目录中的文件与 PMS 已知 package state 对齐。否则,一个遗留、替换或恶意放入的 APK 可能在启动扫描时直接进入 mPackages,绕过安装流程建立的路径、签名、版本和 user state 关系。
但它不是“没有 Settings 记录就一定删除”:OTA system update 有 mExpectingBetter 例外,updated system app 也需要先和 disabled system setting 协调。
3.2 初始请求
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private Pair<ScanResult, Boolean> scanPackageForInitLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags,
@Nullable UserHandle user) throws PackageManagerException {
final boolean scanSystemPartition =
(parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
final ScanRequest initialScanRequest = prepareInitialScanRequest(
parsedPackage, parseFlags, scanFlags, user, null, null);
final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
final PackageSetting originalPkgSetting = initialScanRequest.mOriginalPkgSetting;
final PackageSetting pkgSetting = originalPkgSetting == null
? installedPkgSetting : originalPkgSetting;
final boolean pkgAlreadyExists = pkgSetting != null;prepareInitialScanRequest() 根据 parsed package 和扫描语境找出 Settings 中的 active/original package setting。data/app 后续能否接受该文件,依赖 pkgAlreadyExists、package path、disabled system setting、shared user 和 scan flags,而不是只看 APK 文件是否可解析。
3.3 路径与期待包
Android 17 当前 addForInitLI()/scanPackageForInitLI() 的 known 校验会结合:
- Settings 是否存在对应
PackageSetting; mExpectingBetter是否记录了 OTA 期间期待的 data 版本;- parsed package path 是否与已知 setting 的 path 一致;
- package 是否是 updated system app、普通 data app 或 APEX 内 APK;
- 签名和版本是否满足替换关系。
当普通 data 文件不满足这些条件时,通常生成 INSTALL_FAILED_INVALID_INSTALL_LOCATION、INSTALL_FAILED_PACKAGE_CHANGED 或其他 PackageManagerException,随后由 processParseResult() 决定清理。
3.4 路径冲突
路径检查的安全意义是防止“同名 APK 换了位置”被当作已知安装继续使用。对于 /data/app,PackageSetting 保存的是安装流程确定的 code path;如果扫描到同包名但不同 path 的文件,PMS 不能仅凭签名相同就接受,因为数据目录、更新 owner、rollback 和删除路径都会指向错误对象。
4. 用户包与系统更新
4.1 System语境
源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
final PackageSetting systemPkgSetting =
(scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
&& pkgSetting != null && pkgSetting.isSystem()
? pkgSetting
: disabledPkgSetting;
if (systemPkgSetting != null) {
// updated system application, must at least have SCAN_AS_SYSTEM
scanFlags |= SCAN_AS_SYSTEM;
if ((systemPkgSetting.getPrivateFlags()
& ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
scanFlags |= SCAN_AS_PRIVILEGED;
}
if ((systemPkgSetting.getPrivateFlags()
& ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
scanFlags |= SCAN_AS_VENDOR;
}
if ((systemPkgSetting.getPrivateFlags()
& ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
scanFlags |= SCAN_AS_PRODUCT;
}
}data/app 更新包没有 PARSE_IS_SYSTEM_DIR,但如果 active/disabled setting 表明它覆盖了 system package,PMS 会恢复 SCAN_AS_SYSTEM 及 privileged/vendor/product 等分区 flags。这个恢复保证更新版本继续拥有与 factory package 相同的系统身份边界。
4.2 不一致恢复
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (scanSystemPartition && !pkgAlreadyExists
&& mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) {
Slog.w(TAG, "Inconsistent package setting of updated system app for "
+ disabledPkgName + ". To recover it, enable the system app "
+ "and install it as non-updated system app.");
mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
}
disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
isSystemPkgUpdated = disabledPkgSetting != null;这里处理的是 system scan 发现 active setting 缺失但 disabled system setting 仍存在的异常状态。PMS 删除 disabled record,让 factory system package 恢复为 active;它不是 data/app 普通包的“找不到就恢复”逻辑,而是 system/data 两侧状态不一致时的特定修复。
4.3 旧setting基线
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (scanSystemPartition && isSystemPkgUpdated) {
final ScanRequest request = new ScanRequest(parsedPackage,
mPm.mSettings.getSharedUserSettingLPr(disabledPkgSetting),
null, disabledPkgSetting, initialScanRequest.mSharedUserSetting,
null, null, null, parseFlags, scanFlags,
initialScanRequest.mIsPlatformPackage, user, null,
initialScanRequest.mEnableAlignmentChecks);
ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
mPm.getPlatformPackage(), true);
final ScanResult scanResult = ScanPackageUtils.scanPackageOnly(
request, mPm.mInjector, mPm.mFactoryTest, -1L);
}更新 system app 的新解析包先与 disabled system setting 比较。PMS 需要知道旧版本的 appId、shared user、分区/privileged flags、签名和路径,才能决定 data 版本是否可以覆盖 factory 版本。scanPackageOnly() 只完成扫描计算;最终 active package state 的替换仍由后续注册路径完成。
4.4 版本协调
当 system scan 发现 /data/app 的更新版本时,PMS 还要结合签名 lineage、rollback、downgrade policy、旧 code path 和 system package policy。版本号更高并不自动代表可以替换;版本号更低也可能在允许 downgrade 或特定恢复路径下被接受。具体判定分散在 scanPackageForInitLI()、ScanPackageUtils 和安装 helper 中,不能把 data/app 恢复总结成一个 versionCode > oldVersion 条件。
5. 已卸载与保留数据
5.1 空AndroidPackage
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java
/**
* AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
* package code and manifest data are available; PackageSetting can still retain user state and
* data-related metadata when the package object is absent.
*/
@Nullable
public AndroidPackage getAndroidPackage() {
return pkg;
}在 DELETE_KEEP_DATA 或归档/卸载保留数据场景,Settings 可能仍保留 PackageSetting,但 AndroidPackage 为 null。data/app 扫描发现对应 APK 后,PMS 可以重新填充 package object;在扫描之前,查询引擎只能看到状态记录而不能生成完整 Manifest 对象。
5.2 用户安装位
initNonSystemApps() 的 data/app 扫描主要恢复 package code/state,per-user installed、hidden、stopped、archiveState 等仍由 PackageSetting 和安装/卸载流程维护。一个 APK 被成功扫描并注册,不等于它会自动对所有 user 变成 installed;用户状态会继续参与 getInstalledApplications()、visibility 和启动检查。
5.3 Archived状态
Android 17 把 archived package 保留在 package state 中,但不把它等同于有可执行 code 的普通安装。data/app 扫描恢复 code path 后,是否清除 archive state、对哪些 user 设置 installed,取决于具体 unarchive/install 流程。不要仅凭 APK 重新出现在 /data/app 就推断所有 archive metadata 已经清零。
6. 失败与清理
6.1 结果清理
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void processParseResult(ParallelPackageParser.ParseResult result) {
Throwable throwable = result.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
String errorMsg = null;
ScanParams scanParams = result.scanParams;
if (throwable == null) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");
addForInitLI(result.parsedPackage, scanParams.parseFlags,
scanParams.scanFlags,
new UserHandle(UserHandle.USER_SYSTEM), scanParams.apexInfo);
} catch (PackageManagerException e) {
errorCode = e.error;
errorMsg = "Failed to scan " + result.scanFile + ": " + e.getMessage();
Slog.w(TAG, errorMsg);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
} else if (throwable instanceof PackageManagerException) {
PackageManagerException e = (PackageManagerException) throwable;
errorCode = e.error;
errorMsg = "Failed to parse " + result.scanFile + ": " + e.getMessage();
Slog.w(TAG, errorMsg);
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ result.scanFile, throwable);
}
if ((scanParams.scanFlags & SCAN_AS_SYSTEM) == 0
&& errorCode != PackageManager.INSTALL_SUCCEEDED) {
logCriticalInfo(Log.WARN,
"Deleting invalid package at " + result.scanFile + " (" + errorMsg + ")");
mRemovePackageHelper.removeCodePath(result.scanFile);
}
}data/app 的关键清理条件是:结果失败且 scan flags 不含 SCAN_AS_SYSTEM。普通用户包的 invalid code path 会被 removeCodePath() 删除,避免坏文件在下次启动继续被发现;system package 失败不会走这条普通 data 清理分支。
6.2 无效包删除的范围
removeCodePath(result.scanFile) 处理的是 code path,不等于删除所有 PackageSetting/user data。用户数据是否保留、disabled system setting 是否恢复、旧版本 code path 是否延迟删除,由删除 helper、system cleanup 和安装更新流程分别决定。
6.3 APEX错误
如果 data/app 结果带有 SCAN_AS_APK_IN_APEX,失败时还会调用 mApexManager.reportErrorWithApkInApex()。普通 /data/app APK 不会进入该分支;同一个 parse error 在不同 scan context 下可能产生不同的 owner 通知。
6.4 Executor收尾
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
if (!unfinishedTasks.isEmpty()) {
throw new IllegalStateException("Not all tasks finished before calling close: "
+ unfinishedTasks);
}shutdownNow() 返回未开始执行的 parser task。非空意味着 data/app 扫描没有完整处理输入,PMS 不能继续把 package scan 当作成功;这是扫描完整性失败,不是某一个 APK 的可恢复 parse error。
7. 扫描后的状态修复
7.1 System修复
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void fixSystemPackages(@NonNull int[] userIds) {
mInstallPackageHelper.cleanupDisabledPackageSettings(
mPossiblyDeletedUpdatedSystemApps, userIds, mScanFlags);
mInstallPackageHelper.checkExistingBetterPackages(
mExpectingBetter, mStubSystemApps, mSystemScanFlags, mSystemParseFlags);
// Uncompress and install any stubbed system applications.
// This must be done last to ensure all stubs are replaced or disabled.
mInstallPackageHelper.installSystemStubPackages(mStubSystemApps, mScanFlags);
}data/app 扫描完成后,PMS 还要处理:
- data 中缺失的 updated system app 对应 disabled setting;
- OTA 期间期待的 better package 是否真正出现;
- stub system app 是否被完整版本替换,否则禁用 stub。
这些修复必须在所有 data/app package 处理后执行,因为它们依赖最终 mPackages 集合。
7.2 ExpectingBetter
system scan 阶段将可能被 data update 覆盖的 system package 放入 mExpectingBetter;data scan 处理 /data/app 后,checkExistingBetterPackages() 检查哪些期待的包没有出现;initNonSystemApps() 最后清空 map。map 清空只表示本次启动的协调结束,不表示所有 system update 都成功存在。
7.3 重命名裁剪
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
mExpectingBetter.clear();
mPm.mSettings.pruneRenamedPackagesLPw();重命名记录要等 system/data 两侧都处理完才裁剪。若某个历史名称仍被 active/disabled package 引用,Settings 会保留必要映射;扫描阶段末尾的 prune 不是无条件清空 renamed package 表。
8. 调试与测试
8.1 日志与dumpsys
针对一个 data/app 包,建议按以下证据链检查:
BOOT_PROGRESS_PMS_DATA_SCAN_START和BOOT_PROGRESS_PMS_SCAN_END的时间点;scanDir [data/app]、parallel parsePackage和addForInitLItrace;Failed to parse/Failed to scan、INSTALL_FAILED_PACKAGE_CHANGED或INSTALL_FAILED_INVALID_INSTALL_LOCATION;dumpsys package <package>中 activecodePath、version、flags/privateFlags、user state;- 是否存在 disabled system package、renamed package 或 stub package;
- 失败后
/data/appcode path 是否被removeCodePath()清理。
8.2 ScanTests
测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
assertBasicApplicationInfo(scanResult, applicationInfo);该测试证明 scan result 中的 PackageSetting/AndroidPackage 可以生成 ApplicationInfo;它不证明 /data/app path known 校验、SCAN_REQUIRE_KNOWN、invalid code path 删除或 OTA expectingBetter。
8.3 Parser测试
测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
Parser 测试应覆盖:有效 APK 生成 ParsedPackage;解析异常进入 ParseResult.throwable;中断时 take() 不会永久阻塞;ordered future 保持提交顺序。它不证明 processParseResult() 对 system/data flags 的 cleanup 分支。
8.4 集成测试
要证明完整恢复链,至少要准备:
- packages.xml 中已知且 path 一致的普通 data package;
- 同包名不同 path 的文件,断言 package changed;
- unknown data APK,断言 invalid install location 并清理 code path;
- system package 的 data 更新版本,断言恢复 system/privileged/partition flags;
- OTA expectingBetter 缺失,断言回退 factory system package;
DELETE_KEEP_DATA后重新出现 APK,断言 PackageSetting 与 AndroidPackage 重新关联;- 损坏 APK 和未完成 parser task 的终止路径。
单独跑 PackageParserTest 或查看一次 dumpsys package,都不足以证明这些跨阶段行为。
8.5 推荐命令
adb shell dumpsys package <package-name>
adb shell dumpsys package packages
adb shell dumpsys package apex
adb shell logcat -b events | grep PMS
adb shell logcat -s PackageManager PackageManagerService命令用于观察状态,不替代源码推理。尤其是 dumpsys package packages 只展示当前 Settings/PackageState 视图,不能直接证明某个 data/app 文件经历了完整安装校验。
9. 阅读路线
阅读 data/app 扫描问题时,按下面顺序追源码:
- 从
InitAppsHelper.initNonSystemApps()记录 app install dir、parseFlags=0、SCAN_REQUIRE_KNOWN和首次启动/OTA 分支。 - 进入
scanDirTracedLI()/InstallPackageHelper.installPackagesFromDir(),确认目录筛选、parser executor 和ParseResult。 - 进入
processParseResult(),记录 parse failure、scan/register failure、APEX error report 和 data code path cleanup。 - 进入
addForInitLI()/scanPackageForInitLI(),确认prepareInitialScanRequest()找到的 active/original/disabled settings。 - 若是 updated system app,进入
adjustScanFlagsWithPackageSetting(),检查 system/privileged/vendor/product/system_ext flags 恢复。 - 检查路径、签名、版本、shared UID、rollback 和 user state 的校验 owner。
- 回到
fixSystemPackages()、mExpectingBetter.clear()、pruneRenamedPackagesLPw()和 usage/scan end,确认恢复是否收尾。 - 用日志、dumpsys、packages.xml、parser/scan 测试和设备实验区分“文件被发现”“package 被注册”“用户可用”“系统更新协调完成”。
小结
Android 17 的 /data/app 扫描是一次“发现文件后恢复可信状态”的过程:
initNonSystemApps()先按首次启动/OTA 修复目录 mode,再以SCAN_REQUIRE_KNOWN扫描 app install dir。parseFlags=0只表示输入不是 system directory;updated system app 的 system/privileged/分区身份会从 active/disabled PackageSetting 恢复。SCAN_REQUIRE_KNOWN需要同时考虑 Settings 记录、code path、mExpectingBetter、system update、签名、版本和 shared UID,不能简化成单一 XML 存在检查。ParallelPackageParser负责并行 parse,processParseResult()负责把结果带回受锁保护的注册、错误码、APEX 回报和 data cleanup 路径。- 普通 data 包失败会删除 invalid code path;system package 失败交给恢复/cleanup,不与用户包统一处理。
DELETE_KEEP_DATA、archive 和卸载保留数据会让 PackageSetting 在没有 AndroidPackage 时继续存在;重新发现 APK 只恢复 code/manifest 关联,不自动重置所有 user state。- data 扫描结束后还要处理 disabled system package、expectingBetter、stub、renamed package、usage 和 scan end;文件被发现不等于 package 已经对所有用户可用。
- 教材级调试要把目录、flags、Settings 基线、扫描 policy、错误清理、用户状态、日志和 dumpsys 输出连起来,才能判断是“文件问题”还是“状态协调问题”。
下一篇 PMS020 将继续分析 vendor/product/system_ext 分区扫描,重点比较 partition scan flags、overlay 来源、分区版本约束和跨分区 package conflict。
