IM 聊天中的表情消息怎么实现:Emoji 直发与自定义表情包两种方案

表情消息是 IM 产品里使用频率最高的消息类型之一,也是带动聊天气氛、塑造品牌感的常用手段。但接入时很多团队会在一个问题上纠结:表情到底应该怎么发——是把 emoji 直接当文本,还是把表情包当成图片消息上传?这两种做法对应完全不同的实现路径和成本。这篇基于 ZEGO ZIM 官方“发送表情消息”最佳实践,拆解两种方案各自的实现方式与适用边界。

表情消息的两种形态,对应两条实现路径

ZIM 对表情消息的处理方式,取决于表情的类型。表情消息通常分两种,实现路径完全不同。

表情类型实现方式说明
Emoji 表情直接作为文本消息发送SDK 原生透传,无需额外接口
自定义表情包业务侧自建表情库 + 自定义消息(type = 200)承载ZIM 只负责消息收发与存储

先明确 ZIM 的边界:ZIM 不提供表情面板、表情资源管理和表情渲染,表情库的维护(资源上传、版本管理、下发客户端)由业务客户端自行实现,ZIM 承担的是“把表情作为消息发出去、收进来、存进历史消息”的角色。理清这条边界,下面的实现方案就很好理解了。

方案一:Emoji 直接走文本消息,零额外接入

Emoji 就是普通 Unicode 字符,直接发文本消息(type = 1)即可,SDK 原生透传,不需要任何额外接口。对大多数只需要系统 emoji 的场景,这条路成本最低。发送时有几个限制需要提前知道:

限制值对表情的影响
消息大小文本消息默认上限 2 KB(可联系技术支持配置至 32 KB)Emoji 采用 UTF-8 编码后单字符最多 4 字节,2 KB 约可容纳 500 个 emoji + 文字
发送限频10 次/秒(间隔 100 ms)连发表情时注意客户端节流
空白消息空内容返回错误 6000001纯 emoji 属于合法内容,可正常发送
渲染兼容性与 ZIM 无关老操作系统上的新版本 Unicode emoji 可能显示为方框,表情面板建议按客户端操作系统的版本进行过滤

方案二:自定义表情包,用引用式设计承载高频表情

品牌化的系列表情、大贴图、GIF 不在系统 emoji 里,需要业务自己定义。ZIM 的推荐做法是统一用自定义消息(type = 200)承载,消息体只放“表情 ID + 资源 URL + 宽高”,资源本体由业务 CDN 承载。

之所以用引用式设计,是因为表情是引用型资源——同一表情会被成千上万次发送。如果每次发送都上传图片,会占用大量富媒体存储;而自定义消息只发引用,同一表情发送一万次也只有一万条小消息,资源本体始终只有 CDN 上一份。

{
  "packId": "pack_2026_spring",
  "emojiId": "e_10086",
  "url": "https://cdn.example.com/emoji/e_10086.gif",
  "width": 240,
  "height": 240
}

消息以自定义消息发出后,接收端能否正常解析,取决于对方的 SDK 版本:

接收端 SDK 版本表现建议处理
≥ 2.8.0正常收到自定义消息按 subType 解析渲染
[2.0.0, 2.8.0)可收到消息,但类型未知、无法获取内容UI 显示“[表情]”占位,引导升级
1.x.x收不到该消息无需处理

表情库的维护,是业务侧的核心工作

ZIM 只负责消息收发与存储,表情库的“资源上传、版本管理、下发客户端”需要业务侧自己实现。一个可用的表情库,核心是下面四个动作:

  • 清单下发:启动或进入表情面板时请求表情包清单,按 version 对比本地缓存,增量下载新表情到本地目录;
  • 发送只引用:发送时只引用 emojiId + url,不携带资源本体;
  • 接收回写缓存:接收渲染时优先查本地表情库(按 emojiId),本地没有再按 url 走 CDN 加载,加载成功后写回本地缓存;
  • 下架处理:清单中移除即可,历史消息中的表情按 URL 仍可加载;若要彻底失效需 CDN 侧撤资源,注意历史消息观感。
{
  "packId": "pack_2026_spring",
  "version": 5,
  "emojis": [
    { "emojiId": "e_10086", "file": "e_10086.gif", "width": 240, "height": 240, "order": 1 }
  ],
  "cdnBase": "https://cdn.example.com/emoji/pack_2026_spring/"
}

接入最佳实践

把上面两条路径落地时,以下要点直接影响接入质量和用户体验:

  • 消息体瘦身:自定义消息默认上限 2 KB(可配置至 32 KB),表情消息体只放 ID + URL + 宽高,不要把 base64 图片塞进消息体。
  • subType 规划:表情包消息分配独立 subType(如 3),与红包、投票等其他自定义消息区分,接收端按 subType 分发解析。
  • 宽高占位:消息体带 width/height,接收端先按宽高占位再异步加载,避免聊天页滚动跳动。
  • GIF 注意:ZIM 不做 GIF 解析,GIF 播放由客户端 UI 组件实现,大 GIF 建议压缩后上传 CDN。
  • 限频与节流:连发表情同样受 10 次/秒限频(文本/自定义消息同级),表情面板建议做点击节流。
  • 历史消息:表情消息可存储为历史消息,翻看历史时按消息体中的 URL/本地缓存渲染即可。
  • 低版本降级:接收端 < 2.8.0 显示“[表情]”占位,不要尝试对未知类型消息做内容解析。
  • searchedContent:发送自定义表情时建议设置 searchedContent(如“[表情:开心]”),本地搜索才搜得到这条消息。

接入示例

Emoji 直接作为文本消息发送:

ZIMTextMessage textMessage = new ZIMTextMessage("今天天气不错 😄🏖️");
zim.sendMessage(textMessage, toUserID, ZIMConversationType.PEER, config, new ZIMMessageSentFullCallback() {
    @Override public void onMessageAttached(ZIMMessage message) {}
    @Override public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {}
    @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) {}
});

自定义表情包发送与接收端分发:

// 发送:消息体只放"表情 ID + 资源 URL + 宽高"
String stickerJson = "{\"packId\":\"pack_2026_spring\",\"emojiId\":\"e_10086\"," +
    "\"url\":\"https://cdn.example.com/emoji/e_10086.gif\",\"width\":240,\"height\":240}";
ZIMCustomMessage stickerMessage = new ZIMCustomMessage(stickerJson, 3); // subType = 3:业务自定义的表情包消息
stickerMessage.searchedContent = "[表情:开心]"; // 建议设置:本地搜索只取该字段

zim.sendMessage(stickerMessage, toUserID, ZIMConversationType.PEER, config, new ZIMMessageSentFullCallback() {
    @Override public void onMessageAttached(ZIMMessage message) {}
    @Override public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {}
    @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) {}
});

// 接收端:统一消息回调中按 type / subType 分发解析
zim.setEventHandler(new ZIMEventHandler() {
    @Override
    public void onMessageReceived(ZIM zim, ZIMMessageReceivedEventResult result) {
        for (ZIMMessage msg : result.getMessageList()) {
            if (msg.getType() == ZIMMessageType.CUSTOM) {
                ZIMCustomMessage custom = (ZIMCustomMessage) msg;
                if (custom.subType == 3) {
                    // JSON 解析:优先查本地表情库(按 emojiId),本地没有再按 url 加载并回写缓存
                }
            }
        }
    }
});

结语

表情消息的实现,本质是两条路径的选择:系统 emoji 直接走文本消息,成本最低;品牌化表情包走自定义消息的引用式设计,把高频资源放在 CDN 上,消息体始终轻量。ZIM 在这条链路上负责可靠的消息收发、存储与历史同步,表情库和渲染由业务侧按自己的产品形态自建。接入时的关键,是把消息体瘦身、subType 规划、低版本降级这些细节做对。

本文基于 ZIM 开发者中心“发送表情消息”最佳实践整理,接口与限制以官方文档为准:ZIM Android 发送表情消息

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

赞 (0)

相关推荐