整包恢复事务与进程中断
完整备份先解压到有大小限制的私有隔离目录,再由用户确认。 加密备份在解压前完成认证。替换现有数据前,先检查剪贴板 WAL、数据库版本、偏好 XML 和外部目标目录。 操作步骤见备份与恢复。
所有权与进程隔离
确认后,主进程保存并停止输入引擎,写入私有请求,再打开不对外导出的 :restore Activity。 恢复进程不初始化 AppPrefs、Room、主题、原生输入或应用后台写入任务。 它取得恢复文件锁,停止旧主进程,确认旧进程退出后才复制任何正在使用的目录。
主应用在 attachBaseContext 取得同一把锁,早于内容提供方和 onCreate 初始化偏好或数据库缓存。 新主进程因此不会在恢复进行时打开数据。锁被占用时,启动流程转入恢复页面,不在主线程阻塞等待。 各进程还会串行化自己的调用,避免 JVM 文件锁重叠。
数据目录恢复一致后,大量残留目录的递归清理在 IO 执行,不延迟普通应用初始化。 后台只清理已标记 resolved 的目录,不在冲突消失或外部卷返回后自行重试仍待处理的活动目录;这类决定由启动、恢复界面或方案访问路径在恢复锁下完成。 测试夹具删除根目录前会等待后台任务结束。
主进程记住启动时已准备好的目录,使输入引擎不必等待隐藏残留的删除。 这依赖另一轮恢复在修改活动目录前必须终止该主进程。可选存储返回后,在使用待处理目录之前仍需持锁重查。
持久化恢复决定
noBackupFilesDir/user-data-restore.json 记录所有目录和一个全局阶段:
| 阶段 | 中断后的恢复决定 |
|---|---|
STAGING | 保留现有数据,删除本次拥有的不完整暂存目录。 |
REPLACING | 按相反顺序,将所有受影响目录回滚到原数据。 |
COMMITTED | 保留全部导入目录,继续清理本次拥有的原目录和暂存目录。 |
每个目录在目标旁、同一文件系统上暂存。创建暂存目录前,先在日志中登记 UUID。 移动原目录前,记录目标是否原本存在并添加所有权标记;导入目录使用另一种标记。 全部重命名成功后才写入全局提交。日志和标记通过同步临时文件加原子重命名写入。
恢复先持久化每个已解决状态,再清理相应证据;清理时最后移除所有权标记。 因此再次退出后可以重复执行。递归操作不跟随子软链接,不接受被重定向的根目录,也不为完成回滚而删除非空的外来目标目录。
可选存储不可用时保留 pending 状态,不尝试重建其卷。 内部应用目录必须在普通初始化之前恢复。原生方案加载、切换、新建、重命名和删除也会检查待处理路径,包含启动后才重新可用的存储。 冲突时保留两边目录,并打开最小恢复页,提供重试和只含路径/错误、不含文件正文的诊断文档。 保留原数据等待恢复时,不要清除应用数据。
偏好与 SQLite
Android 的 .xml.bak 优先于对应 XML。导入先规范化并校验该快照;替换主偏好时,移除旧安装数据对应的 .bak,避免下次启动用旧值覆盖已导入的新值。
复制数据库前旧进程已经退出。导入的剪贴板数据库先用其自身 WAL 恢复。 只为本次替换的数据库删除旧安装的侧文件;备份未包含的数据库,其原文件和 WAL 一并保留。
保证范围
该机制处理进程中断和普通 IO 失败。它不保证跨多个卷的物理断电原子性:Android 存储和硬件可能另有缓存,全部目录元数据也没有作为一个硬件事务同步。 文件系统丢失、外部卷拔出和外部修改所有权标记可能需要人工恢复。不会通过删除不认识的原数据来掩盖此类错误。
固定提交、主机进程中断、Android 交接和 WAL 读回的范围见恢复审计。