一、功能描述
在即时通信工具(例如:WhatsApp、微信等)中,当接收人查看消息时,发送人会看到消息旁边的已读标记,例如蓝色的对勾或“已读”字样。
注意:说明:
注意:“回执”的含义是“回复的收据”,它代表了一种确认接收的凭证。当你发送一条消息,并请求一个回执,你实际上是在请求对方“我想确认你们是否接收并阅读了我的消息”。这个确认就像是一张“收据”,证明你的消息已经被接收。
已读回执有助于确保重要信息已被查看,但也可能引发心理压力和隐私问题,因此我们支持用户关闭已读回执功能。
注意:说明:
二、效果展示
你可以使用本功能,实现如下所示的消息未读、已读效果:
三、设置支持已读回执的群类型
如果要支持群聊消息已读回执,需要先在 即时通信 IM 控制台 > 功能配置 > 群组配置 > 群已读消息回执配置中,设置支持已读回执的群类型,勾选的群类型才能使用已读回执功能。
单聊消息已读回执不需要在控制台做任何设置,默认支持已读回执。
四、发送端设置消息需要已读回执
发送端创建消息后,先通过消息对象 V2TIMMessage 的 needReadReceipt (Java / Swift / Objective-C / C++ / ArkTS) 字段设置这条消息需要已读回执,再发送消息到会话中。
示例代码如下:
JavaSwiftObjective-CC++ArkTSV2TIMMessage 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) { }});// 接口调用示例if let message = V2TIMManager.shared.createTextMessage(text: "群已读回执消息") { // 设置消息需要已读回执 message.needReadReceipt = true // 发送消息 V2TIMManager.shared.sendMessage(message: message, receiver: nil, groupID: "groupA", priority: .V2TIM_PRIORITY_NORMAL, onlineUserOnly: false, offlinePushInfo: nil, progress: nil, succ: { // 消息发送成功 print("消息发送成功") }, fail: { code, msg in // 消息发送失败 print("消息发送失败,code: \\(code), msg: \\(msg)") })}/// 接口调用示例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);const messageManager = V2TIMManager.getMessageManager();const message: V2TIMMessage = messageManager.createTextMessage("群已读回执消息");// 设置消息需要已读回执,必须在发送前设置。message.needReadReceipt = true;const result = messageManager.sendMessage(message, { groupID: "groupA",});result.promise .then((sentMessage: V2TIMMessage) => { console.info(`消息发送成功: ${sentMessage.msgID}`); }) .catch((error: Error) => { console.error(`消息发送失败: ${error.message}`); });
五、接收端发送消息已读回执
接收端 收到消息 后,根据消息对象 V2TIMMessage 的 needReadReceipt 字段判断消息是否需要已读回执。如果需要,当用户查看消息后,调用 sendMessageReadReceipts (Java / Swift / Objective-C / C++ / ArkTS) 接口发送消息已读回执。
示例代码如下:
JavaSwiftObjective-CC++ArkTS// 假设 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) { // 发送消息已读回执失败 } });}// 接口调用示例// 假设 message 消息用户已经查看if !message.isSelf && message.needReadReceipt { V2TIMManager.shared.sendMessageReadReceipts(messageList: [message], succ: { // 发送消息已读回执成功 print("发送消息已读回执成功") }, fail: { code, desc in // 发送消息已读回执失败 print("发送消息已读回执失败,code: \\(code), desc: \\(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);}// 在用户实际查看消息后调用。async function sendMessageReadReceipt(message: V2TIMMessage): Promise<void> { if (!message.isSelf && message.needReadReceipt) { // messageList 中的消息必须属于同一会话。 await V2TIMManager.getMessageManager().sendMessageReadReceipts([message]); console.info("发送消息已读回执成功"); }}
六、发送端监听消息已读回执通知
接收端发送消息已读回执后,发送端可以在 V2TIMAdvancedMsgListener 的 onRecvMessageReadReceipts (Java / Swift / Objective-C / C++ / ArkTS) 中收到已读回执通知,在通知中更新 UI,例如更新某条消息为 “2 人已读”。
示例代码如下:
JavaSwiftObjective-CC++ArkTSV2TIMAdvancedMsgListener 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(); // C2C 消息对方已读时间 long timestamp = receipt.getTimestamp(); // 群消息最新已读数 long readCount = receipt.getReadCount(); // 群消息最新未读数 long unreadCount = receipt.getUnreadCount(); } }};V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);// 接口调用示例V2TIMManager.sharedInstance().addAdvancedMsgListener(self)func onRecvMessageReadReceipts(_ receiptList: [V2TIMMessageReceipt]) { for receipt in receiptList { // 已读回执消息 ID let msgID = receipt.msgID // C2C 消息对方 ID let userID = receipt.userID // C2C 消息对方已读状态 let isPeerRead = receipt.isPeerRead // C2C 消息对方已读时间 let timestamp = receipt.timestamp // 群组 ID let groupID = receipt.groupID // 群消息最新已读数 let readCount = receipt.readCount // 群消息最新未读数 let unreadCount = receipt.unreadCount // 这里可以添加处理已读回执的逻辑 print("Message ID: \\(msgID), User ID: \\(userID), Is Peer Read: \\(isPeerRead), Timestamp: \\(timestamp), Group ID: \\(groupID), Read Count: \\(readCount), Unread Count: \\(unreadCount)") }}/// 接口调用示例[[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; // C2C 消息对方已读时间 time_t timestamp = receipt.timestamp; // 群组 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; // C2C 消息对方已读时间 int64_t timestamp = receipt.timestamp; // 群组 ID V2TIMString groupID = receipt.groupID; // 群消息最新已读数 int32_t readCount = receipt.readCount; // 群消息最新未读数 int32_t unreadCount = receipt.unreadCount; } } // 其他成员 …};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);const advancedMsgListener: V2TIMAdvancedMsgListener = { onRecvMessageReadReceipts: (receiptList: V2TIMMessageReceipt[]) => { receiptList.forEach((receipt) => { // C2C 消息可使用 userID、isPeerRead、timestamp;群消息可使用 groupID、readCount、unreadCount。 console.info( `消息 ID: ${receipt.msgID}, 群 ID: ${receipt.groupID}, 已读: ${receipt.readCount}, 未读: ${receipt.unreadCount}`, ); }); },};V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);
七、发送端主动拉取消息已读回执信息
发送端从其他界面进入消息列表后,先拉取历史消息,再调用 getMessageReadReceipts(Java / Swift / Objective-C / C++ / ArkTS) 接口拉取消息已读回执信息。
其中已读回执信息 V2TIMMessageReceipt 字段含义如下:
| 属性 | 含义 | 说明 |
| msgID | 消息 ID | 消息唯一 ID。 |
| userID | 对端用户 ID | 如果是单聊,该字段表示对端用户 ID。 |
| isPeerRead | 消息对端用户是否已读 | 如果是单聊,该字段表示消息对端用户是否已读。 |
| timestamp | 对端用户已读时间 | 如果 msgID 为空,该字段表示对端用户标记会话已读的时间。如果 msgID 不为空,该字段表示对端用户发送消息已读回执的时间(8.1 及以上版本支持)。 |
| groupID | 群组 ID | 如果是群聊,该字段为群组 ID。 |
| readCount | 群消息已读人数 | 如果是群聊,该字段为群消息已读人数。 |
| unreadCount | 群消息未读人数 | 如果是群聊,该字段为群消息未读人数。 |
示例代码如下:
JavaSwiftObjective-CC++ArkTS/// 接口调用示例(以 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(); // C2C 消息对方已读时间 long timestamp = receipt.getTimestamp(); // 群组 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.shared.getGroupHistoryMessageList(groupID: "groupA", count: 20, lastMsg: nil, succ: { msgs in var receiptMsgs: [V2TIMMessage] = [] // 自己发送的消息 && 需要已读回执,需要拉取消息的已读回执信息 for msg in msgs { if msg.isSelf && msg.needReadReceipt { receiptMsgs.append(msg) } } V2TIMManager.shared.getMessageReadReceipts(messageList: receiptMsgs, succ: { receiptList in var param: [String: V2TIMMessageReceipt] = [:] for receipt in receiptList { if let msgID = receipt.msgID { param[msgID] = receipt } } for msg in msgs { if let receipt = param[msg.msgID] { // C2C 消息对方 ID let userID = receipt.userID // C2C 消息对方已读状态 let isPeerRead = receipt.isPeerRead // C2C 消息对方已读时间 let timestamp = receipt.timestamp // 群组 ID let groupID = receipt.groupID // 消息已读数,readCount 为 0,表示消息无人已读 let readCount = receipt.readCount // 消息未读数,unreadCount 为 0,表示消息全部已读 let unreadCount = receipt.unreadCount // 这里可以添加处理已读回执的逻辑 print("Message ID: \\(msg.msgID), User ID: \\(userID), Is Peer Read: \\(isPeerRead), Timestamp: \\(timestamp), Group ID: \\(groupID), Read Count: \\(readCount), Unread Count: \\(unreadCount)") } } }, fail: { code, desc in // 拉取消息已读状态失败 print("拉取消息已读状态失败,code: \\(code), desc: \\(desc)") })}, fail: { code, desc in // 拉取消息失败 print("拉取消息失败,code: \\(code), desc: \\(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; // C2C 消息对方已读时间 time_t timestamp = receipt.timestamp; // 群组 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; // C2C 消息对方已读时间 int64_t timestamp = receipt.timestamp; // 群组 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);const messageManager = V2TIMManager.getMessageManager();// ArkTS 使用统一的 getHistoryMessageList;传入 groupID 即可拉取群历史消息。const option: V2TIMMessageListGetOption = { getType: V2TIMMessageGetType.V2TIM_GET_CLOUD_OLDER_MSG, count: 20, groupID: "groupA",};const historyMessages = await messageManager.getHistoryMessageList(option);const receiptMessages = historyMessages.filter( (message) => message.isSelf && message.needReadReceipt,);if (receiptMessages.length === 0) { return;}const receiptList: V2TIMMessageReceipt[] = await messageManager.getMessageReadReceipts(receiptMessages);receiptList.forEach((receipt) => { // C2C 消息可使用 userID、isPeerRead、timestamp;群消息可使用 groupID、readCount、unreadCount。 console.info( `消息 ID: ${receipt.msgID}, 对端已读: ${receipt.isPeerRead}, 已读: ${receipt.readCount}, 未读: ${receipt.unreadCount}`, );});
八、发送端主动拉取群消息已读或未读成员列表
发送端在需要查看群消息已读或未读成员列表时,可以调用 getGroupMessageReadMemberList(Java / Swift / Objective-C / C++ / ArkTS) 接口分页拉取消息已读或未读群成员列表。
JavaSwiftObjective-CC++ArkTS/// 接口调用示例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.shared.getGroupMessageReadMemberList(message: message, filter: .V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, nextSeq: 0, count: 100, succ: { members, nextSeq, isFinished in // members 当前分页拉取的已读成员列表 // nextSeq 下次分页拉取的游标位置 // isFinished 已读列表是否已经全部拉取完毕 // 如果已读列表没有全部拉取完毕,继续下一页拉取(这里只是示例代码,分页拉取建议由用户点击 UI 触发) if !isFinished { V2TIMManager.shared.getGroupMessageReadMemberList(message: message, filter: .V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, nextSeq: UInt(nextSeq), count: 100, succ: { members, nextSeq, isFinished in // 拉群已读成员列表成功 print("拉取群已读成员列表成功,当前成员数量: \\(members.count)") }, fail: { code, desc in // 拉群已读成员列表失败 print("拉取群已读成员列表失败,code: \\(code), desc: \\(desc)") }) }}, fail: { code, desc in // 拉群已读成员列表失败 print("拉取群已读成员列表失败,code: \\(code), desc: \\(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);async function getGroupReadMembers(message: V2TIMMessage): Promise<void> { const messageManager = V2TIMManager.getMessageManager(); const result = await messageManager.getGroupMessageReadMemberList( message, V2TIMGroupMessageReadMembersFilter.V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, 0, 100, ); // members 当前分页拉取的已读成员列表。 const members = result.members; // nextSeq 下次分页拉取的游标位置。 const nextSeq = result.nextSeq; // isFinished 已读列表是否已经全部拉取完毕。 const isFinished = result.isFinished; // 如果已读列表没有全部拉取完毕,继续下一页拉取。 // 分页拉取建议由用户点击 UI 触发。 if (!isFinished) { const nextPage = await messageManager.getGroupMessageReadMemberList( message, V2TIMGroupMessageReadMembersFilter.V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ, nextSeq, 100, ); console.info(`下一页已读成员数量: ${nextPage.members.length}`); }}