ZIM 最佳实践:用信令消息实现“正在输入”状态提示

正在输入状态提示(Typing Indicator)是消息通讯场景中提升沟通实时感的关键细节。此状态提示可以让消息接收方感知到对方正在编辑消息,减少 “等待回应” 的焦虑。

ZIM(ZEGO 即时通讯)已提供完整的官方接入方案,核心结论先行:Typing 是瞬时状态而非消息,因此选用信令消息(ZIMCommandMessage,type = 2)承载,配合 3s 保活心跳与 10s 超时兜底,即可实现体验完备的“正在输入”提示。

正在输入状态效果展示:

场景预期表现
用户 A 输入文字用户 B 立即看到 “对方正在输入…”。
用户 A 持续输入每 3s 保活一次,提示持续显示 。
用户 A 停止输入1s 后发送 stop,用户 B 的提示隐藏。
stop 信号丢失(弱网)用户 B 在 10s 超时后自动隐藏提示(关键验证项)。
历史消息检查A、B 双方翻看历史消息,均看不到任何 Typing 记录(关键验证项)。
会话列表检查会话列表最新消息不被 Typing 污染(关键验证项)。
离线用户离线期间不收到 Typing,上线后不收到过期 Typing 。
群聊(小群)多位成员输入时分别展示各自状态。

为什么选择信令消息

Typing 是瞬时状态,不是消息

“正在输入”与普通聊天消息有本质区别:它不进入历史消息、不产生数据留痕、也不应出现在会话列表的最新消息展示中。基于这一约束,方案采用 ZIM 的信令消息(ZIMCommandMessage,type = 2)实现,其特点与 Typing 的语义完全匹配:

  • 不可存储,天然无留痕:离线用户上线后不会收到过期 Typing,无需额外处理。
  • 不进入历史消息与会话最新消息展示。
  • 支持 30 次/秒的高发送频率,足以支撑保活节奏。
  • 不保证送达与顺序的语义与瞬时状态匹配——丢包不影响体验。
  • 信令不可靠,因此使用 3s 保活心跳对抗丢包,优先保活而非节流。

消息类型选型对比

消息类型type是否可存储为历史消息限频(单客户端)
信令消息 ZIMCommandMessage2不可存储30 次/秒(间隔 34ms)
弹幕消息 ZIMBarrageMessage20不可存储无限制(仅房间)
文本消息 ZIMTextMessage1可存储10 次/秒(间隔 100ms)
自定义消息 ZIMCustomMessage200可存储10 次/秒(间隔 100ms)

为什么不使用自定义消息(type = 200)承载 Typing?自定义消息属可存储消息,会导致 Typing 信号进入历史消息列表、出现在会话列表的最新消息展示中。接入时务必确认消息类型为 type = 2。

实现方案

信令消息格式

信令消息的 message 字段为二进制数据,业务侧需要自行序列化。消息可以用一个 JSON 表示:

// 正在输入(含保活心跳)
{ "bizType": "typing", "action": "typing", "ts": 1725000000000 }
// 停止输入
{ "bizType": "typing", "action": "stop", "ts": 1725000005000 }
字段说明
bizType业务标识,固定 “typing”,用于与其他业务信令区分
actiontyping(输入中)/ stop(停止)
ts时间戳,接收端可用于防乱序(信令不保证顺序)

发送侧状态策略

策略时机说明
首条立即发送用户输入第一个字符时保证提示快速出现
保活心跳持续输入期间每 3 秒一次对抗丢包;即使丢 1~2 条也能被下一条补上
停止信号停止输入 1 秒后 / 输入框失焦 / 清空主动通知对端隐藏
发送节流两次发送间隔 ≥ 34ms(30 次/秒限频)保活 3 秒的节奏远低于限频,无需额外处理

消息时序

发送端负责首条立即发送 + 3s 保活 + stop,接收端负责展示 + 隐藏 + 10s 超时兜底,两端配合完成完整闭环:

ZIM 最佳实践:用信令消息实现“正在输入”状态提示

UI 状态展示

正在输入状态触发条件UI 表现
隐藏初始状态 / 收到 action: stop无提示
展示中收到 action: typing展示”对方正在输入…”
超时隐藏距最后一条 typing 信号超过 10 秒自动隐藏

接入示例

发送端:构造并发送 typing 信令

// 构造 typing 信令并发送(信令消息为二进制,需自行序列化)
String payload = "{\"bizType\":\"typing\",\"action\":\"typing\",\"ts\":" + System.currentTimeMillis() + "}";
ZIMCommandMessage commandMessage = new ZIMCommandMessage();
commandMessage.message = payload.getBytes();

ZIMMessageSendConfig config = new ZIMMessageSendConfig();
config.priority = ZIMMessagePriority.LOW; // 低优先级,避免抢占业务消息的发送队列

zim.sendMessage(commandMessage, toUserID, ZIMConversationType.PEER, config, new ZIMMessageSentFullCallback() {
    @Override
    public void onMessageAttached(ZIMMessage message) {}

    @Override
    public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
        // typing 丢失不影响主流程,失败静默处理
    }

    @Override
    public void onMediaUploadingProgress(ZIMMediaMessage message, long currentFileSize, long totalFileSize) {}

    @Override
    public void onMultipleMediaUploadingProgress(ZIMMultipleMessage message, long currentFileSize,
        long totalFileSize, int messageInfoIndex, long currentIndexFileSize, long totalIndexFileSize) {}
});

接收端:处理信令并更新 UI

// 接收端:仅处理信令消息,反序列化后按 action 更新 UI,10s 超时兜底
zim.setEventHandler(new ZIMEventHandler() {
    @Override
    public void onMessageReceived(ZIM zim, ZIMMessageReceivedEventResult result) {
        for (ZIMMessage msg : result.getMessageList()) { // 注意:messageList 为 private 字段,需走 getter
            if (msg.getType() != ZIMMessageType.COMMAND) continue;
            ZIMCommandMessage cmd = (ZIMCommandMessage) msg;
            String payload = new String(cmd.message, StandardCharsets.UTF_8);
            // JSON 解析后:action = typing 展示、action = stop 隐藏;发送者用 msg.getSenderUserID() 获取
        }
    }
});

群聊场景扩展

群聊场景同样支持“正在输入”状态提示,使用时强烈建议遵守以下要求:

  1. 规模控制:推荐在群成员 ≤ 50 的小群启用,大群建议关闭或仅对最近活跃成员展示。
  2. 多状态维护:按 senderUserID 维护多份状态,可展示“张三、李四正在输入…”。
  3. 降频与降级:Typing 为不可存储信令,不产生历史消息存储开销,但会占用共享的信令限频额度。多人高频输入时建议降低保活频率(如 3s → 5s)或设置独立降级开关。

结语

“正在输入”提示的实现关键在于瞬时状态的定位:选对消息类型(信令消息 type = 2),用 3s 保活对抗丢包、用 10s 超时兜底残留、用 stop 信号及时隐藏,即可在最低成本下获得完整的实时沟通体验。接入后建议按文章开头的关键验证项逐项自测:stop 丢失后的超时隐藏、历史消息无留痕、会话列表不被污染。

参考资料:ZEGO 开发者中心 · 实现「正在输入」状态提示

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/72502.html

赞 (0)

相关推荐