跳转到主要内容
消息管理器负责消息的增删改查、新消息监听、刷新消息监听、消息入库、发送消息回执监听、监听同步某个聊天数据等。

发送消息

基础发送方法

/**
 * 发送消息
 * @param textContent 消息正文
 * @param channelID 投递的频道ID
 * @param channelType 投递的频道类型(个人频道,群频道,客服频道等等)
 */
WKIM.getInstance().getMsgManager().sendMessage(textContent, channelID, channelType);
SDK 内置频道类型可通过 WKChannelType 查看

文本消息

// 定义文本消息
WKTextContent textContent = new WKTextContent("你好,悟空");
// 发送消息
WKIM.getInstance().getMsgManager().send(textContent, channel);

图片消息

// 定义图片消息
WKImageContent imageContent = new WKImageContent(localPath);
// 发送消息
WKIM.getInstance().getMsgManager().send(imageContent, channel);
在构建图片消息正文时,无需传递图片的高宽。SDK 会自动获取图片高宽

完整发送示例

public class ChatActivity extends AppCompatActivity {
    
    private String channelID;
    private byte channelType;
    
    // 发送文本消息
    private void sendTextMessage(String content) {
        WKTextContent textContent = new WKTextContent(content);
        WKIM.getInstance().getMsgManager().send(textContent, channelID, channelType);
    }
    
    // 发送图片消息
    private void sendImageMessage(String imagePath) {
        WKImageContent imageContent = new WKImageContent(imagePath);
        WKIM.getInstance().getMsgManager().send(imageContent, channelID, channelType);
    }
    
    // 发送语音消息
    private void sendVoiceMessage(String voicePath, int duration) {
        WKVoiceContent voiceContent = new WKVoiceContent(voicePath, duration);
        WKIM.getInstance().getMsgManager().send(voiceContent, channelID, channelType);
    }
    
    // 发送位置消息
    private void sendLocationMessage(double latitude, double longitude, String address) {
        WKLocationContent locationContent = new WKLocationContent(latitude, longitude, address);
        WKIM.getInstance().getMsgManager().send(locationContent, channelID, channelType);
    }
}

自定义消息

参考自定义消息: 自定义消息

消息入库监听

在发送消息时,SDK 将消息保存在本地数据库后就会触发入库回调。此时消息并未进行发送,可在此监听中将消息展示在 UI 上。
WKIM.getInstance().getMsgManager().addOnSendMsgCallback("key", new ISendMsgCallBackListener() {
    @Override
    public void onInsertMsg(WKMsg wkMsg) {
        // 可以在这里将保存在数据库的消息`wkMsg`展示在UI上
        runOnUiThread(() -> {
            addMessageToUI(wkMsg);
        });
    }
});
关于事件是否传入唯一 key 说明请查看事件监听

收到新消息监听

// 添加监听
WKIM.getInstance().getMsgManager().addOnNewMsgListener("key", new INewMsgListener() {
    @Override
    public void newMsg(List<WKMsg> list) {
        // list:接收到的消息
        runOnUiThread(() -> {
            handleNewMessages(list);
        });
    }
});

// 退出页面时移除监听
WKIM.getInstance().getMsgManager().removeNewMsgListener("key");
如果在聊天页面内收到新消息时需判断该消息是否属于当前会话,可通过消息对象 WKMsgchannelIDchannelType 判断

新消息处理示例

public class ChatActivity extends AppCompatActivity {
    
    private List<WKMsg> messageList = new ArrayList<>();
    private MessageAdapter messageAdapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 添加新消息监听
        WKIM.getInstance().getMsgManager().addOnNewMsgListener("ChatActivity", new INewMsgListener() {
            @Override
            public void newMsg(List<WKMsg> list) {
                handleNewMessages(list);
            }
        });
    }
    
    private void handleNewMessages(List<WKMsg> newMessages) {
        for (WKMsg msg : newMessages) {
            // 判断是否是当前会话的消息
            if (msg.channelID.equals(this.channelID) && msg.channelType == this.channelType) {
                runOnUiThread(() -> {
                    messageList.add(msg);
                    messageAdapter.notifyItemInserted(messageList.size() - 1);
                    
                    // 滚动到最新消息
                    recyclerView.scrollToPosition(messageList.size() - 1);
                    
                    // 标记消息为已读
                    markMessageAsRead(msg);
                });
            }
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除监听器
        WKIM.getInstance().getMsgManager().removeNewMsgListener("ChatActivity");
    }
}

刷新消息监听

在 SDK 更新过消息时,如:消息发送状态、有人点赞消息、消息已读回执、消息撤回、消息被编辑等等,SDK 都将回调以下事件。UI 可通过消息对象 WKMsgclientMsgNO 来判断具体是哪条消息发生了更改。
// 添加刷新监听
WKIM.getInstance().getMsgManager().addOnRefreshMsgListener("key", new IRefreshMsg() {
    @Override
    public void onRefresh(WKMsg wkMsg, boolean isEnd) {
        // wkMsg:刷新的消息对象
        // isEnd:为了避免频繁刷新UI导致卡顿,当isEnd为true时再刷新UI
        if (isEnd) {
            runOnUiThread(() -> {
                refreshMessageInUI(wkMsg);
            });
        }
    }
});

// 退出页面时移除刷新监听
WKIM.getInstance().getMsgManager().removeRefreshMsgListener("key");

消息刷新处理示例

private void refreshMessageInUI(WKMsg updatedMsg) {
    // 根据clientMsgNO找到对应的消息并更新
    for (int i = 0; i < messageList.size(); i++) {
        WKMsg msg = messageList.get(i);
        if (msg.clientMsgNO.equals(updatedMsg.clientMsgNO)) {
            messageList.set(i, updatedMsg);
            messageAdapter.notifyItemChanged(i);
            break;
        }
    }
}

消息发送状态码(ReasonCode)

当消息发送后,可以通过监听消息刷新事件获取 WKMsg 对象。WKMsg 中的 status(发送状态)和 reasonCode(原因码)指示了消息发送的结果。
名称说明
0ReasonUnknown未知错误
1ReasonSuccess成功
2ReasonAuthFail认证失败
3ReasonSubscriberNotExist订阅者在频道内不存在
4ReasonInBlacklist在黑名单列表里
5ReasonChannelNotExist频道不存在
6ReasonUserNotOnNode用户没在节点上
7ReasonSenderOffline发送者离线,消息发送失败
8ReasonMsgKeyError消息key错误,消息不合法
9ReasonPayloadDecodeErrorpayload解码失败
10ReasonForwardSendPacketError转发发送包失败
11ReasonNotAllowSend不允许发送消息
12ReasonConnectKick连接被踢
13ReasonNotInWhitelist没在白名单内
14ReasonQueryTokenError查询用户token错误
15ReasonSystemError系统错误
16ReasonChannelIDError错误的频道ID
17ReasonNodeMatchError节点匹配错误
18ReasonNodeNotMatch节点不匹配
19ReasonBan频道被封禁
20ReasonNotSupportHeader不支持的header
21ReasonClientKeyIsEmptyclientKey 是空的
22ReasonRateLimit速率限制
23ReasonNotSupportChannelType不支持的频道类型
24ReasonDisband频道已解散
25ReasonSendBan发送被封禁

查看历史消息

/**
 * 查询或同步某个频道消息
 *
 * @param channelId                频道ID
 * @param channelType              频道类型
 * @param oldestOrderSeq           最后一次消息大orderSeq 第一次进入聊天传入0
 * @param contain                  是否包含 oldestOrderSeq 这条消息
 * @param dropDown                 是否下拉
 * @param aroundMsgOrderSeq        查询此消息附近消息 如 aroundMsgOrderSeq=20 返回数据则是 [16,17,19,20,21,22,23,24,25]
 * @param limit                    每次获取数量
 * @param iGetOrSyncHistoryMsgBack 请求返回
 */
WKIM.getInstance().getMsgManager().getOrSyncHistoryMessages(
    channelId, 
    channelType, 
    oldestOrderSeq, 
    contain, 
    dropDown, 
    limit, 
    aroundMsgOrderSeq, 
    new IGetOrSyncHistoryMsgBack() {
        @Override
        public void onSyncing() {
            // 正在同步中 按需显示loading
        }

        @Override
        public void onResult(List<WKMsg> list) {
            // 展示消息
        }
    }
);
获取历史消息并不是同步方法,因为有可能存在非连续性时会往服务器同步数据

历史消息加载示例

public class ChatActivity extends AppCompatActivity {
    
    private boolean isLoadingHistory = false;
    private long oldestOrderSeq = 0;
    
    // 加载历史消息
    private void loadHistoryMessages() {
        if (isLoadingHistory) return;
        
        isLoadingHistory = true;
        showLoadingIndicator();
        
        WKIM.getInstance().getMsgManager().getOrSyncHistoryMessages(
            channelID,
            channelType,
            oldestOrderSeq,
            false,  // 不包含oldestOrderSeq这条消息
            true,   // 下拉加载
            20,     // 每次加载20条
            0,      // 不查询周围消息
            new IGetOrSyncHistoryMsgBack() {
                @Override
                public void onSyncing() {
                    // 正在同步中
                    runOnUiThread(() -> showSyncingIndicator());
                }

                @Override
                public void onResult(List<WKMsg> list) {
                    runOnUiThread(() -> {
                        hideLoadingIndicator();
                        isLoadingHistory = false;
                        
                        if (list != null && !list.isEmpty()) {
                            // 将历史消息插入到列表开头
                            messageList.addAll(0, list);
                            messageAdapter.notifyItemRangeInserted(0, list.size());
                            
                            // 更新最旧消息的orderSeq
                            oldestOrderSeq = list.get(0).orderSeq;
                            
                            // 保持滚动位置
                            recyclerView.scrollToPosition(list.size());
                        }
                    });
                }
            }
        );
    }
    
    // 下拉刷新触发
    private void onPullToRefresh() {
        loadHistoryMessages();
    }
}

离线消息接收

需要实现同步频道消息数据源: 频道消息数据源 因为 WuKongIM 是支持消息永久存储,所以会产生海量的离线消息。对此我们采用了按需拉取的机制,如 10 个会话一个会话 10 万条消息,WuKongIM 不会把这个 10×10 万=100 万条消息都拉取到本地。而是采用拉取这 10 个会话的信息和对应的最新 20 条消息,也就是实际只拉取了 200 条消息相对 100 万条消息来说大大提高了离线拉取速度。用户点进对应的会话才会去按需拉取这个会话的消息。这些机制 SDK 内部都已做好了封装,使用者其实不需要关心。使用者只需要关心最近会话的变化和监听获取数据的回调即可。

数据结构说明

消息类核心属性

public class WKMsg {
    // 服务器消息ID(全局唯一,无序)
    public String messageID;
    // 本地唯一ID
    public String clientMsgNO;
    // 服务器时间 (10位时间戳)
    public long timestamp;
    // 消息来源发送者
    public String fromUID;
    // 聊天频道ID
    public String channelID;
    // 聊天频道类型
    public byte channelType;
    // 消息正文
    public WKMessageContent baseContentMsgModel;
    // 消息头
    public WKMsgHeader header;
    // 本地扩展字段
    public HashMap localExtraMap;
    // 远程扩展
    public WKMsgExtra remoteExtra;
    // ...
}

消息正文核心属性

public class WKMessageContent {
    // 消息内容类型
    public int type;
    // 消息中的@提醒信息
    public WKMentionInfo mentionInfo;
    // 消息回复对象
    public WKReply reply;
    
    // 编码消息 上层需实现该方法并编码
    public JSONObject encodeMsg() {
        return new JSONObject();
    }
    
    // 解码消息 上层需实现该方法并解码
    public WKMessageContent decodeMsg(JSONObject jsonObject) {
        return this;
    }
    // ...
}

消息属性说明

属性类型说明
messageIDString服务器消息ID(全局唯一,无序)
clientMsgNOString本地唯一ID,用于消息去重和更新
timestamplong服务器时间(10位时间戳)
fromUIDString消息发送者UID
channelIDString聊天频道ID
channelTypebyte聊天频道类型
baseContentMsgModelWKMessageContent消息正文内容
headerWKMsgHeader消息头信息
localExtraMapHashMap本地扩展字段
remoteExtraWKMsgExtra远程扩展信息

最佳实践

1. 消息列表性能优化

// 使用RecyclerView的ViewHolder模式
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder> {
    
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        WKMsg msg = messageList.get(position);
        
        // 避免频繁的类型判断
        if (holder.getItemViewType() == msg.baseContentMsgModel.type) {
            holder.bindMessage(msg);
        }
    }
    
    @Override
    public int getItemViewType(int position) {
        return messageList.get(position).baseContentMsgModel.type;
    }
}

2. 消息状态管理

private void updateMessageStatus(WKMsg msg) {
    switch (msg.status) {
        case WKSendMsgResult.send_success:
            // 发送成功
            showSuccessStatus(msg);
            break;
        case WKSendMsgResult.send_fail:
            // 发送失败
            showFailStatus(msg);
            break;
        case WKSendMsgResult.send_ing:
            // 发送中
            showSendingStatus(msg);
            break;
    }
}

3. 内存管理

@Override
protected void onDestroy() {
    super.onDestroy();
    
    // 移除所有监听器
    WKIM.getInstance().getMsgManager().removeNewMsgListener("ChatActivity");
    WKIM.getInstance().getMsgManager().removeRefreshMsgListener("ChatActivity");
    WKIM.getInstance().getMsgManager().removeOnSendMsgCallback("ChatActivity");
    
    // 清理消息列表
    if (messageList != null) {
        messageList.clear();
    }
}

下一步