消息概览
了解 OpenIM Flutter SDK 的 Message 模型、消息类型和消息生命周期。
OpenIM Flutter SDK 的消息能力围绕 Message 展开。普通消息以及由本地路径或字节创建的媒体消息,使用对应的 create*Message() 创建,再通过 sendMessage() 发送。媒体文件已经由业务侧上传并取得 URL 时,使用 create*MessageByURL() 创建消息,再通过 sendMessageNotOss() 发送。两条路径都可以发送到单聊或群聊;新消息、撤回、删除、已读回执、上传进度和会话变化通过对应 listener 同步到应用状态。
Flutter 的 Message 模型没有 conversationID 字段。历史、搜索和会话状态使用的 conversationID 来自当前会话上下文;全局 listener 收到跨会话消息时,应根据 sessionType、groupID、sendID、recvID 和当前登录用户解析会话目标,再用 getConversationIDBySessionType() 校准。
消息页通常需要长期保存以下标识:
| 标识 | 用途 |
|---|---|
clientMsgID | 消息在客户端侧的稳定 ID,用于渲染去重、状态更新、查询和分页游标;字段为空时不能作为合并键。 |
conversationID | 消息所属会话的记录 ID,用于读取历史消息、清理未读数和搜索;它来自会话上下文,不是 Message 字段。 |
recvID | 单聊发送目标用户 ID;发送群聊消息时不传。 |
groupID | 群聊发送目标群组 ID;发送单聊消息时不传。 |
消息类型
不同内容先通过对应的创建方法生成 Message。发送层不需要为每种内容维护不同入口,统一把创建方法返回的消息对象传给 sendMessage() 或 sendMessageNotOss()。
消息类型对比
| 内容类型 | 创建方法 | 内容元素 |
|---|---|---|
| 普通文本 | createTextMessage() | textElem |
| @ 文本 | createTextAtMessage() | atTextElem |
| 图片 | createImageMessage()、createImageMessageFromFullPath() 或 createImageMessageByURL() | pictureElem |
| 音频 | createSoundMessage()、createSoundMessageFromFullPath() 或 createSoundMessageByURL() | soundElem |
| 视频 | createVideoMessage()、createVideoMessageFromFullPath() 或 createVideoMessageByURL() | videoElem |
| 文件 | createFileMessage()、createFileMessageFromFullPath() 或 createFileMessageByURL() | fileElem |
| 自定义消息 | createCustomMessage() | customElem |
| 引用、合并、位置、名片和表情 | 对应 create*Message() | quoteElem、mergeElem、locationElem、cardElem、faceElem |
从本地路径或字节创建的媒体消息通常交给 SDK 上传并使用 sendMessage();媒体已经由业务上传并取得 URL 时,使用 create*MessageByURL() 创建,再调用 sendMessageNotOss(),避免重复进入 SDK 上传链路。
需要被其他成员收到的业务数据应放入消息内容或服务端扩展字段;只影响当前设备展示的状态可写入 localEx。
Message 常用字段
| 字段 | 说明 |
|---|---|
clientMsgID | 客户端消息稳定标识,用于发送状态、查询和事件合并。 |
serverMsgID | 服务端确认后的消息标识。 |
sendID / recvID / groupID | 发送者、单聊接收者和群组目标。 |
sessionType | 单聊或群聊会话类型;用于解析消息所属会话。 |
contentType | 消息内容类型,使用 MessageType 的数字值判断。 |
status | 消息发送状态。 |
sendTime / createTime | 服务端发送时间与本地创建时间。 |
isRead | 当前消息的已读状态。 |
textElem、pictureElem、soundElem、videoElem、fileElem | 各消息类型的内容对象。 |
atTextElem、quoteElem、mergeElem、customElem | @、引用、合并和自定义内容。 |
offlinePush | 当前消息的离线推送配置。 |
ex / localEx | 服务端扩展与当前设备本地扩展。 |
clientMsgID、sendID、recvID、groupID 和 sessionType 在 Dart 模型中都是 nullable。写入索引前必须检查必要字段,不能用 ! 把异常载荷变成运行时崩溃。
消息生命周期与会话归属
- 使用
createTextMessage()、createImageMessage()等工厂创建Message;创建本身不发送,也不触发新消息事件。 - 使用
sendMessage()发送,或在自定义上传后使用sendMessageNotOss()。 - Future 成功后使用返回的
Message更新发送端状态;其他端通过消息 listener 接收增量。 - 历史读取、搜索和按 ID 查询返回当前本地快照,不触发新消息事件。
当前聊天页已经持有 conversationID 时,直接把查询结果和发送结果合并到该会话。全局接收事件没有这一上下文,应先解析 sourceID:群聊使用非空 groupID;单聊根据 sendID 是否等于当前登录用户,在 recvID 与 sendID 中选择对端用户 ID。随后把 sourceID 和 sessionType 传给 getConversationIDBySessionType()。
解析所需字段缺失时跳过该条消息并记录非敏感诊断信息。不要使用数组位置、展示名称或当前列表长度代替会话与消息标识。
按主题查看能力
发送消息
先创建消息,再发送到单聊或群聊。单聊填写 userID,群聊填写 groupID,另一个目标字段不传。
媒体文件由 SDK 上传时,使用本地路径或字节对应的创建方法与 sendMessage();媒体文件已由业务上传时,使用 create*MessageByURL() 与 sendMessageNotOss()。两条路径都以 Future 返回的 Message 更新发送端状态,并按非空 clientMsgID 合并。
完整的参数、错误处理和发送边界见发送消息。图片、音频、视频、文件及富消息已经按类型拆分在“创建消息”菜单中。
接收消息
普通实时消息、登录后的离线同步消息和只在线消息分别由 onRecvNewMessage、onRecvOfflineNewMessage 与 onRecvOnlineOnlyMessage 处理。全局 listener 应先解析消息所属会话,再按 conversationID:clientMsgID 幂等合并;只在线消息不进入本地历史。
完整接收和异常载荷处理见接收消息。
获取消息
历史消息通过 getAdvancedHistoryMessageList() 按 conversationID 分页读取。第一页不传 startMsg;继续加载时使用边界消息作为下一页起点。历史结果与 listener 可能包含同一消息,必须使用相同的 clientMsgID 去重规则。
搜索消息
searchLocalMessages() 搜索当前账号已经同步到本地的消息。群聊搜索使用群会话的 conversationID,不是发送消息时的 groupID;固定实现未使用的筛选字段不能描述为已经生效。
搜索流程见搜索消息。
管理消息
已发送消息可以按实际能力执行转发、合并、删除、撤回、修改、置顶和清空历史,也可以插入只供本地展示的消息或发送输入状态。删除、撤回和修改事件都应按 conversationID:clientMsgID 更新原消息;置顶列表按 conversationID 替换,再按 clientMsgID 去重。
相关能力见创建转发消息、创建合并消息、删除消息、撤回消息、修改消息、置顶或取消置顶消息、插入本地单聊消息、清理全部本地消息和上报输入状态。
标记已读
会话未读数通过 markConversationMessageAsRead() 清理。固定 Flutter SDK 支持接收单聊已读回执,但没有 WASM 的群消息成员级已读回执上报、已读/未读成员查询和群回执 listener,不能根据会话未读数推断群成员阅读情况。
单聊已读回执的处理也归入标记会话已读。
提及其他用户
群聊 @ 消息使用 createTextAtMessage() 创建,再通过 sendMessage() 发送到群组。提及目标使用稳定 userID;接收端从 atTextElem 渲染成员,并通过会话的 groupAtType 展示 @ 提醒。
相关流程见创建 @ 消息。
获取未读数
会话列表、总未读数和 @ 提醒来自会话 API 与 OnConversationListener。会话层按 conversationID 合并变化,并使用最新总未读计数更新全局角标;消息页面不应设置第二个会话 listener。
相关能力见维护总未读数。
自定义消息与扩展数据
需要同步给其他成员的附加数据,在发送前写入自定义消息或服务端扩展字段;只影响当前设备展示的数据写入 localEx。setMessageLocalEx() 写入完整字符串,不会自动合并旧值,也不会产生共享消息事件。
将音频转为文字
speechToText() 接收当前设备可读取的音频文件路径并返回 nullable 的识别文字。Flutter 的 speechToTextCapabilities() 只返回 Future<void>,不公开 WASM 能力对象中的格式、采样率、时长和大小字段。
转写结果可以通过 setMessageLocalContent() 写入语音消息的 soundElem.text,但只保存到当前设备,不会同步给其他成员或触发共享消息事件。完整边界见将音频转为文字。
事件归属
消息事件按职责分散在对应能力页,概览页只说明导航和归属:新消息和离线同步见接收消息,删除见删除消息,撤回见撤回消息,修改见修改消息,置顶见置顶或取消置顶消息,单聊已读回执见标记会话已读,输入状态见上报输入状态。会话变化的完整监听见获取会话列表。
创建消息对象和纯查询 API 直接使用 Future 返回值建立对象或快照,不应描述为触发共享事件。会改变状态的调用应分别处理 Future 结果、事件增量与重新查询校准。
Flutter enterprise SDK 公开消息修改、置顶会话消息和音频转写能力;这些页面的 API、model 与 listener 以固定 enterprise SDK commit 核对。当前 SDK 没有公开定向群消息创建 API,不应在客户端文档中模拟该能力。
这个页面有帮助吗?