PackageHandler
PMS 的 Handler 不是一个“把耗时工作放到后台”的简单线程池。Android 17 中,消息队列承担的是状态机之间的交接:Binder 入口先完成权限和参数检查,再把安装、验证、清理、设置写入或广播收尾投递给特定 Looper;PackageHandler.handleMessage() 取出 token/state,调用 owner 完成下一阶段;需要持久化或回调时,又通过延迟消息、另一个 Handler 或重新进入 PMS 锁保护的路径发布结果。
本篇沿源码回答以下问题:
mHandler和mBackgroundHandler为什么分工不同,分别运行在哪个线程;POST_INSTALL如何从 token 找到InstallRequest,何时关闭 freezer,何时发安装回调;PACKAGE_VERIFIED与CHECK_PENDING_VERIFICATION如何处理重复、超时和被覆盖的验证状态;WRITE_SETTINGS、WRITE_PACKAGE_LIST和 dirty restrictions 如何合并、延迟和取消;- 消息处理中的
snapshotComputer()、锁、广播和外部 I/O 怎样串起来; - 消息不存在、状态已完成、回滚失败、ART 未就绪等异常路径如何降级;
- 测试应该证明“消息被投递”还是证明“状态机完成”,两者为什么不同。
PMS012 已经讨论锁保护域,本篇只在消息处理需要时引用锁;PMS014 的 PackageManagerNative 不使用这套 Java PackageHandler 主线,因此不在本文展开。
1. 消息架构
1.1 两个 Handler
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
HandlerThread backgroundThread = new ServiceThread("PackageManagerBg",
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
backgroundThread.start();
Handler backgroundHandler = new Handler(backgroundThread.getLooper(),
BACKGROUND_HANDLER_CALLBACK);前台 Handler 由 injector 创建 PackageHandler:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
(i, pm) -> {
HandlerThread thread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
thread.start();
return new PackageHandler(thread.getLooper(), pm);
},当前 Android 17 的两个队列分别是:
| Handler | 线程 | 处理方式 | 典型消息 |
|---|---|---|---|
mHandler | ServiceThread(TAG),默认优先级 | PackageHandler.handleMessage() | 安装收尾、验证、广播、回滚、设置写入 |
mBackgroundHandler | PackageManagerBg,后台优先级 | Handler.Callback | dirty package restrictions、用户限制写入 |
两个线程都标记 allowIo = true,但这不等于所有消息都适合长时间阻塞。前台 Handler 还受 Watchdog 监控,PMS 把它的超时时间设为十分钟,因为安装多 GB 应用时可能发生长 I/O;后台队列则承接可合并、可延后的持久化工作。
1.2 Watchdog注册
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
static final long WATCHDOG_TIMEOUT = 1000 * 60 * 10; // ten minutes
mHandler = injector.getHandler();
mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);Watchdog 监控的是 Handler 线程是否长时间不响应,不是每条消息是否一定在十分钟内完成。POST_INSTALL、验证和安装后处理可能调用外部服务或等待状态机;如果线程被锁循环、无界等待或异常阻塞,Watchdog 才能提供系统级诊断信号。
2. 消息常量
2.1 常量定义
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
static final int SEND_PENDING_BROADCAST = 1;
static final int POST_INSTALL = 9;
static final int WRITE_SETTINGS = 13;
static final int WRITE_DIRTY_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
static final int CHECK_PENDING_VERIFICATION = 16;
static final int WRITE_PACKAGE_LIST = 19;
static final int INSTANT_APP_RESOLUTION_PHASE_TWO = 20;
static final int ENABLE_ROLLBACK_STATUS = 21;
static final int ENABLE_ROLLBACK_TIMEOUT = 22;
static final int DEFERRED_NO_KILL_POST_DELETE = 23;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
static final int DOMAIN_VERIFICATION = 27;
static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
static final int WRITE_USER_PACKAGE_RESTRICTIONS = 30;常量值是进程内协议,不是公开 API。消息的真实 payload 由 arg1、arg2 和 obj 共同决定:例如 POST_INSTALL 用 arg1 作为 token、arg2 表示是否恢复;PACKAGE_VERIFIED 用 arg1 作为 verificationId、obj 放 response;WRITE_PACKAGE_LIST 用 arg1 放 userId。
2.2 延迟参数
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
private static final long DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED =
TimeUnit.DAYS.toMillis(1);
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds延迟不是统一的“降低优先级”:
- 删除旧 code path 默认延迟 3 秒,DeviceConfig flag 可以延长到 1 天;
- 不杀进程的安装 observer 延迟 500 ms,需要杀进程的 observer 延迟 1000 ms;
- Settings、package list 和 dirty restrictions 默认延迟 10 秒,用来合并连续变化。
延迟消息的生效时机是“消息从队列取出”而不是“调用 sendMessageDelayed()”。在延迟窗口内,状态可能继续变化,handler 处理时必须重新读取当前 owner 状态,而不能假设投递时的对象仍然有效。
3. Handler入口
3.1 构造与异常恢复
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
final class PackageHandler extends Handler {
private final PackageManagerService mPm;
PackageHandler(Looper looper, PackageManagerService pm) {
super(looper);
mPm = pm;
}
@Override
public void handleMessage(Message msg) {
try {
doHandleMessage(msg);
} finally {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
}
}handleMessage() 只负责包住分发函数和恢复线程优先级。某条消息临时提升了线程优先级或处理过程中抛出异常,finally 都会把当前线程恢复为默认优先级;它不负责重试消息,也不吞掉异常。
3.2 switch分发
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
void doHandleMessage(Message msg) {
switch (msg.what) {
case SEND_PENDING_BROADCAST: {
mPm.sendPendingBroadcasts((String) msg.obj, msg.arg1);
break;
}
case POST_INSTALL: {
// installation completion state machine
break;
}
case PACKAGE_VERIFIED: {
// verification response state machine
break;
}
case WRITE_SETTINGS: {
mPm.writeSettings(/*sync=*/false);
break;
}
// ... other message codes
}
}源码没有 default 分支。未知 what 会直接走完 switch,不会自动记录错误或重新入队。因此新增消息时必须同时修改常量、投递点和 handler 分支;仅仅发送一个未实现的 code 不会得到失败回调。
4. POST_INSTALL
4.1 安装投递
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (doRestore) {
if (packageSetting != null) {
synchronized (mPm.mLock) {
packageSetting.setPendingRestore(false);
}
}
} else {
// No restore possible, or the Backup Manager was mysteriously not available.
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
Message msg = mPm.mHandler.obtainMessage(POST_INSTALL, token, 0);
mPm.mHandler.sendMessage(msg);
}InstallPackageHelper 先决定 backup/restore 是否完成;没有 restore 时,直接把 token 投给前台 Handler。这里的消息只表示“可以开始 post-install”,不表示安装 observer 已经收到回调,也不表示广播已经发出。
4.2 取出Request
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case POST_INSTALL: {
if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
final InstallRequest request;
final int token;
final boolean didRestore;
synchronized (mPm.mRunningInstalls) {
request = mPm.mRunningInstalls.get(msg.arg1);
token = msg.arg1;
didRestore = (msg.arg2 != 0);
mPm.mRunningInstalls.delete(token);
}
if (request == null) {
if (DEBUG_INSTALL) {
Slog.i(TAG, "InstallRequest is null. Nothing to do for post-install token "
+ token);
}
break;
}mRunningInstalls 用独立锁保护 token 到 InstallRequest 的映射。Handler 先在锁内取出并删除,再在锁外执行后续流程;这样同一个 token 的重复消息第二次会拿到 null,不会重复关闭 freezer 或发送回调。
4.3 收尾顺序
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
request.onRestoreFinished();
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
if (!request.isInstallExistingForUser()) {
mPm.handlePackagePostInstall(request, didRestore);
} else if (DEBUG_INSTALL) {
// No post-install when we run restore from installExistingPackageForUser
Slog.i(TAG, "Nothing to do for post-install token " + token);
}
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);顺序具有状态意义:
onRestoreFinished()结束 restore 相关状态;closeFreezer()释放安装期间的 package freeze;onInstallCompleted()更新 request 的安装完成状态;- 执行 request 自己挂入的 post-install runnable;
- 普通安装进入 PMS 的
handlePackagePostInstall(),installExistingPackageForUser则跳过普通 post-install; - 最后结束异步 trace。
如果在第 2 步之前抛异常,freezer 清理责任不能假设由后面的 PMS 逻辑承担;InstallRequest 的实现和调用方需要保证异常路径可清理。
4.4 POST_INSTALL时序
4.5 回调时机
PMS 的安装回调可能还要经过 notifyInstallObserver()、延迟 observer 消息和 package broadcast。比如:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void notifyInstallObserver(InstallRequest request) {
if (request.getObserver() != null) {
try {
Bundle extras = extrasForInstallResult(request);
request.getObserver().onPackageInstalled(request.getName(),
request.getReturnCode(), request.getReturnMsg(), extras);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
}
}
void scheduleDeferredNoKillInstallObserver(InstallRequest request) {
String packageName = request.getPkg().getPackageName();
mNoKillInstallObservers.put(packageName, request);
Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_INSTALL_OBSERVER, packageName);
mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
}POST_INSTALL 只是进入安装完成状态机;observer 的执行时机、是否杀进程和广播顺序由 handlePackagePostInstall() 及其后续消息决定。
5. 验证消息
5.1 PACKAGE_VERIFIED
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case PACKAGE_VERIFIED: {
final int verificationId = msg.arg1;
final PackageVerificationState state =
mPm.mPendingVerification.get(verificationId);
if (state == null) {
Slog.w(TAG, "Verification with id " + verificationId
+ " not found. It may be invalid or overridden by integrity verification");
break;
}
if (state.isVerificationComplete()) {
Slog.w(TAG, "Verification with id " + verificationId + " already complete.");
break;
}
final PackageVerificationResponse response =
(PackageVerificationResponse) msg.obj;
VerificationUtils.processVerificationResponse(verificationId, state, response, mPm);
break;
}Handler 不在这里自己决定 allow/reject,而是把 response 和仍未完成的 state 交给 VerificationUtils。两个 guard 处理了两类重复/竞态:验证状态已经被移除,或另一个 verifier 已经先完成验证。
5.2 验证投递
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
final int callingUid = Binder.getCallingUid();
mHandler.post(() -> {
final int id = verificationId >= 0 ? verificationId : -verificationId;
final PackageVerificationState state = mPendingVerification.get(id);
if (state == null) {
return;
}
// Only allow calls from verifiers.
final Message msg = mHandler.obtainMessage(PackageManagerService.PACKAGE_VERIFIED);
final PackageVerificationResponse response =
new PackageVerificationResponse(verificationCode, callingUid);
msg.arg1 = id;
msg.obj = response;
mHandler.sendMessage(msg);
});Binder 入口先捕获 callingUid,再把验证结果投递到 PackageHandler 线程;Handler 线程处理时使用这个 response 中的 UID,而不是依赖异步线程上的 Binder calling identity。这个模式避免了 Binder.clearCallingIdentity() 或 Handler 切线程后身份丢失。
5.3 验证超时
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case CHECK_PENDING_VERIFICATION: {
final int verificationId = msg.arg1;
final boolean streaming = msg.arg2 != 0;
final PackageVerificationState state =
mPm.mPendingVerification.get(verificationId);
if (state == null || state.isVerificationComplete()) {
// Not found or complete.
break;
}
final PackageVerificationResponse response =
(PackageVerificationResponse) msg.obj;
if (!streaming && state.timeoutExtended(response.callerUid)) {
// Timeout extended.
break;
}
VerificationUtils.processVerificationResponseOnTimeout(
verificationId, state, response, mPm);
break;
}超时消息不是无条件 reject:如果非 streaming 验证仍可由该 caller 延长 timeout,handler 直接结束本轮;否则才把超时 response 交给 processVerificationResponseOnTimeout()。状态机 owner 决定最终策略,handler 只负责选择分支。
5.4 验证状态图
6. 设置持久化
6.1 调度Settings
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void scheduleWriteSettings() {
// We normally invalidate when we write settings, but in cases where we delay and
// coalesce settings writes, this strategy would have us invalidate the cache too late.
// Invalidating on schedule addresses this problem.
invalidatePackageInfoCache(
PackageMetrics.INVALIDATION_REASON_SCHEDULE_WRITE_SETTINGS);
ApplicationPackageManager.invalidateQueryIntentActivitiesCache();
if (!mHandler.hasMessages(WRITE_SETTINGS)) {
mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY);
}
}这里先失效客户端 package info/query cache,再检查队列中是否已有 WRITE_SETTINGS。多次调用不会堆积同类消息;缓存失效发生在 schedule 时,而不是十秒后真正写盘时,避免延迟期间读到旧缓存。
6.2 Settings
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java、PackageManagerService.java
case WRITE_SETTINGS: {
mPm.writeSettings(/*sync=*/false);
break;
}Handler 只负责把消息转给 PMS;具体写入和消息取消发生在 writeSettings()。
void writeSettings(boolean sync) {
synchronized (mLock) {
mHandler.removeMessages(WRITE_SETTINGS);
mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP(sync);
synchronized (mDirtyUsers) {
mDirtyUsers.clear();
}
}
}Handler 分支本身很短,真正写 Settings 在 PMS 的 mLock 内完成,并取消尚未处理的后台 dirty restrictions 消息。同步写入路径会清理 dirty user 集合,避免同一批状态被再次异步写入。
6.3 包列表
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、PackageHandler.java
void scheduleWritePackageListLocked(int userId) {
invalidatePackageInfoCache(
PackageMetrics.INVALIDATION_REASON_SCHEDULE_WRITE_PACKAGE_LIST);
ApplicationPackageManager.invalidateQueryIntentActivitiesCache();
if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
msg.arg1 = userId;
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
case WRITE_PACKAGE_LIST: {
mPm.writePackageList(msg.arg1);
break;
}Handler 消息只携带 userId,真正的 Settings 写入由下面的方法在 mLock 内完成。
void writePackageList(int userId) {
synchronized (mLock) {
mHandler.removeMessages(WRITE_PACKAGE_LIST);
mSettings.writePackageListLPr(userId);
}
}WRITE_PACKAGE_LIST 只保留一个待处理消息,因此连续为不同 user 调用时,后一次调用不会追加第二条消息;读源码时要继续检查调用方是否会合并 user 或另行触发全量写入。消息消费时用 arg1 指定 user,并在 mLock 下调用 Settings。
6.4 dirty集
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void scheduleWritePackageRestrictions(@CanBeALL @UserIdInt int userId) {
invalidatePackageInfoCache(
PackageMetrics.INVALIDATION_REASON_SCHEDULE_WRITE_PACKAGE_RESTRICTIONS);
if (userId == USER_ALL) {
synchronized (mDirtyUsers) {
for (int aUserId : mUserManager.getUserIds()) {
mDirtyUsers.add(aUserId);
}
}
} else {
if (!mUserManager.exists(userId)) {
return;
}
synchronized (mDirtyUsers) {
mDirtyUsers.add(userId);
}
}
if (!mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)) {
mBackgroundHandler.sendMessageDelayed(
mBackgroundHandler.obtainMessage(WRITE_DIRTY_PACKAGE_RESTRICTIONS, this),
WRITE_SETTINGS_DELAY);
}
}这里的合并对象是 mDirtyUsers 集合,不是消息数量。多个 user 在十秒内变脏,会被收集到集合中,只投递一条后台消息。
6.5 后台写盘
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case WRITE_DIRTY_PACKAGE_RESTRICTIONS: {
PackageManagerService pm = (PackageManagerService) msg.obj;
pm.writePendingRestrictions();
return true;
}
case WRITE_USER_PACKAGE_RESTRICTIONS: {
final Runnable r = (Runnable) msg.obj;
r.run();
return true;
}
}
return false;
}
};后台 callback 先调用 writePendingRestrictions(),该方法只在锁内摘取待写用户,再把 I/O 留在锁外。
void writePendingRestrictions() {
final Integer[] dirtyUsers;
synchronized (mLock) {
mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
synchronized (mDirtyUsers) {
if (mDirtyUsers.isEmpty()) {
return;
}
dirtyUsers = mDirtyUsers.toArray(Integer[]::new);
mDirtyUsers.clear();
}
}
mSettings.writePackageRestrictions(dirtyUsers);
}writePendingRestrictions() 在锁内摘取并清空 dirty user 快照,释放 mLock 后才执行 mSettings.writePackageRestrictions()。这让磁盘 I/O 不阻塞包状态读写;代价是写盘期间新的变化会进入下一轮 dirty 集合,而不是修改本次 dirtyUsers 数组。
7. 其他消息
7.1 延迟删除
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、PackageHandler.java
void scheduleDeferredNoKillPostDelete(CleanUpArgs args) {
Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
long deleteDelayMillis = DEFERRED_NO_KILL_POST_DELETE_DELAY_MS;
deleteDelayMillis = Binder.withCleanCallingIdentity(() ->
DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
PROPERTY_DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED,
DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED));
mHandler.sendMessageDelayed(message, deleteDelayMillis);
}
case DEFERRED_NO_KILL_POST_DELETE: {
CleanUpArgs args = (CleanUpArgs) msg.obj;
if (args != null) {
mPm.cleanUpResources(args.getPackageName(), args.getCodeFile());
}
} break;消息只携带 code path 清理参数;如果 payload 为 null,handler 静默结束。延迟删除的目的通常是给旧进程/文件使用留出窗口,cleanUpResources() 的实际删除和异常处理由 PMS owner 完成。
7.2 Instant App
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case INSTANT_APP_RESOLUTION_PHASE_TWO: {
InstantAppResolver.doInstantAppResolutionPhaseTwo(mPm.mContext,
mPm.snapshotComputer(),
mPm.mUserManager,
mPm.mInstantAppResolverConnection,
(InstantAppRequest) msg.obj,
mPm.mInstantAppInstallerActivity,
mPm.mHandler);
break;
}Handler 线程在执行第二阶段前重新取得 Computer snapshot;它不会复用消息投递时的旧对象。解析器可能再次向 mHandler 投递后续消息,因此这是一个消息驱动的多阶段状态机,而非单个同步函数。
7.3 回滚状态与超时
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case ENABLE_ROLLBACK_STATUS: {
final int enableRollbackToken = msg.arg1;
final int enableRollbackCode = msg.arg2;
final VerifyingSession params =
mPm.mPendingEnableRollback.get(enableRollbackToken);
if (params == null) {
Slog.w(TAG, "Invalid rollback enabled token " + enableRollbackToken + " received");
break;
}
mPm.mPendingEnableRollback.remove(enableRollbackToken);
if (enableRollbackCode != PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED) {
final Uri originUri = Uri.fromFile(params.mOriginInfo.mResolvedFile);
Slog.w(TAG, "Failed to enable rollback for " + originUri);
Slog.w(TAG, "Continuing with installation of " + originUri);
}
params.handleRollbackEnabled();
break;
}失败策略是“记录失败但继续安装”,不是把整个安装标成失败。token 从 mPendingEnableRollback 移除后,重复状态消息会被视为 invalid。超时分支还会向 rollback agent 发送 ACTION_CANCEL_ENABLE_ROLLBACK 广播,通知外部 owner 放弃 rollback enable。
7.4 域验证与库清理
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case DOMAIN_VERIFICATION: {
int messageCode = msg.arg1;
Object object = msg.obj;
mPm.mDomainVerificationManager.runMessage(messageCode, object);
break;
}
case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: {
try {
mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries(
mPm.snapshotComputer(), Long.MAX_VALUE,
Settings.Global.getLong(mPm.mContext.getContentResolver(),
Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD));
} catch (IOException e) {
Log.w(TAG, "Failed to prune unused static shared libraries :" + e.getMessage());
}
break;
}域验证把 code/object 原样委托给 DomainVerificationManager;共享库清理重新取得 snapshot,并把 IOException 转成 warning 后结束消息。清理失败不会自动重试,是否下次再次调度由共享库 owner 决定。
8. 消息与锁
8.1 Handler与锁
PackageHandler 本身没有在 doHandleMessage() 外包 synchronized (mPm.mLock)。每个 owner 按自己的需要获取锁:
writeSettings()、writePackageList()在 PMS 内部获取mLock;writePendingRestrictions()只在摘取 dirty users 时获取mLock,I/O 放锁外;POST_INSTALL先锁mRunningInstalls,然后在锁外执行 request 回调和 PMS post-install;INSTANT_APP_RESOLUTION_PHASE_TWO主要使用 snapshot,不由 handler 自动加大锁。
因此不能因为代码运行在 PackageManager 线程就直接访问任意 mPackages/Settings 字段。线程归属和状态锁是两层独立约束。
8.2 发送边界
延迟写入方法通常只在 dirty 集合锁内更新集合,然后在锁外检查/发送消息;持有 mLock 时发送 Handler 消息也要确认不会同步执行 callback。Handler.sendMessage() 只是入队,但如果使用 post() 到当前 Looper 或调用方随后等待 latch,就可能产生隐式顺序依赖。
8.3 异步snapshot
入口获取的 Computer 不能默认跨消息延迟仍然有效。真实代码在需要时重新调用 snapshotComputer():
mHandler.post(() -> {
final Computer snapshot = snapshotComputer();
// Use the state that is current when this message executes.
});这保证消息执行时至少从当前 PMS snapshot 入口开始读取;如果业务必须保留投递时的版本,则应显式保存版本/状态并在处理时比较,而不是把 Java 对象随意跨线程共享。
9. 测试与诊断
9.1 测试消息分发
对 PackageHandler 的单元测试应把输入拆成 what、arg1、arg2、obj 和预置 owner state:
| 消息 | 预置 | 动作 | 断言 |
|---|---|---|---|
POST_INSTALL | mRunningInstalls[token] = request | doHandleMessage() | request 被删除,按顺序调用收尾方法 |
POST_INSTALL 重复 | map 中无 token | 再处理相同 token | 不重复 close/callback |
PACKAGE_VERIFIED | pending state 未完成 | 携带 response | 调用 VerificationUtils |
PACKAGE_VERIFIED 已完成 | state complete | 再处理 | 不重复完成 |
CHECK_PENDING_VERIFICATION | timeout 可延长 | 处理 timeout | 不立即执行 reject |
WRITE_SETTINGS | handler 有消息 | 处理 | 调用异步 writeSettings(false) |
| dirty restrictions | dirty users 非空 | 后台处理 | 锁内摘取、锁外写盘 |
这些断言证明的是 handler 到 owner 的分发和状态转移,不自动证明 HandlerThread 真正启动、消息延迟精确、磁盘文件内容或 Watchdog 行为。
9.2 安装收尾测试
测试应验证 InstallRequest 的副作用顺序,而不是只断言 handlePackagePostInstall() 被调用:
- 输入一个带 restore、freezer、observer 和 post-install runnable 的 request;
- 投递
POST_INSTALL(token, didRestore); - 断言 token 从
mRunningInstalls删除; - 断言
onRestoreFinished → closeFreezer → onInstallCompleted → runnable的顺序; - 对
installExistingForUser断言跳过普通 post-install; - 对无 request、重复 token 和 observer RemoteException 断言安全结束。
9.3 持久化测试
设置写入测试要区分“缓存失效已发生”和“文件已写入”:scheduleWriteSettings() 会立即 invalidation,但 WRITE_SETTINGS 十秒后才执行。测试输入连续两次 schedule 调用,断言只保留一条消息;处理消息后再断言 writeSettings(false) 清理消息和 dirty users。真正的 XML 原子写入属于 Settings/ResilientAtomicFile 测试,不应由 Handler 单测代替。
9.4 Watchdog诊断
前台 Handler 被 Watchdog.addThread(mHandler, WATCHDOG_TIMEOUT) 注册。要诊断卡顿,应同时观察:
- Handler 当前堆栈是否卡在 Installer、锁、Binder 或文件 I/O;
- 消息队列中是否有同类消息被重复投递;
mRunningInstalls、mPendingVerification、mPendingEnableRollback是否存在悬挂 token;- 延迟消息是否被取消后仍有 owner 状态等待回调;
- 后台 dirty users 是否持续增长而没有完成写入。
10. 阅读路线
阅读 PMS 的 Handler 代码时,按下面顺序追踪:
- 从消息常量找到
PackageHandler分支,记录 payload 的每个字段来源。 - 反向搜索
obtainMessage()、sendMessage()、sendMessageDelayed()和post(),找到真正的投递方。 - 对 token 消息,先找 map/state owner,再看 handler 是否在处理前删除 token,判断重复消息行为。
- 对延迟消息,记录 delay、是否
hasMessages()合并、是否removeMessages()取消,以及状态变化发生在延迟窗口时的处理方式。 - 对持久化消息,区分缓存 invalidation、消息入队、锁内摘取和锁外 I/O 四个时点。
- 对安装/验证消息,画出 owner 状态从 pending 到 complete 的转移,并标出失败时是丢弃、重试、继续主流程还是广播通知。
- 检查异步边界后是否重新取得 snapshot、Binder UID 是否提前保存、锁是否在回调前释放。
- 最后读测试,明确它证明了分发、状态转移、延迟合并还是实际磁盘/线程行为。
小结
Android 17 的 PMS Handler 是多阶段状态机的调度边界:
mHandler运行PackageHandler,承接安装收尾、验证、回滚、域验证、广播和延迟清理;mBackgroundHandler只处理可合并、可延后的限制持久化。- 消息 payload 是进程内协议,
what、arg1、arg2、obj必须和投递点成对阅读;未知消息不会自动报错或重试。 POST_INSTALL先从 token map 原子取出并删除 request,再按 restore、freezer、completed、runnable、PMS post-install 的顺序收尾;重复 token 会安全结束。- 验证消息在 Handler 侧只做 state lookup、重复/超时 guard,最终 allow/reject 由
VerificationUtils和验证状态 owner 决定。 - 设置写入在 schedule 时立即失效客户端缓存,消息阶段负责延迟合并;dirty restrictions 在锁内摘取 user 集合,锁外写盘。
- Handler 不自动持有 PMS 大锁;每个 owner 自己决定
mLock、局部锁、snapshot 和锁外 I/O 边界。 - 异步消息执行时必须重新确认状态和 snapshot,不能把投递时对象、Binder calling identity 或 token 生命周期想当然地带过队列。
- 测试应分别证明消息分发、状态转移、延迟合并、锁外 I/O 和真实文件结果,不能用一个 Handler 单测覆盖整条安装/持久化链。
下一篇 PMS014 将转向 PackageManagerNative,比较 native package manager 接口与 Java IPackageManager/PackageHandler 的边界,以及 JNI/系统服务调用为什么只暴露更窄的能力集合。
