客户端启动、恢复与资源生命周期
客户端生命周期包含本机数据加载、登录事务、已登录会话、增量刷新、消息重试、媒体连接和持续位置共享。它们的恢复条件不同:重新打开 App 不等于重新登录,关闭聊天页不等于结束通话或位置共享,进程内队列也不等于已持久化的离线发件箱。
启动入口与状态所有权
| 状态 | iOS 所有者 | Android 所有者 |
|---|---|---|
| 本机任务/收藏 | 根视图持有 LocalWorkspaceStore |
AppState 中可观察列表与 LocalData |
| 记忆 | 根视图持有 MemoryStore |
AppState.memories/memoryLocked |
| 聊天 token 与用户 | ChatStore,token 来自 Keychain |
AppState,token 来自加密文件 |
| 原生登录事务 | ChatNativeAuth 与独立 pending 存储 |
NativeAuthCoordinator 与加密 pending |
| 普通事件刷新 | ChatStore.pollingTask |
AppState.pollJob |
| 通话 | ChatCallCoordinator、系统来电桥接 |
AppState、LiveKit Room 与轨道任务 |
| 位置持续采集 | ChatLiveLocationManager |
独立 LiveLocationService |
iOS App 初始化 ChatStore 并绑定 CallSystemBridge;根视图清理旧记忆转出和预览临时文件,加载记忆与工作台快照。工作台读取失败会显示保护性错误,而不是空数据首页。随后 .task 先尝试恢复有 ticket 的原生登录,再恢复聊天会话。记忆加载失败有独立错误状态,不应触发卸载或重置。
Android MainActivity.onCreate 创建 AppState、处理初始 Intent,再组合页面;onNewIntent 负责已运行时的深链。状态初始化先加载普通本机列表和加密记忆,记忆读取异常会设置 memoryLocked;若已有 token,异步请求 /me 恢复资料、列表和轮询。授权回调与本机数据加载不应相互清除。
已登录会话恢复
正常恢复是:读取设备凭据,请求 /me,核对响应仍属于发起请求的 token,设置当前用户和 eventCursor,再刷新会话等快照并启动监听。服务端返回 401 表示凭据无效,客户端清理该聊天会话;断网、超时或 503 不足以证明 token 已失效,应保留凭据并显示错误。
iOS restore 在同一 Store 生命周期内只自动执行一次;回到 active 时会调用 resumePendingSignIn 和 recheckSession。后者主要在当前用户为空、Keychain 仍有 token 时恢复,不是每次前台切换都强制重新登录。Android 恢复在 AppState 初始化触发,普通网络操作的 401 清理也会核对发起会话,防止旧响应退出新账号。
设备聊天会话默认有效 24 小时,没有 refresh token 或永久登录保证。Account 浏览器 Cookie 与聊天 token 各自到期。修改 Account 密码没有本机记忆重加密逻辑;是否撤销已有聊天 token 还需按聊天服务自身会话机制判断,不能将 Account 会话撤销自动扩大到所有业务凭据。
pending 登录:两端的持久化差异
| 阶段 | iOS | Android |
|---|---|---|
| 打开浏览器前 | 持久化 verifier、state、expiresAt、API 基址 | 持久化 verifier、state、expiresAt |
| 回调校验 | 预期 scheme/host、参数唯一、state、期限、API 基址 | 预期 scheme/host、无异常 URL 部件、唯一参数、state、期限 |
| 换票前 | 把 ticket 写回 pending | 先清除 pending,再在内存中携带 ticket 换票 |
| 换票中进程终止 | 在 ticket 和 flow 仍有效时可尝试 resume() |
当前没有持久 ticket 的恢复入口,需要重新发起授权 |
| 成功替换会话 | 先保存 session,再尽力清 pending | 先加密保存新 token,再替换旧账号界面状态 |
| 取消或旧请求迟到 | generation 和当前 pending 复核 | generation、operation 和 exchanging 复核 |
iOS 的 pending expiresAt 与 Android 相同,是协议 Unix 毫秒,不是 Swift Date 默认编码。冷启动恢复仍受一次性票据和服务端期限约束;如果服务端已经消费 ticket 而响应丢失,保存过 ticket 也不代表总能无损重放。网络恢复与服务端幂等能力需分别看待。
两端授权入口信任规则也不同:iOS Debug 在明确使用回环聊天服务时允许回环 Account HTTP;Android 原生授权地址当前只允许正式 Account HTTPS,没有同等 Debug 本地例外。修改聊天 base URL 或转发端口不会自动增加授权来源。登录详细协议见认证,隔离条件见配置参考。
普通事件游标与断线重试
| 项目 | iOS | Android |
|---|---|---|
| 长轮询 | /events?after=<eventCursor>&wait=20 |
/events?after=<cursor>&wait=20 |
| 客户端请求超时 | 事件 32 秒、普通 JSON 15 秒 | 连接 15 秒;事件读取 35 秒、普通读取 20 秒 |
| 非 401 失败等待 | 约 3 秒 | 约 4 秒 |
| 成功响应 | 更新 cursor,再按事件类型刷新相关资源 | cursor 取不倒退的最大值,并刷新聊天、通话及当前相关页面 |
| 状态期限 | Store 实例中的内存游标 | AppState 实例中的内存游标 |
事件不是完整聊天数据库。iOS 在 message/group_message 后重新读当前会话并标记已读,call_changed 后查当前通话,成员和屏蔽变化触发相应资源重查;Android 每轮刷新聊天,当前是聊天页时刷新消息,新事相关页面时刷新动态。响应迟到时需核对原 token 或当前 thread 版本,避免旧会话覆盖新页面。
普通 cursor、消息 ID 与 MLS sequence 是不同序列。两端普通轮询没有持久化为一份可恢复的消息总账;冷启动先读服务器最新快照和 /me 的游标。iOS 独立 MLS 传输器另有加密 checkpoint 和 sequence 规则,不能把其持久离线能力归到普通聊天界面。协议见MLS与实时与通话。
消息发送、失败与队列寿命
iOS 为一次发送生成 clientId,在当前账号和会话范围的 pending 列表中显示 sending。收到响应后按服务端消息合并,并删除对应 pending;请求失败则标 failed。若轮询已经收到相同 clientId,迟到的失败不会再显示一条假失败。用户重试 failed 时复用原 clientId 和原正文/媒体引用;退出账号会清空这些内存队列。
Android MessageSendIdCoordinator 按 accountId + scopeKind + scopeId + text + mediaId 保存未确认请求的 ID。相同提交在不确定结果后重试时沿用 ID,明确响应成功后清除此映射;账号退出会清空。聊天输入草稿可由 rememberSaveable 管理,但 ID 映射仍是进程内状态,不构成磁盘发件箱。进程被系统销毁后,不应宣称所有待发消息都会自动继续发送。
维护者处理超时应区分:服务端未接受、已接受但响应丢失、消息已同步回来。继续使用原重试入口和 clientId,避免再建一条同内容请求。修改正文或媒体改变请求身份;使用同 ID 提交不同内容会触发服务端冲突。普通消息上限和幂等见消息与媒体。
前后台、通话与位置
源码中的普通轮询没有仅因切到后台就自动改成可永久运行的系统服务。iOS/Android 对后台执行各有限制,事件循环存在不表示锁屏时仍能持续联网;当前 Android 也没有完整后台来电推送交付。系统唤醒、媒体后台和一般消息刷新应分别验收。
通话媒体不是某个聊天页面的临时子视图。iOS 记录本机参与的 call ID,恢复 active 状态时避免自动接入另一设备才参与的通话;连接媒体先取短期 token,再连 LiveKit,开启轨道后反复核对连接 attempt 和当前 call。断开会取消订阅任务、释放 Room 引用并恢复音频输出;Android 用媒体 generation 与 call revision 防迟到,断开并 release Room,取消半秒级轨道观察任务。收起通话页面只是隐藏界面,结束需要走相应信令。
位置共享在显式开始后独立于聊天页面。iOS 启用后台位置更新,按至少约五秒间隔向仍共享的范围上传,断网时保留范围并显示最后更新时间;重启后查询 /live-locations/mine 再恢复。Android 前台服务带持续通知,约每 30 秒重查范围,位置上传至少约五秒间隔;权限撤销、无范围或 401 会停止或提示。服务返回 START_NOT_STICKY,不能承诺进程被杀后自动恢复服务。
停止位置采集与删除服务端最后坐标是两件事。Android 通知“结束共享”先停止采集,再尽力向服务器删除并重试;本机退出也会停止服务后尝试远端清理。iOS 退出先停止本机跟踪,但不能仅凭本机退出视为所有远端共享记录已删除。具体权限与位置语义见位置共享。
退出与资源检查步骤
- 取消原生 pending,阻止迟到登录结果覆盖状态;保留个人任务、收藏和记忆。
- 停止轮询,释放通话媒体、当前消息缓存和账号界面状态。Android 先清本机,随后尽力远端清理;iOS 尝试服务端注销后清本机。
- 成功的
/auth/logout只撤销当前聊天 session;请求失败时,本机退出仍可能完成,不能声称已撤销服务器凭据。 - Activity 销毁时 Android 调用
dispose,取消所属协程和通话资源;独立位置服务按自己的生命周期清理。浏览器、扫码相机与附件预览也需各自释放。 - 重开后分别验证本机数据仍在、无效会话需登录、短时离线未抹掉凭据,以及旧请求不写入新账号。
本页依据 iOS StarlineWorkbenchApp、ChatStore、ChatNativeAuth、ChatCallCoordinator、ChatLiveLocation,以及 Android MainActivity、AppState、NativeAuth、MessageSendIdCoordinator、LiveLocationService。本轮仅静态核对,未执行杀进程、后台、跨网或双设备测试;具体回归组合见QA矩阵。