Appearance
录音转写:录制进行中 + 断点上传 + 会话同步 + 推送回流
一句话定位
这是一个把 移动端录音、后台上传、服务端转写、结果同步、推送唤醒回流 串成完整链路的业务专题页,用来回答:一个真实可上线的“会议录音转文字”功能,后台任务到底该分层落在哪。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| Flutter 会话状态机 | recording-sync-state-machine | recording_sync_state_machine_demo.dart |
| Android 录音交接上传 | recording-upload-handoff | RecordingUploadHandoffActivity.kt |
| 推送丢失后的补拉恢复 | push-miss-sync-recovery | push_miss_sync_recovery.mjs |
为什么需要
录音转写类业务经常把多个后台问题误当成同一件事:
- 为什么“录音在后台继续”不等于“上传、转写、同步也都能一直在客户端稳定跑”?
- 一句话答:因为录音、上传、转写、回流的系统语义不同,只有录音更接近持续可感知任务,后几段必须拆给系统调度或服务端状态机。
- 为什么 Flutter 插件把录音、上传、推送都包起来后,仍不等于跨端能力已经拉平?
- 一句话答:Flutter 能统一业务状态与调用入口,但不能凭空拿到 Android / iOS 的后台执行授权。
- 为什么 silent push / 厂商推送 / 前台服务拼在一起,依然凑不出“准实时、永不丢”的保活模型?
- 一句话答:因为这些能力都不是最终真相源,推送可能丢,前台服务只限 Android 持续任务,iOS 还会进一步收紧机会窗。
- 为什么只靠客户端多写几层重试,不足以顶掉服务端状态机与幂等设计?
- 一句话答:因为多端登录、重装恢复、推送丢失与任务幂等都需要服务端持有统一状态与任务 ID 才能收敛。
真实线上事故通常是:
- 录音能继续,上传却在退后台后被中断
- Android 方案看起来成立,iOS 上却经常只能机会性补传
- 转写结果已经生成,客户端却因为没有稳定唤醒窗口而迟迟不同步
- 同一条音频被重复上传、重复转写、重复入库,最后用户看到多份会议纪要
所以这个场景很适合用来强化 03-background-tasks 的业务落位层:
- 录音 不等于 上传
- 上传 不等于 转写完成通知
- 推送唤醒 不等于 可靠后台同步
- 客户端后台能力 不等于 业务结果可达成
代码索引
当前专题页以业务落位决策为主,暂未绑定可运行实验;相关机制文档见 §底层机制 内的模块链接。
底层机制 / 决策
- Android 后台能力总览:WorkManager / ForegroundService / AlarmManager 对比
- Flutter 后台边界:Flutter 后台能力边界
- iOS 后台现实边界:iOS 后台能力现实边界
1. 先拆成四段,不要试图用一个能力兜全链路
对应 Lab:recording-sync-state-machine · recording_sync_state_machine_demo.dart
1.1 四段责任落位总图
下图刻意不用 Mermaid,而用文档内嵌 SVG:因为它同时要表达 四个业务阶段、Android / Flutter / iOS / 服务端四条责任泳道、以及阶段间交接关系。这类总装配图如果硬塞进 Mermaid,节点会很多、换行会挤、责任边界也不够直观;用 SVG 更容易长期维护语义与排版。
这张图解决的不是“阶段名词记忆”,而是一个更容易混淆的问题:到底是哪一端在每一段真正负责执行,哪一段开始必须把真相源移交给服务端。
录音转写至少要拆成四段:
- 录制阶段:麦克风采集、文件落盘、时长/波形/UI 状态展示
- 上传阶段:音频文件切片或整文件上传、失败重试、断点续传、网络约束
- 转写阶段:服务端异步转码、识别、摘要、结构化入库
- 回流阶段:客户端获知结果、刷新会话页、拉取纪要与状态
| 业务阶段 | 核心诉求 | Android 更像 | iOS 更像 | Flutter 更适合扮演什么 |
|---|---|---|---|---|
| 录制阶段 | 持续进行、用户可感知 | Foreground Service / 前台可见录音会话 | 合法后台音频模式内的录音会话 | 控制层、页面状态、权限交互 |
| 上传阶段 | 可靠完成、网络约束、断点续传 | WorkManager 或前台明确进行中的上传任务 | 系统允许的后台上传模型 / 机会性补传 | 发起、展示进度、恢复后查询状态 |
| 转写阶段 | 长耗时、可重试、幂等、可扩展 | 不该长期压在客户端 | 不该长期压在客户端 | 只展示服务端任务状态 |
| 回流阶段 | 尽快看到结果,但不承诺秒达 | 推送 + 前台拉取 + 机会补拉 | 推送 / 前台进入 / 系统机会窗补拉 | 会话刷新、结果落地展示 |
核心决策是:
- 录制要看用户感知与系统白名单
- 上传要看可靠完成与网络约束
- 转写必须服务端化
- 结果回流不要绑定到“客户端一定能被叫醒”这个幻想上
2. Android:录制与上传经常要分两个模型
对应 Lab:recording-upload-handoff · RecordingUploadHandoffActivity.kt
录音进行中时,Android 更接近前台服务语义;但上传阶段不一定继续使用前台服务。
如果满足这些条件:
- 只是“录音结束后补上传”
- 用户不要求必须盯着实时进度
- 允许联网后再传、失败后稍后重试
那么它更像 WorkManager:
- 带网络约束
- 可恢复
- 不要求持续前台常驻
只有在下面情况,上传才更像“进行中的前台任务”:
- 用户主动点击“立即上传,别退出”
- 文件很大,且用户明确期待看到持续进度
- 上传过程中如果中断,业务损失高,且产品接受通知常驻
这也是为什么要把“录制完成 → uploadSessionId 建立 → 本地文件延后清理”单独做成 handoff 样板,而不是把上传继续塞在录音页里。
3. iOS:录音能成立,不代表录音后的全链路后台都成立
iOS 的真实困难不是“录音 API 没有”,而是:
- 后台执行窗口由系统严格控制
- 后台上传/刷新不是任意时刻都能稳定继续
- silent push、BGTask、后台刷新都更像机会窗
因此 iOS 上要反向约束产品承诺:
- 允许录音完成后 不一定立刻上传完
- 允许转写结果 不一定秒级回流到本机
- 把“最终会同步成功”建立在 服务端任务状态机 + 客户端机会性补拉 上
4. Flutter:适合表达业务意图,不适合承担后台真执行
对应 Lab:recording-sync-state-machine · recording_sync_state_machine_demo.dart
在这个案例里,Flutter 更适合负责:
- 会话页状态机:
recording / waitingUpload / uploading / transcribing / synced / failed - 录音按钮、权限说明、上传进度、转写结果 UI
- 调宿主录音能力、上传任务注册、查询当前任务状态
- App 回前台后主动补拉会话详情
Flutter 不适合负责:
- 把 Dart isolate 当录音保活器
- 指望页面销毁后 Dart 逻辑继续稳定顶住长链路上传
- 把推送消息到达后的后台执行能力想成平台无差别
结论是:Flutter 在这个场景里最值钱的是统一业务状态,不是统一后台能力。
5. 服务端必须成为“真相源”,而不是客户端的被动附庸
对应 Lab:push-miss-sync-recovery · push_miss_sync_recovery.mjs
录音转写是典型的异步链路,服务端至少应承担:
- 上传会话
uploadSessionId - 音频文件
fileId - 转写任务
transcriptionJobId - 幂等键:避免重复提交生成多份任务
- 状态机:
pending_upload / uploaded / transcribing / partial_ready / done / failed - 失败原因与可恢复标记
如果这些都没有,客户端就会出现:
- 重试一次就重复建任务
- 多端登录时状态互相覆盖
- 推送丢失后无法靠主动拉取恢复
- 用户删了页面但服务端仍在继续转写,状态无法收敛
常见场景
场景 1:采访/会议录音,录制中允许切后台,录完后联网再传
特征:
- 用户更关心“录音别丢”而不是“录完秒传”
- 文件可能较大
- 现场可能无网或弱网
- 可以接受稍后转写完成
建议落位:
- Android:录制阶段走 Foreground Service;录制结束后上传交 WorkManager
- iOS:录音按合法音频能力设计;上传按机会性 / 系统允许模型处理
- Flutter:展示录制状态、上传中 / 待上传、转写排队中
- 服务端:兜底任务状态,不依赖客户端常驻
场景 2:用户点击“结束会议”后必须尽快把纪要发给群成员
特征:
- 时效要求比普通录音高
- 结果要发给其他人,不只是本机查看
- 即使当前设备掉线,也希望结果最终能产出
建议落位:
- 录制结束后立即触发上传
- 若客户端没传完,允许用户重新打开 App 后继续补传
- 转写与纪要生成全部服务端化
- 群成员收到结果依赖服务端消息/推送,不依赖原录制设备在线
这里最重要的不是“手机能不能一直后台跑”,而是:原始音频一旦安全入服务端,后续链路就不应再绑定单机存活。
场景 3:即时语音笔记,用户期待“录完马上看到文字”
特征:
- 对主观体验要求高
- 但平台后台能力仍然有限
建议落位:
- 前台内优先给快速反馈:本地先显示“处理中”
- 如果用户没离开页面,可走前台直传 + 轮询 / 流式回流
- 一旦退后台,立刻降级为“稍后同步完成后提醒我”模型
这类场景适合做 前台快路径 + 后台保守路径 双轨设计,而不是一条路径赌到底。
场景 4:跨设备查看录音纪要
特征:
- 用户可能在 A 设备录音,在 B 设备看结果
- 会话一致性比本机保活更重要
建议落位:
- 服务端维护统一任务状态
- 任意设备进入会话页时都可主动拉取
- 推送只是“加速看到结果”,不是唯一通知通道
这会自然把设计重心从“本机后台保活”转向“服务端真相源 + 多端同步模型”。
常见坑
| 坑 | 线上现象 | 为什么会发生 | 修法 |
|---|---|---|---|
| 把录音、上传、转写都塞进一个前台服务 / 插件 | Android 看似能跑,iOS 完全不稳 | 不同阶段系统语义不同 | 拆阶段:录制、上传、转写、回流分别建模 |
| 把 isolate 当后台上传方案 | 页面退后台或进程被挂起后上传丢失 | isolate 不是系统后台授权 | 上传交宿主能力或系统允许模型 |
| 依赖 silent push 做唯一回流 | 用户经常收不到已完成纪要 | push 只是机会型触发 | 加前台进入补拉、列表页轮询、手动刷新 |
| 客户端本地状态当真相源 | 重装 / 换机 / 多端登录后状态错乱 | 状态没服务端化 | 服务端维护任务状态机与幂等键 |
| 录音结束立刻删本地文件 | 上传失败后无法恢复 | 文件生命周期设计错误 | 直到服务端确认 uploaded 再清理本地 |
| 同一音频重复点提交 | 生成多份转写任务 | 没有幂等键 | 用会话 ID + 文件指纹做幂等控制 |
| 把“结果尽快到达”承诺成“后台一定秒达” | iOS 经常被投诉不稳定 | 超出系统边界 | 改成“尽快回流 + 下次进入必补齐” |
与相近概念对比
| 概念 | 它解决什么 | 在本案例里不该被误用成什么 |
|---|---|---|
| Foreground Service | Android 上持续用户可感知任务 | 全链路万能保活器 |
| WorkManager | 延迟、约束、可恢复的后台工作 | 录音进行中的持续执行器 |
isolate / compute | Dart 计算隔离 | 跨平台后台执行授权 |
| silent push | 结果回流的加速触发器 | 稳定后台时钟或必达唤醒器 |
| 后台音频模式 | 合法录音 / 播放场景白名单 | 任意业务后台常驻许可证 |
| 服务端转写任务 | 真正长期、可重试、可扩展处理链路 | 可有可无的“后补优化” |
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| recording-sync-state-machine | Flutter 会话状态机:上传、转写、推送丢失补拉 | recording_sync_state_machine_demo.dart |
| recording-upload-handoff | Android 录音结束后的 uploadSessionId 建立与本地文件生命周期 | RecordingUploadHandoffActivity.kt |
| push-miss-sync-recovery | 推送丢失后靠前台补拉与多端进入恢复结果 | push_miss_sync_recovery.mjs |
复习检查题
为什么“会议录音转写”是一个适合讲后台任务分层的高价值业务案例?
答:因为它把录制、上传、服务端异步处理、结果回流四种完全不同的系统语义串在了一起,能逼着我们区分哪些该落客户端持续任务,哪些该落系统调度,哪些必须服务端化,哪些只能机会性同步。
为什么录音结束后的上传,不应该天然继续沿用录音阶段的前台服务模型?
答:因为录制阶段强调持续进行与用户可感知,而上传阶段更强调可靠完成、联网约束、断点续传与失败恢复。两者系统语义不同,很多情况下上传更适合交给可恢复的后台调度,而不是继续占用持续前台执行。
为什么推送回流在这个案例里只能算“加速器”,不能算“真相源”?
答:因为推送到达与否、何时到达、到达后是否获得足够执行窗口,都受平台与设备状态影响。真正的结果状态必须由服务端保存,客户端只能靠推送加速感知,再用前台进入或机会性补拉收敛状态。
为什么跨端实现要默认以 iOS 的保守边界来约束产品承诺?
答:因为 Android 上看似可以通过前台服务、调度器和通知把链路做得更激进,但 iOS 对后台执行窗口、刷新时机和静默唤醒都更严格。若产品承诺建立在 Android 最强模型上,跨端上线后就会在 iOS 上持续暴露稳定性问题。
速记
- 录音转写不是一个后台任务,而是四段链路
- 录制看持续感知,上传看可靠完成,转写看服务端,回流看机会补齐
- Flutter 统一业务状态,不统一系统授权
- 推送是加速器,不是保活器
- 真正要稳定,必须让服务端成为任务真相源