# App 原生缓存与账号生命周期

> 本页保留此前阶段的实现与验收范围。2026-10-04 已追加后端订单、App 多选/队列与离线播放器接线，当前进展见[缓存订单与页面](dramivio-offline-order-implementation.md)及[本轮复审](dramivio-offline-order-review.md)。真实签发器、完整 iOS 与真机仍待验收。

本轮将原生下载队列接到现有 App 账号流程，复用 Better Auth、Zustand/MMKV、Media3 和 Apple 系统下载。消费者下载入口仍关闭；本轮不代替来源许可、签发或真机验收。

## 当前行为

| 场景 | 处理 |
| --- | --- |
| 冷启动 | 原生任务里的 owner 不作为登录证明；队列暂停、播放锁定。JS 确认账号并完成该账号待清理操作后才配置原生 owner；配置不会自动恢复下载 |
| 退出、权威无会话、会话到期、账号终态 | 原账号进度、明确归属的文件和真实原生队列接到同一个清理函数；旧账号任务停止，离线播放器清空当前媒体项 |
| 换号 | 使用 origin + UID 清理旧账号。A 的迟到结果不能删除 B 的文件、查询或身份；A 清理失败也不阻止 B 配置自己的原生 owner |
| 临时身份服务故障 | 暂停原生访问，保留已有文件和原账号归属；不把网络错误当作已注销。重新确认同一账号后可恢复访问 |
| 清理未完成 | 现有缓存存储保留 pendingNativeCleanup，按原 origin + UID 重试；失败文件保持 cleanup_pending，不能继续播放 |
| 重启、回到前台、手动重试 | 重试原待清理账号，等待真实原生结果。我的页面只显示“本机缓存未清理完成，请重试”及重试按钮，不显示旧账号资料 |
| 同 UID、不同站点 | 原生队列、文件和进度按站点区分；换站点清理旧站点，不移除新站点的身份和查询 |
| 旧安装包缺少桥接 | 明确报告原生能力不可用，不把文件或队列擦除当作成功；这类原生修改需要重新构建安装包 |

## 清理和恢复

clearOwnerDownloads 使用现有缓存存储保存待清理账号，不新建下载器。原生 clearOwner 的 Promise 与手机旧文件清理均参与结果；同一账号的并发调用共用一次执行。JS 存储失败仍尝试停止/删除真实原生任务，但返回未完成；写入和移除均读取原记录核对，避免未保存的重试状态被当作成功。

原生清理成功不代表旧文件适配器也成功。文件失败记录继续锁定并参与重试；原生失败则保留原 owner 的待处理记录。清理期间禁止重新激活同一 owner；其他账号不受该 owner 的失败牵连。当前账号重试完成后无需重新登录即可重新配置原生 owner。

任意一项进度或 viewer 存储失败，仍执行内存身份失效、私有查询移除与原生清理，再等待结果并报告未完成。viewer 的订阅通知与本机来源状态重置不再被持久化错误跳过。

账号退出仍通过官方 Expo signOut 完成。它等待本机清理结果；本机清理错误不会阻止一次服务端退出，最终保留清理提示。注销页继续按原操作查询结果，清理错误不能把页面标成“已注销”。

## 原生播放边界

Android 原生 owner 为进程内状态，冷启动不读取旧 active 偏好恢复权限。清理原 owner 时先使当前访问失效，再写清理记录；偏好存储失败也不能恢复旧账号读取。原生 owner 变化通知当前离线播放器，复用 react-native-video 的 clearSrc 停止 ExoPlayer、移除媒体项及旧来源；异步来源准备必须仍是当前 source 才能挂载，避免旧加载结果重新播放。解除订阅随来源更换和组件销毁执行。

iOS 在同样的账号失效事件中，对当前离线 AVPlayer 执行 pause、replaceCurrentItem(nil)，并移除 item observer。准备中的本地 item 绑定原生账号、任务版本与当前加载请求；挂载前在同一次主线程执行中重新检查。退出后重新登录同一账号也不能挂载上次留下的 item。暂时写盘错误可在真实保存成功后恢复；损坏记录仍拒绝访问，不能默默重建为空。

仅禁止 DataSource 后续读取不能停掉已经缓冲的画面，因此播放器停止与读取校验分别执行。以上是本进程内账号变化的处理，不是远程即时撤销离线 DRM。

## 源码与调用

- src/auth/ConsumerAuthGate.tsx：确认账号、冷启动锁定、换号/到期/终态、前台重试和按账号配置原生 owner。
- src/store/downloads.ts：clearOwnerDownloads、retryOwnerDownloads、待清理账号及旧文件恢复。
- src/screens/MyScreen.tsx：中英文重试提示，不泄露原账号资料。
- src/playback/consumerProgress.ts、useConsumerProgress.ts：按 origin + UID 清理旧位置队列。
- react-native-video 的可安装补丁：真实原生 clearOwner、账号与播放器失效接线；不依赖单纯 OTA。

```ts
await clearConsumerAccount(queryClient, oldUserId, oldOrigin, currentOrigin);
const result = await retryOwnerDownloads();
```

两个函数均可普通导入和传参调用。原生集成检查继续使用已有 runOfflineNativeTests({ javaHome, androidHome, logPath })。

## 当前证据

App 全量 228 项及类型检查通过，新增账号/原生生命周期 16 项，另新增进度站点隔离检查。JS 测试使用实际 React、Zustand/MMKV 接口与官方 Expo SDK；原生桥接、文件和故障 HTTP 是明确适配器。覆盖待清理状态的序列化/重载、暂停与重新确认、A/B 迟到、存储抛错/未生效、原生清理失败、收据移除失败、原 owner 重试和中英文界面。

Android 19 项实际 Media3/SQLite/cache 集成通过；新检查覆盖持久 active 不能自动确认账号、guest 取消真实 DownloadHelper 准备、偏好 commit 失败后已打开读取拒绝，以及实际 ExoPlayer 媒体项停止/移除。媒体 transport 是受控合成字节；ExoPlayer 项目检查未运行解码器或屏幕渲染，不能写成已通过真机视频播放。

iOS 119 项真实 macOS Foundation/AVFoundation SDK 夹具通过，其中实际 AVPlayer 播放项达到 readyToPlay 后，由生产代码复用的 helper 停止并清空；包含账号、迟到请求、实际文件删除与暂时写盘故障恢复。RCTVideo 的 iOS target 语法 parse 通过；没有运行 UIKit/React wrapper 或完整 iOS 编译，也没有证明 iPhone 解码与画面。

Android 完整 assembleRelease 构建通过，检查实际 APK 的原生模块、服务清单、Hermes bundle 与四种 ABI。沿用项目现有 debug 签名、连接本机受控地址；未安装、未发布，不作为正式版本或真机验收。

来源后台任务、系统唤醒前已发生的网络请求、真实许可/密钥和 iOS 所有子请求隔离不能由此证明。

[独立复审](dramivio-offline-lifecycle-review.md)复现并关闭一项存储失败中断清理的问题；新增 JS 4 项、Android 3 项通过，另独立复跑作者相关 JS 29 项与 Android 19 项。范围不包含该 Agent 自己实现的 iOS 代码或真机。

## 仍需完成

O01 的来源能力、离线授权签发和额度、消费者缓存列表/多选队列接线；完整 iOS 构建与宿主后台 hook；Android/iOS 安装和飞行模式、seek、后台/强退、空间故障、系统逐出、真实许可/密钥清理。下载入口继续返回 offline_authorization_required。

网站视频不写磁盘的约束不变，原生缓存只写手机 App 沙盒。本轮没有发布真实 API、安装包或 OTA，也没有提交/推送仓库。
