有奖捉虫:办公协同&微信生态&物联网文档专题 HOT

功能描述

消息已读回执指的是,发送端往会话中发送消息,如果他想知道哪些人读过/还没读过这条消息,那么他就需要开启消息已读回执功能。 开启后,发送端发送消息时可以设置消息是否需要已读回执。如果设置为“需要”,接收端查看消息后才会发送自己已读给发送端。
单聊和群聊均支持消息已读回执功能,操作步骤一致。
说明
该功能仅对旗舰版客户开放,购买 旗舰版套餐包 后可使用。
群聊已读回执仅增强版 6.1.2155 及以上版本支持。
单聊已读回执仅增强版 6.2.2363 及以上版本支持。

消息已读回执

设置支持已读回执的群类型

如果要支持群聊消息已读回执,需要先在 即时通信 IM 控制台 > 功能配置 > 群组配置 > 群已读消息回执配置 中,设置支持已读回执的群类型,勾选的群类型才能使用已读回执功能。
单聊消息已读回执不需要在控制台做任何设置,默认支持已读回执。

发送端设置消息需要已读回执

发送端创建消息后,先通过消息对象 V2TIMMessageneedReadReceipt (Android / iOS & Mac / Windows) 字段设置这条消息需要已读回执,再发送消息到会话中。
示例代码如下:
Android
iOS & Mac
Windows
V2TIMMessage message = V2TIMManager.getMessageManager().createTextMessage("群已读回执消息");
// 设置消息需要已读回执
message.setNeedReadReceipt(true);
// 发送消息
V2TIMManager.getMessageManager().sendMessage(message, null, "groupA", V2TIMMessage.V2TIM_PRIORITY_NORMAL, false, null, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onProgress(int progress) {
}

@Override
public void onSuccess(V2TIMMessage message) {
}

@Override
public void onError(int code, String desc) {
}
});
/// 接口调用示例
V2TIMMessage *message = [[V2TIMManager sharedInstance] createTextMessage:@"群已读回执消息"];
// 设置消息需要已读回执
message.needReadReceipt = YES;
// 发送消息
[[V2TIMManager sharedInstance] sendMessage:message receiver:nil groupID:@"groupA" priority:V2TIM_PRIORITY_NORMAL onlineUserOnly:NO offlinePushInfo:nil progress:nil succ:nil fail:nil];
class SendCallback final : public V2TIMSendCallback {
public:
using SuccessCallback = std::function<void(const V2TIMMessage&)>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;
using ProgressCallback = std::function<void(uint32_t)>;

SendCallback() = default;
~SendCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,
ProgressCallback progress_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
progress_callback_ = std::move(progress_callback);
}

void OnSuccess(const V2TIMMessage& message) override {
if (success_callback_) {
success_callback_(message);
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}
void OnProgress(uint32_t progress) override {
if (progress_callback_) {
progress_callback_(progress);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
ProgressCallback progress_callback_;
};

V2TIMMessage message = V2TIMManager::GetInstance()->GetMessageManager()->CreateTextMessage(u8"群已读回执消息");
// 设置消息需要已读回执
message.setNeedReadReceipt(true);

auto callback = new SendCallback{};
callback->SetCallback([=](const V2TIMMessage& message) { delete callback; },
[=](int error_code, const V2TIMString& error_message) { delete callback; },
[=](uint32_t progress) {});

V2TIMManager::GetInstance()->GetMessageManager()->SendMessage(
message, "groupA", {}, V2TIMMessagePriority::V2TIM_PRIORITY_DEFAULT, false, {}, callback);

接收端发送消息已读回执

接收端 收到消息 后,根据消息对象 V2TIMMessageneedReadReceipt 字段判断消息是否需要已读回执。如果需要,当用户查看消息后,调用 sendMessageReadReceipts (Android / iOS & Mac / Windows) 接口发送消息已读回执。
示例代码如下:
Android
iOS & Mac
Windows
// 假设 message 消息用户已经查看
if (!message.isSelf() && message.isNeedReadReceipt()) {
List<V2TIMMessage> messageList = new ArrayList<>();
messageList.add(message);
V2TIMManager.getMessageManager().sendMessageReadReceipts(messageList, new V2TIMCallback() {
@Override
public void onSuccess() {
// 发送消息已读回执成功
}

@Override
public void onError(int code, String desc) {
// 发送消息已读回执失败
}
});
}
/// 接口调用示例
/// 假设 msg 消息用户已经查看
if (!msg.isSelf && msg.needReadReceipt) {
[[V2TIMManager sharedInstance] sendMessageReadReceipts:@[msg] succ:^{
// 发送消息已读回执成功
} fail:^(int code, NSString *desc) {
// 发送消息已读回执失败
}];
}
class Callback final : public V2TIMCallback {
public:
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;

Callback() = default;
~Callback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
}

void OnSuccess() override {
if (success_callback_) {
success_callback_();
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
};

auto callback = new Callback;
callback->SetCallback(
[=]() {
// 发送消息已读回执成功
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 发送消息已读回执失败
delete callback;
});

// 假设 message 消息用户已经查看
if (!message.isSelf && message.needReadReceipt) {
V2TIMMessageVector messageList;
messageList.PushBack(message);
V2TIMManager::GetInstance()->GetMessageManager()->SendMessageReadReceipts(messageList, callback);
}

发送端监听消息已读回执通知

接收端发送消息已读回执后,发送端可以在 V2TIMAdvancedMsgListeneronRecvMessageReadReceipts (Android / iOS & Mac / Windows) 中收到已读回执通知,在通知中更新 UI,例如更新某条消息为 “2 人已读”。
示例代码如下:
Android
iOS & Mac
Windows
V2TIMAdvancedMsgListener advancedMsgListener = new V2TIMAdvancedMsgListener() {
@Override
public void onRecvMessageReadReceipts(List<V2TIMMessageReceipt> receiptList) {
for (V2TIMMessageReceipt receipt : receiptList) {
// 已读回执消息 ID
String msgID = receipt.getMsgID();
// C2C 消息对方 ID
String userID = receipt.getUserID();
// C2C 消息对方已读状态
boolean isPeerRead = receipt.isPeerRead();
// 群消息最新已读数
long readCount = receipt.getReadCount();
// 群消息最新未读数
long unreadCount = receipt.getUnreadCount();
}
}
};
V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);
/// 接口调用示例
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
- (void)onRecvMessageReadReceipts:(NSArray<V2TIMMessageReceipt *> *)receiptList {
for(V2TIMMessageReceipt *receipt in receiptList) {
// 已读回执消息 ID
NSString *msgID = receipt.msgID;
// C2C 消息对方 ID
NSString * userID = receipt.userID;
// C2C 消息对方已读状态
BOOL isPeerRead = receipt.isPeerRead;
// 群组 ID
NSString * groupID = receipt.groupID;
// 群消息最新已读数
uint64_t readCount = receipt.readCount;
// 群消息最新未读数
uint64_t unreadCount = receipt.unreadCount;
}
}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {
public:
/**
* 消息已读回执通知
*
* @param receiptList 已读回执列表
*/
void OnRecvMessageReadReceipts(const V2TIMMessageReceiptVector& receiptList) override {
for (size_t i = 0; i < receiptList.Size(); ++i) {
const V2TIMMessageReceipt& receipt = receiptList[i];
// 已读回执消息 ID
V2TIMString msgID = receipt.msgID;
// C2C 消息对方 ID
V2TIMString userID = receipt.userID;
// C2C 消息对方已读状态
bool isPeerRead = receipt.isPeerRead;
// 群组 ID
V2TIMString groupID = receipt.groupID;
// 群消息最新已读数
int32_t readCount = receipt.readCount;
// 群消息最新未读数
int32_t unreadCount = receipt.unreadCount;
}
}
// 其他成员 ...
};

// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调
AdvancedMsgListener advancedMsgListener;
V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);

发送端主动拉取消息已读回执信息

发送端从其他界面进入消息列表后,先拉取历史消息,再调用 getMessageReadReceipts(Android / iOS & Mac / Windows) 接口拉取消息已读回执信息。
其中已读回执信息 V2TIMMessageReceipt 字段含义如下:
属性
含义
说明
msgID
消息 ID
消息唯一 ID。
userID
对端用户 ID
如果是单聊,该字段表示对端用户 ID。
isPeerRead
消息对端用户是否已读
如果是单聊,该字段表示消息对端用户是否已读。
timestamp
对端用户标记会话已读时间
该字段在消息已读场景无效。如果是单聊,当对端用户调用 markC2CMessageAsRead 接口标记会话已读时,自己会收到的 onRecvC2CReadReceipt 回调,回调里会携带 timestamp 信息。 markC2CMessageAsRead 的使用请参考:会话未读数
groupID
群组 ID
如果是群聊,该字段为群组 ID。
readCount
群消息已读人数
如果是群聊,该字段为群消息已读人数。
unreadCount
群消息未读人数
如果是群聊,该字段为群消息未读人数。
示例代码如下:
Android
iOS & Mac
Windows
/// 接口调用示例(以 Group 消息为例)
V2TIMManager.getMessageManager().getGroupHistoryMessageList("groupA", 20, null, new V2TIMValueCallback<List<V2TIMMessage>>() {
@Override
public void onSuccess(final List<V2TIMMessage> v2TIMMessages) {
List<V2TIMMessage> receiptMsgs = new ArrayList<>();
// 自己发送的消息 && 需要已读回执,需要拉取消息的已读回执信息
for (V2TIMMessage msg : v2TIMMessages) {
if (msg.isSelf() && msg.isNeedReadReceipt()) {
receiptMsgs.add(msg);
}
}
V2TIMManager.getMessageManager().getMessageReadReceipts(receiptMsgs, new V2TIMValueCallback<List<V2TIMMessageReceipt>>() {
@Override
public void onSuccess(List<V2TIMMessageReceipt> v2TIMMessageReceipts) {
Map<String, V2TIMMessageReceipt> messageReceiptMap = new HashMap<>();
for (V2TIMMessageReceipt receipt : v2TIMMessageReceipts) {
messageReceiptMap.put(receipt.getMsgID(), receipt);
}
for (V2TIMMessage msg : v2TIMMessages) {
V2TIMMessageReceipt receipt = messageReceiptMap.get(msg.getMsgID());
if (receipt != null) {
// C2C 消息对方 ID
String userID = receipt.getUserID();
// C2C 消息对方已读状态
boolean isPeerRead = receipt.isPeerRead();
// 群组 ID
String groupID = receipt.getGroupID();
// 消息已读数,readCount 为 0,表示消息无人已读
long readCount = receipt.getReadCount();
// 消息未读数,unreadCount 为 0,表示消息全部已读
long unreadCount = receipt.getUnreadCount();
}
}
}

@Override
public void onError(int code, String desc) {
// 拉取消息已读状态失败
}
});
}

@Override
public void onError(int code, String desc) {
// 拉取消息失败
}
});
/// 接口调用示例(以 Group 消息为例)
[[V2TIMManager sharedInstance] getGroupHistoryMessageList:@"groupA" count:20 lastMsg:nil succ:^(NSArray<V2TIMMessage *> *msgs) {
NSMutableArray *receiptMsgs = [NSMutableArray array];
// 自己发送的消息 && 需要已读回执,需要拉取消息的已读回执信息
for (V2TIMMessage *msg in msgs) {
if (msg.isSelf && msg.needReadReceipt) {
[receiptMsgs addObject:msg];
}
}
[[V2TIMManager sharedInstance] getMessageReadReceipts:receiptMsgs succ:^(NSArray<V2TIMMessageReceipt *> *receiptList) {
NSMutableDictionary *param = [NSMutableDictionary dictionary];
for (V2TIMMessageReceipt *receipt in receiptList) {
[param setObject:receipt forKey:receipt.msgID];
}
for (V2TIMMessage *msg in msgs) {
V2TIMMessageReceipt *receipt = param[msg.msgID];
// C2C 消息对方 ID
NSString * userID = receipt.userID;
// C2C 消息对方已读状态
BOOL isPeerRead = receipt.isPeerRead;
// 群组 ID
NSString * groupID = receipt.groupID;
// 消息已读数,readCount 为 0,表示消息无人已读
uint64_t readCount = receipt.readCount;
// 消息未读数,unreadCount 为 0,表示消息全部已读
uint64_t unreadCount = receipt.unreadCount;
}
} fail:^(int code, NSString *desc) {
// 拉取消息已读状态失败
}];
} fail:^(int code, NSString *desc) {
// 拉取消息失败
}];
template <class T>
class ValueCallback final : public V2TIMValueCallback<T> {
public:
using SuccessCallback = std::function<void(const T&)>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;

ValueCallback() = default;
~ValueCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
}

void OnSuccess(const T& value) override {
if (success_callback_) {
success_callback_(value);
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
};

// 接口调用示例(以 Group 消息为例)
V2TIMMessageListGetOption option;
option.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息
option.count = 20; // 每页 20 条
option.groupID = "groupA"; // 拉取群聊消息

auto callback = new ValueCallback<V2TIMMessageVector>{};
callback->SetCallback(
[=](const V2TIMMessageVector& messageList) {
V2TIMMessageVector receiptMsgs;
for (size_t i = 0; i < messageList.Size(); ++i) {
const V2TIMMessage& message = messageList[0];
// 自己发送的消息 && 需要已读回执,需要拉取消息的已读回执信息
if (message.isSelf && message.needReadReceipt) {
receiptMsgs.PushBack(message);
}
}

auto receipt_callback = new ValueCallback<V2TIMMessageReceiptVector>{};
receipt_callback->SetCallback(
[=](const V2TIMMessageReceiptVector& receiptList) {
std::unordered_map<V2TIMString, V2TIMMessageReceipt> map;
for (size_t i = 0; i < receiptList.Size(); ++i) {
const V2TIMMessageReceipt& receipt = receiptList[i];
map[receipt.msgID] = receipt;
}
for (size_t i = 0; i < messageList.Size(); ++i) {
const V2TIMMessage& message = messageList[0];
if (map.count(message.msgID)) {
const V2TIMMessageReceipt& receipt = map[message.msgID];
// C2C 消息对方 ID
V2TIMString userID = receipt.userID;
// C2C 消息对方已读状态
bool isPeerRead = receipt.isPeerRead;
// 群组 ID
V2TIMString groupID = receipt.groupID;
// 消息已读数,readCount 为 0,表示消息无人已读
int32_t readCount = receipt.readCount;
// 消息未读数,unreadCount 为 0,表示消息全部已读
int32_t unreadCount = receipt.unreadCount;
}
const V2TIMMessageReceipt& receipt = receiptList[i];
}

delete receipt_callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 拉取消息已读状态失败
delete receipt_callback;
});

V2TIMManager::GetInstance()->GetMessageManager()->GetMessageReadReceipts(messageList, receipt_callback);

delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 拉取消息失败
delete callback;
});

V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);

发送端主动拉取群消息已读或未读成员列表

发送端在需要查看群消息已读或未读成员列表时,可以调用 getGroupMessageReadMemberList(Android / iOS & Mac / Windows) 接口分页拉取消息已读或未读群成员列表。
Android
iOS & Mac
Windows
/// 接口调用示例
V2TIMManager.getMessageManager().getGroupMessageReadMemberList(message, V2TIMMessage.V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, 0, 100, new V2TIMValueCallback<V2TIMGroupMessageReadMemberList>() {
@Override
public void onSuccess(V2TIMGroupMessageReadMemberList v2TIMGroupMessageReadMemberList) {
// members 当前分页拉取的已读成员列表
List<V2TIMGroupMemberInfo> members = v2TIMGroupMessageReadMemberList.getMemberInfoList();
// nextSeq 下次分页拉取的游标位置
long nextSeq = v2TIMGroupMessageReadMemberList.getNextSeq();
// isFinished 已读列表是否已经全部拉取完毕
boolean isFinished = v2TIMGroupMessageReadMemberList.isFinished();
// 如果已读列表没有全部拉取完毕,继续下一页拉取(这里只是示例代码,分页拉取建议由用户点击 UI 触发)
if (!isFinished) {
V2TIMManager.getMessageManager().getGroupMessageReadMemberList(message, V2TIMMessage.V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, nextSeq, 100, new V2TIMValueCallback<V2TIMGroupMessageReadMemberList>() {
@Override
public void onSuccess(V2TIMGroupMessageReadMemberList v2TIMGroupMessageReadMemberList) {
// 拉群已读成员列表成功
}

@Override
public void onError(int code, String desc) {
// 拉群已读成员列表失败
}
});
}
}

@Override
public void onError(int code, String desc) {
// 拉群已读成员列表失败
}
});
/// 接口调用示例
[[V2TIMManager sharedInstance] getGroupMessageReadMemberList:message filter:V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ nextSeq:0 count:100 succ:^(NSMutableArray<V2TIMGroupMemberInfo *> *members, uint64_t nextSeq, BOOL isFinished) {
// members 当前分页拉取的已读成员列表
// nextSeq 下次分页拉取的游标位置
// isFinished 已读列表是否已经全部拉取完毕
// 如果已读列表没有全部拉取完毕,继续下一页拉取(这里只是示例代码,分页拉取建议由用户点击 UI 触发)
if (!isFinished) {
[[V2TIMManager sharedInstance] getGroupMessageReadMemberList:message filter:V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ nextSeq:nextSeq count:100 succ:^(NSMutableArray<V2TIMGroupMemberInfo *> *members, uint64_t nextSeq, BOOL isFinished) {
// 拉群已读成员列表成功
} fail:^(int code, NSString *desc) {
// 拉群已读成员列表失败
}];
}
} fail:^(int code, NSString *desc) {
// 拉群已读成员列表失败
}];
template <class T>
class ValueCallback final : public V2TIMValueCallback<T> {
public:
using SuccessCallback = std::function<void(const T&)>;
using ErrorCallback = std::function<void(int, const V2TIMString&)>;

ValueCallback() = default;
~ValueCallback() override = default;

void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback) {
success_callback_ = std::move(success_callback);
error_callback_ = std::move(error_callback);
}

void OnSuccess(const T& value) override {
if (success_callback_) {
success_callback_(value);
}
}
void OnError(int error_code, const V2TIMString& error_message) override {
if (error_callback_) {
error_callback_(error_code, error_message);
}
}

private:
SuccessCallback success_callback_;
ErrorCallback error_callback_;
};

auto callback = new ValueCallback<V2TIMGroupMessageReadMemberList>{};
callback->SetCallback(
[=](const V2TIMGroupMessageReadMemberList& groupMessageReadMemberList) {
// members 当前分页拉取的已读成员列表
const V2TIMGroupMemberInfoVector& members = groupMessageReadMemberList.members;
// nextSeq 下次分页拉取的游标位置
uint64_t nextSeq = groupMessageReadMemberList.nextSeq;
// isFinished 已读列表是否已经全部拉取完毕
bool isFinished = groupMessageReadMemberList.isFinished;
// 如果已读列表没有全部拉取完毕,继续下一页拉取(这里只是示例代码,分页拉取建议由用户点击 UI 触发)
if (!isFinished) {
auto callback = new ValueCallback<V2TIMGroupMessageReadMemberList>{};
callback->SetCallback(
[=](const V2TIMGroupMessageReadMemberList& groupMessageReadMemberList) {
// 拉群已读成员列表成功
delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 拉群已读成员列表失败
delete callback;
});
V2TIMManager::GetInstance()->GetMessageManager()->GetGroupMessageReadMemberList(
message, V2TIMGroupMessageReadMembersFilter::V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ,
nextSeq, 100, callback);
}

delete callback;
},
[=](int error_code, const V2TIMString& error_message) {
// 拉群已读成员列表失败
delete callback;
});

V2TIMManager::GetInstance()->GetMessageManager()->GetGroupMessageReadMemberList(
message, V2TIMGroupMessageReadMembersFilter::V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, 0, 100,
callback);