外观
Android 怎样展示流式回答,避免卡顿、重复和串消息?
标准回答
我会把网络接收、协议解析、消息状态和 UI 展示分开。网络读取到的一块字节不一定是一条完整事件,更不一定是一个 token;UTF-8 字符和 JSON 也可能跨块。先用正确的流式解码和协议解析器还原事件,再交给消息状态管理。
每次生成分配 requestId,并把增量关联到明确的 conversationId 和 messageId。数据层累计文本,维护生成中、完成、失败、已取消等状态;UI 只观察当前会话的有效状态。重复或迟到事件按服务协议里的序号、事件标识或本地任务版本处理,不能把所有收到的字符串无条件追加。
主线程不做大量 JSON 或 Markdown 解析,也不必每来一小段就重绑整个列表。我会合并短时间内的更新,只刷新正在生成的条目,必要时降低复杂富文本解析频率;最终完成时补齐最后一批内容。用户正在向上阅读时,也不强制每次滚动到底部。
用户取消、切换会话或页面销毁时,更新订阅与任务状态,并按任务归属决定是否取消网络请求。重连能否续传要看服务端协议:有事件标识不等于服务端一定保存历史;不支持续传时,明确中断或新建一次生成,不能把新回答误接到旧回答后面。
关键机制
- 网络分块、协议事件、模型 token 和一次 UI 更新是四种边界。
- 消息标识与任务版本防串流;序号或事件 ID 去重必须配合服务端语义。
- 合并更新、局部刷新、按生命周期订阅;完成和取消都要结束对应状态。
通俗例子
用户先问 A,随即取消并问 B。A 的最后一段稍后到达:状态层发现 requestId 已失效,丢弃这段 UI 更新。B 的回答继续展示,不会混入 A 的文本。
追问与回答
SSE 和 WebSocket 怎么选?
如果主要是客户端发起一次请求,服务端持续下发生成结果,基于 HTTP 的流式响应或 SSE 往往就够。需要持续双向交互的场景再评估 WebSocket。选择后还要定义心跳、取消、重连和业务事件;换一种传输方式并不会自动解决重复、错序或生命周期问题。
为什么不能每收到一个 token 就刷新?
首先客户端收到的事件不一定对应单个 token。其次高频刷新可能重复做文本排版、Markdown 解析和列表布局,导致主线程忙于中间状态。我会保持数据完整接收,但按短时间窗口或帧节奏合并可见更新,最终内容不丢,显示延迟则通过实测控制。
易混淆点
SSE 提供事件格式和可选事件 ID;它不自动提供服务端持久化、严格去重或任意 Android 客户端的断点续传。连接断开也不能擅自当作业务生成成功。
参考资料
内容来源
- Android 技术札记 · 原条目
streaming-ui