异步任务状态机:PENDING、DOWNLOADING、INSTALLING、INSTALLED、FAILED、CANCELED
SplitInstall 模块下载与安装的过程是一个典型的有限状态机(FSM)。每个异步任务从创建到消亡,都会依次或跳转地经历一组互斥状态。客户端只有准确理解这六种状态的触发条件与切换路径,才能在回调中做出正确的 UI 更新与错误处理。
状态定义与切换规则
| 状态 | 含义 | 触发时机 | 可流转至 |
|---|---|---|---|
| PENDING | 任务已排队,等待系统调度 | startInstall() 调用成功返回 |
DOWNLOADING, FAILED, CANCELED |
| DOWNLOADING | 正在从 Google Play 下载安装包 | 系统开始传输数据 | INSTALLING, FAILED, CANCELED |
| INSTALLING | 下载完成,正在安装到应用沙箱 | 下载进度达到 100% | INSTALLED, FAILED |
| INSTALLED | 模块已就绪,可立即调用 | 安装流程完成 | (终止态) |
| FAILED | 因网络、存储、权限等原因终止 | 任何阶段发生不可恢复错误 | (终止态) |
| CANCELED | 用户或客户端主动取消 | 调用 cancelInstall() 或系统清理 |
(终止态) |
注意:CANCELED 只在 PENDING 或 DOWNLOADING 阶段生效;进入 INSTALLING 后无法取消,只能等待 FAILED 或成功。
状态机流程图
状态监听 API
客户端通过 SplitInstallStateUpdatedListener 接收状态变更。核心实现在 onStateUpdate() 回调中根据 sessionId 和 status 字段分发逻辑:
SplitInstallStateUpdatedListener listener = new SplitInstallStateUpdatedListener() {
@Override
public void onStateUpdate(SplitInstallSessionState state) {
switch (state.status()) {
case PENDING:
// 显示排队提示
break;
case DOWNLOADING:
long bytesDownloaded = state.bytesDownloaded();
long totalBytes = state.totalBytesToDownload();
updateProgressBar(bytesDownloaded, totalBytes);
break;
case INSTALLING:
// 显示“安装中”状态,避免用户操作
setInstallingUI(true);
break;
case INSTALLED:
// 模块已就绪,可加载代码或资源
loadSplit(state.moduleName());
break;
case FAILED:
int errorCode = state.errorCode();
handleError(errorCode);
break;
case CANCELED:
// 用户取消或系统清理,重置 UI
resetUI(state.moduleName());
break;
}
}
};
关键校验: 状态回调可能在 任何线程 上触发。建议使用
Handler 或 LiveData 将 UI 更新切回主线程。
调试与日志
开发时可通过 Android Studio 的 Logcat 观察 SplitInstall 内部日志。启用详细日志:
adb shell setprop log.tag.SplitInstall VERBOSE
adb logcat -s SplitInstall D
日志常见模式:
Processing state: PENDING → DOWNLOADING—— 正常调度Session [ID] canceled by user—— 收到 CANCELEDError code -2 (NETWORK_ERROR)—— 导致 FAILED 的可能原因
常见错误与优化
陷阱: 在
INSTALLING 状态下调用 cancelInstall() 会被忽略。若用户想取消,应仅在 PENDING 或 DOWNLOADING 时取消,并立即清理对应 UI。
- 状态丢失: 应用进程被杀后,未完成的 session 状态会丢失。建议在
onResume()调用getSessionStates()恢复。 - 重复注册监听器:
registerListener()应在Application.onCreate()或 BaseActivity 中一次注册,避免多次创建导致泄漏。 - 性能建议: 不要在
onStateUpdate()中执行耗时操作;若需要持久化状态,使用WorkManager异步写数据库。