历史消息
功能描述
拉取历史消息的 API 在类
V2TIMManager
和 V2TIMManager+Message(iOS & Mac)
/ V2TIMMessageManager(Android & Windows)
中。除了支持单聊、群聊历史消息的拉取外,还提供了高级接口以支持按指定方向拉取、按指定起点和指定时间范围拉取。
除了支持单独拉取本地历史消息外,还支持拉取云端历史消息。
说明:
在拉取云端历史消息时,如果检测到网络异常,SDK 会返回本地存储的历史消息。
本地存储的历史消息无时间限制,但云端存储的历史消息有存储时长的限制:
开发版:免费存储 7 天,不支持延长。
标准版:免费存储 7 天,支持延长。
进阶版:免费存储 30 天,支持延长。
拉取单聊历史消息
示例代码如下:
// 拉取单聊历史消息// 首次拉取,lastMsg 设置为 null// 再次拉取时,lastMsg 可以使用返回的消息列表中的最后一条消息V2TIMManager.getMessageManager().getC2CHistoryMessageList(#your user id#, 20, null, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "fail, " + code + ", " + desc);}@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {// 记录下次拉取的 lastMsg, 用于下次拉取V2TIMMessage lastMsg = v2TIMMessages.get(v2TIMMessages.size() - 1);Log.i("imsdk", "success");}});
// 拉取单聊历史消息// 首次拉取,lastMsg 设置为 nil// 再次拉取时,lastMsg 可以使用返回的消息列表中的最后一条消息[V2TIMManager.sharedInstance getC2CHistoryMessageList:#your user id# count:20 lastMsg:nil succ:^(NSArray<V2TIMMessage *> *msgs) {// 记录下次拉取的 lastMsg,用于下次拉取V2TIMMessage *lastMsg = msgs.lastObject;NSLog(@"success, %@", msgs);} fail:^(int code, NSString *desc) {NSLog(@"fail, %d, %@", code, desc);}];
拉取群聊历史消息
您可以调用接口
getGroupHistoryMessageList
(Android / iOS & Mac) 获取群聊历史消息。
在网络正常的情况下会拉取最新的云端数据。如果网络出现异常,SDK 会返回本地存储的历史消息。注意:
只有会议群(Meeting)才能拉取到进群前的历史消息,更多关于群消息的限制,详见 消息能力差异 。
直播群(AVChatRoom)消息不存云端漫游和本地数据库,调用此接口无效。进阶版客户可以在 控制台 配置 “Message History for New Members”,即“直播群新成员查看入群前消息量”,配置路径:Applications > Your App > Chat > Configuration > Group Configuration > Group feature configuration > Message History for New Members > Previous Messages Viewable。配置后会在加入直播群成功后,在
onRecvNewMessage
回调中获得入群前消息。示例代码如下:
// 拉取群聊历史消息// 首次拉取,lastMsg 设置为 null// 再次拉取时,lastMsg 可以使用返回的消息列表中的最后一条消息V2TIMManager.getMessageManager().getGroupHistoryMessageList(#your group id#, 20, null, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "fail, " + code + ", " + desc);}@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {// 记录下次拉取的 lastMsg,用于下次拉取V2TIMMessage lastMsg = v2TIMMessages.get(v2TIMMessages.size() - 1);Log.i("imsdk", "success");}});
// 拉取群聊历史消息// 首次拉取,lastMsg 设置为 null// 再次拉取时,lastMsg 可以使用返回的消息列表中的最后一条消息[V2TIMManager.sharedInstance getGroupHistoryMessageList:#your group id# count:20 lastMsg:nil succ:^(NSArray<V2TIMMessage *> *msgs) {// 记录下次拉取的 lastMsg,用于下次拉取V2TIMMessage *lastMsg = msgs.lastObject;NSLog(@"success, %@", msgs);} fail:^(int code, NSString *desc) {NSLog(@"fail, %d, %@", code, desc);}];
高级功能
高级接口
该接口除了支持普通拉取单聊、群聊历史消息外,还支持以下高级特性:
支持设置消息拉取时的起点。
支持设置拉取消息的时间范围。
支持设置拉取消息的位置:从本地拉取、从云端拉取。
支持按照指定的方向拉取:往消息时间更老的方向拉取、往消息时间更新的方向拉取。
支持拉取本地指定的消息类型:文本、图片、语音、视频、文件、表情、群 tips 消息、合并消息、自定义消息等。
接口原型:
public abstract void getHistoryMessageList(V2TIMMessageListGetOption option, V2TIMValueCallback<List<V2TIMMessage>> callback);
- (void)getHistoryMessageList:(V2TIMMessageListGetOption *)optionsucc:(V2TIMMessageListSucc)succfail:(V2TIMFail)fail;
virtual void GetHistoryMessageList(const V2TIMMessageListGetOption& option,V2TIMValueCallback<V2TIMMessageVector>* callback) = 0;
V2TIMMessageListGetOption
类参数说明:参数 | 含义 | 单聊有效 | 群聊有效 | 是否必填 | 说明 |
getType | 拉取消息的位置及方向,可以设置拉取 本地/云端 的 更老/更新 的消息 | YES | YES | YES | 当设置从云端拉取时,会将本地存储消息列表与云端存储消息列表合并后返回。如果无网络,则直接返回本地消息列表。 |
userID | 拉取指定用户的单聊历史消息 | YES | NO | NO | 拉取单聊消息,需要指定对方的 userID ,此时 groupID 传空即可。 |
groupID | 拉取指定群组的群聊历史消息 | NO | YES | NO | 拉取群聊消息,需要指定群聊的 groupID ,此时 userID 传空即可。 |
count | 单次拉取的消息数量 | YES | YES | YES | 建议设置为 20,否则可能影响拉取速度 |
messageTypeList | 拉取的消息类型集合 | YES | YES | NO | 1. 只支持本地拉取,即当 getType 为 V2TIM_GET_LOCAL_OLDER_MSG 或 V2TIM_GET_LOCAL_NEWER_MSG 时有效。2. 如果该字段为空,表示拉取所有的消息类型。 |
lastMsg | 最后一条消息 | YES | YES | NO | 可用于拉取历史消息的场景。 1. 单聊和群聊中均能使用。 2. 设置 lastMsg 作为拉取的起点,返回的消息列表中不包含这条消息。3. 如果设置为空,则使用会话的最新一条消息作为拉取起点。 |
lastMsgSeq | 最后一条消息的 seq | NO | YES | NO | 可用于拉取历史消息或消息定位等场景。 1. 仅能在群聊中使用该字段。 2. 设置 lastMsgSeq 作为拉取的起点,返回的消息列表中包含这条消息。3. 如果同时指定了 lastMsg 和 lastMsgSeq ,SDK 优先使用 lastMsg 。4. 如果均未指定 lastMsg 和 lastMsgSeq ,拉取的起点取决于是否设置 getTimeBegin 。设置了,则使用设置的范围作为起点;未设置,则使用最新消息作为起点。 |
getTimeBegin | 拉取消息的时间起点。UTC 时间戳,单位:秒 | YES | YES | NO | 默认为 0,表示从当前时间开始拉取。 |
getTimePeriod | 拉取消息的时间范围。单位:秒 | YES | YES | NO | 1. 默认为 0,表示不限制时间范围。 2. 取值的范围区间为闭区间,包含起止时间,二者关系如下: 如果 getType 指定了朝消息时间更老的方向拉取,则时间范围表示为 [getTimeBegin - getTimePeriod , getTimeBegin ]。如果 getType 指定了朝消息时间更新的方向拉取,则时间范围表示为 [ getTimeBegin , getTimeBegin + getTimePeriod ]。 |
分页拉取
上述拉取单聊历史消息、拉取群聊历史消息,及高级接口均可按照相同的方式实现分页,即使用
lastMsg
和 count
来实现:1. 首次拉取,设置
lastMsg
为空,此时 SDK 会拉取到最新的消息。2. 非首次拉取,使用上一次拉取到的消息列表中最后一条消息作为本次拉取的
lastMsg
,拉取下一页数据。此时消息列表返回的消息不包含设置的 lastMsg。3. 如果成功回调中返回的消息列表为空,表示拉取完毕,没有更多数据了。
说明:
1. 拉取到的消息列表中,越新的消息越靠前。
2. 考虑到加载效率、网络省流,建议分页时 count 设置为 20。
如果您使用
lastMsgSeq
拉取历史消息,返回的消息列表会包含该 lastMsgSeq
所对应的消息。
因此,非首次拉取群聊历史消息(也就是续拉)时,建议不要使用 lastMsgSeq
,否则可能出现同一条消息被重复拉到的情况。
例如,现在的历史消息有 8 条,分别是:msg1、msg2、msg3、msg4、msg5、msg6、msg7、msg8。
每次拉取 4 条,首次拉取得到了 msg1、msg2、msg3、msg4,此时再用 msg4 的 lastMsgSeq
续拉,会拉到消息 msg4、msg5、msg6、msg7。两次拉取中,msg4 被重复拉取了。
如果您一定要使用 lastMsgSeq
续拉,建议处理好消息去重的逻辑。按照时间范围拉取
您可以通过设置
getTimeBegin
和 getTimePeriod
来拉取指定的时间范围内的消息。
如下图所示,时间范围的起止时间戳与 getType 有关。
示例代码将演示:从 2022-01-01 00:00:00 开始(时间戳是 1640966400),往过去的方向,从云端拉取一整天的群聊消息。
V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 拉取云端的更老的消息option.setGetTimeBegin(1640966400); // 从 2022-01-01 00:00:00 开始option.setGetTimePeriod(1 * 24 * 60 * 60); // 拉取一整天的消息option.setCount(Integer.MAX_VALUE); // 返回时间范围内所有的消息option.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];option.getType = V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息option.getTimeBegin = 1640966400; // 从 2022-01-01 00:00:00 开始option.getTimePeriod = 1 * 24 * 60 * 60; // 拉取一整天的消息option.count = INT_MAX; // 返回时间范围内所有的消息option.groupID = #your group id#; // 拉取群聊消息[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};V2TIMMessageListGetOption option;option.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息option.getTimeBegin = 1640966400; // 从 2022-01-01 00:00:00 开始option.getTimePeriod = 1 * 24 * 60 * 60; // 拉取一整天的消息option.count = std::numeric_limits<uint64_t>::max(); // 返回时间范围内所有的消息option.groupID = "your group id"; // 拉取群聊消息auto callback = new ValueCallback<V2TIMMessageVector>{};callback->SetCallback([=](const V2TIMMessageVector& messageList) {std::cout << "success" << std::endl;delete callback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete callback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);
在时间范围内分页拉取
您可以同时指定拉取的时间范围和
lastMsg
/lastMsgSeq
。此时 SDK 的表现是:1. 如果用户同时设置了
getTimeBegin
/getTimePeriod
和 lastMsg
/lastMsgSeq
,结果集可理解成:「单独按起始消息拉取的结果」与「单独按时间范围拉取的结果」 取交集。2. 如果用户都没设置
getTimeBegin
/getTimePeriod
和 lastMsg
/lastMsgSeq
,结果集可理解成:从当前会话最新的一条消息开始,按照 getType
所指定的方向和拉取方式拉取。示例代码将演示:从 2022-01-01 00:00:00 开始(时间戳是 1640966400),往过去的方向,每页 20 条,分页地从云端拉取一整天的群聊消息。
// 定义变量,记录每次拉取的游标private V2TIMMessage m_lastMsg = null; // 首次拉取时为 null// 分页拉取逻辑V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 拉取云端的更老的消息option.setGetTimeBegin(1640966400); // 从 2022-01-01 00:00:00 开始option.setGetTimePeriod(1 * 24 * 60 * 60); // 拉取一整天的消息option.setCount(20); // 每页 20 条option.setLastMsg(m_lastMsg); // 上次拉取的位置 (每次返回的消息列表的最后一条消息)option.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {Log.i("imsdk", "success");// 记录下一次拉取的 lastMsgm_lastMsg = v2TIMMessages.get(v2TIMMessages.size() - 1);}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
// 定义变量,记录每次拉取的游标@property (nonatomic, copy) V2TIMMessage *lastMsg;// 分页拉取逻辑V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];option.getType = V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息option.getTimeBegin = 1640966400; // 从 2022-01-01 00:00:00 开始option.getTimePeriod = 1 * 24 * 60 * 60; // 拉取一整天的消息option.count = 20; // 每页 20 条option.lastMsg = self.lastMsg; // 上次拉取的位置 (每次返回的消息列表的最后一条消息)option.groupID = #your group id#; // 拉取群聊消息__weak typeof(self) weakSelf = self;[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {NSLog(@"success");// 记录下一次拉取的 lastMsgweakSelf.lastMsg = msgs.lastObject;} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};// 定义变量,记录每次拉取的游标V2TIMMessage lastMsg;// 分页拉取逻辑V2TIMMessageListGetOption option;option.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息option.getTimeBegin = 1640966400; // 从 2022-01-01 00:00:00 开始option.getTimePeriod = 1 * 24 * 60 * 60; // 拉取一整天的消息option.count = 20; // 每页 20 条option.lastMsg = 首次拉取 ? nullptr : &lastMsg; // 上次拉取的位置(每次返回的消息列表的最后一条消息)option.groupID = "your group id"; // 拉取群聊消息auto callback = new ValueCallback<V2TIMMessageVector>{};callback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {std::cout << "success" << std::endl;// 记录下一次拉取的 lastMsglastMsg = messageList[messageList.Size() - 1];delete callback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete callback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);
仅拉取本地消息
通过设置
getType
来实现仅拉取本地消息:getType
取值为 V2TIM_GET_LOCAL_OLDER_MSG
时,表示往时间更旧的方向,拉取本地存储的消息。getType
取值为 V2TIM_GET_LOCAL_NEWER_MSG
时,表示往时间更新的方向,拉取本地存储的消息。示例代码将演示:从最新的消息开始,往更老的方向,从本地数据库中拉取 20 条单聊消息。
V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_LOCAL_OLDER_MSG); // 拉取本地的更老的消息option.setLastMsg(null); // 设置从最新的消息开始拉取option.setCount(20); // 拉取 20 条消息option.setUserID(#you user id#); // 拉取单聊消息V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];option.getType = V2TIM_GET_LOCAL_OLDER_MSG; // 拉取本地的更老的消息option.lastMsg = nil; // 设置从最新的消息开始拉取option.count = 20; // 拉取 20 条消息option.userID = #your user id#; // 拉取单聊消息[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};V2TIMMessageListGetOption option;option.getType = V2TIMMessageGetType::V2TIM_GET_LOCAL_OLDER_MSG; // 拉取本地的更老的消息option.count = 20; // 拉取 20 条消息option.userID = "you user id"; // 拉取单聊消息auto callback = new ValueCallback<V2TIMMessageVector>{};callback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {std::cout << "success" << std::endl;delete callback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete callback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);
拉取指定类型的消息
注意:
1. 当
messageTypeList
为空时,表示拉取所有类型的消息。2. 拉取指定的消息类型,只支持从本地拉取。从云端拉取消息不支持指定消息类型。
示例代码将演示:从当前开始,往过去的方向,拉取 20 条文本和图片消息。
// 拉取图片消息、文本消息ArrayList<Integer> messageTypeList = new ArrayList<Integer>();messageTypeList.add(V2TIM_ELEM_TYPE_IMAGE);messageTypeList.add(V2TIM_ELEM_TYPE_TEXT);V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 拉取云端的更老的消息option.setCount(20);option.setMessageTypeList(messageTypeList);option.setGroupID("you group id");V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];option.getType = V2TIM_GET_CLOUD_OLDER_MSG;// 拉取图片消息、文本消息option.messageTypeList = @[@(V2TIM_ELEM_TYPE_IMAGE), @(V2TIM_ELEM_TYPE_TEXT)];option.count = 20;option.groupID = @"your group id";[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};V2TIMMessageListGetOption option;option.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息option.count = 20; // 拉取 20 条消息option.messageTypeList.PushBack(V2TIMElemType::V2TIM_ELEM_TYPE_IMAGE); // 拉取图片消息option.messageTypeList.PushBack(V2TIMElemType::V2TIM_ELEM_TYPE_TEXT); // 拉取文本消息option.userID = "you group id"; // 拉取群聊消息auto callback = new ValueCallback<V2TIMMessageVector>{};callback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {std::cout << "success" << std::endl;delete callback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete callback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);
拉取指定 sequence 的消息
在拉取群聊历史消息时,您可以按照指定的消息序列号 sequence 拉取。
说明:
仅拉取群组历史消息时有效;
消息序列号 seq 可以通过 V2TIMMessage 对象的 seq 字段获取;
当 getType 设置为从云端拉取时,会将本地存储消息列表与云端存储消息列表合并后返回;如果无网络,则直接返回本地消息列表;
当 getType 设置为从本地拉取时,直接返回本地的消息列表;
当 getType 设置为拉取更旧的消息时,消息列表按照时间逆序,也即消息按照时间戳从大往小的顺序排序;
当 getType 设置为拉取更新的消息时,消息列表按照时间顺序,也即消息按照时间戳从小往大的顺序排序。
示例代码将演示:在群组中,从云端拉取消息 sequence 为 1,3,5,9 的消息,并将消息按照时间逆序返回。
ArrayList<Long> sequenceList = new ArrayList<>();sequenceList.add(1L);sequenceList.add(3L);sequenceList.add(5L);sequenceList.add(9L);V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGroupID("your groupID");option.setMessageSeqList(sequenceList);option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG;);V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {}@Overridepublic void onError(int code, String desc) {}});
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];option.getType = V2TIM_GET_CLOUD_OLDER_MSG;option.groupID = @"your groupID";option.messageSeqList = @[@(1), @(3), @(5), @(9)];[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {} 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_;};V2TIMUInt64Vector sequence_list;sequence_list.PushBack(1);sequence_list.PushBack(3);sequence_list.PushBack(5);sequence_list.PushBack(9);V2TIMMessageListGetOption option;option.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG;option.messageSeqList = sequence_list;option.groupID = "your group id";auto callback = new ValueCallback<V2TIMMessageVector>{};callback->SetCallback([=](const V2TIMMessageVector& messageList) {std::cout << "success" << std::endl;delete callback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete callback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(option, callback);
一键跳转到群 @ 消息
在群聊会话中,收到群 @ 消息后,一般需要通过点击群 @ 提示条,跳转到群 @ 消息的位置,并拉取附近的消息列表用于显示。
由于群 @ 消息本身也需要显示,可以将 lastMsgSeq 设置成群 @ 消息的
sequence
,并使用高级接口 getHistoryMessageList
来拉取。示例代码将演示:点击群 @ 提示后,跳转到群 @ 消息,并拉取前后各 20 条消息用于展示。
// 获取群 @ 消息对应的的 sequencelong atSequence = 1081;// 拉取群 @ 消息及之前的消息V2TIMMessageListGetOption beforeOption = new V2TIMMessageListGetOption();beforeOption.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 拉取比群 @ 消息更早的消息beforeOption.setCount(20); // 拉取 20 条beforeOption.setLastMsgSeq(atSequence); // 从群 @ 消息开始拉取,包括群 @ 消息beforeOption.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(beforeOption, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {、// 返回的消息列表中包括群 @ 消息Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});// 拉取群 @ 消息之后的消息V2TIMMessageListGetOption afterOption = new V2TIMMessageListGetOption();afterOption.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_NEWER_MSG); // 拉取比群 @ 消息更晚的消息afterOption.setCount(20); // 拉取 20 条afterOption.setLastMsgSeq(atSequence + 1); // 从群 @ 消息的后一条消息开始拉取,不包括群 @ 消息afterOption.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(afterOption, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {// 返回的消息列表中不包括群 @ 消息Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
// 获取群 @ 消息对应的的 sequenceNSInteger atSequence = 1081;// 拉取群 @ 消息及之前的消息V2TIMMessageListGetOption *beforeOption = [[V2TIMMessageListGetOption alloc] init];beforeOption.getType = V2TIM_GET_CLOUD_OLDER_MSG; // 拉取比群 @ 消息更早的消息beforeOption.count = 20; // 拉取 20 条beforeOption.lastMsgSeq = atSequence; // 从群 @ 消息开始拉取, 包括群 @ 消息beforeOption.groupID = #your group id#; // 拉取群聊消息[V2TIMManager.sharedInstance getHistoryMessageList:beforeOption succ:^(NSArray<V2TIMMessage *> *msgs) {// 返回的消息列表中包括群 @ 消息NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, desc);}];// 拉取群 @ 消息之后的消息V2TIMMessageListGetOption *afterOption = [[V2TIMMessageListGetOption alloc] init];afterOption.getType = V2TIM_GET_CLOUD_NEWER_MSG; // 拉取比群 @ 消息更晚的消息afterOption.count = 20; // 拉取 20 条afterOption.lastMsgSeq = atSequence + 1; // 从群 @ 消息的后一条消息开始拉取,不包括群 @ 消息afterOption.groupID = #your group id#; // 拉取群聊消息[V2TIMManager.sharedInstance getHistoryMessageList:afterOption succ:^(NSArray<V2TIMMessage *> *msgs) {// 返回的消息列表中不包括群 @ 消息NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};// 获取群 @ 消息对应的的 sequenceuint64_t atSequence = 1081;// 拉取群 @ 消息及之前的消息V2TIMMessageListGetOption beforeOption;beforeOption.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取比群 @ 消息更早的消息beforeOption.count = 20; // 拉取 20 条消息beforeOption.lastMsgSeq = atSequence; // 从群 @ 消息开始拉取,包括群 @ 消息beforeOption.userID = "you group id"; // 拉取群聊消息auto beforeCallback = new ValueCallback<V2TIMMessageVector>{};beforeCallback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {// 返回的消息列表中包括群 @ 消息std::cout << "success" << std::endl;delete beforeCallback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete beforeCallback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(beforeOption, beforeCallback);// 拉取群 @ 消息及之前的消息V2TIMMessageListGetOption afterOption;afterOption.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_NEWER_MSG; // 拉取比群 @ 消息更晚的消息afterOption.count = 20; // 拉取 20 条消息afterOption.lastMsgSeq = atSequence + 1; // 从群 @ 消息的后一条消息开始拉取,不包括群 @ 消息afterOption.userID = "you group id"; // 拉取群聊消息auto afterCallback = new ValueCallback<V2TIMMessageVector>{};afterCallback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {// 返回的消息列表中不包括群 @ 消息std::cout << "success" << std::endl;delete afterCallback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete afterCallback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(afterOption, afterCallback);
搜索场景下的消息定位
当用户通过本地搜索获取到
V2TIMMessage
对象。点击该消息后,一般需要跳转到该消息对应的位置并展示附近的消息。
此时可以将 lastMsg
设置成该消息对象,并使用高级接口 getHistoryMessageList
拉取前后的消息。示例代码将演示:拉取搜索到的消息前后各 20 条消息,用于展示。
// 获取当前的 message 对象V2TIMMessage lastMsg = #搜索到的消息#;// 拉取指定消息之前的消息V2TIMMessageListGetOption beforeOption = new V2TIMMessageListGetOption();beforeOption.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 拉取比指定消息更早的消息beforeOption.setCount(20); // 拉取 20 条beforeOption.setLastMsg(lastMsg); // 从指定消息开始拉取,不包括该消息beforeOption.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(beforeOption, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {、// 返回的消息列表中不包括 lastMsgLog.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});// 拉取指定消息之后的消息V2TIMMessageListGetOption afterOption = new V2TIMMessageListGetOption();afterOption.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_NEWER_MSG); // 拉取比指定消息更晚的消息afterOption.setCount(20); // 拉取 20 条afterOption.setLastMsg(lastMsg); // 从指定消息开始拉取,不包括该消息afterOption.setGroupID(#you group id#); // 拉取群聊消息V2TIMManager.getMessageManager().getHistoryMessageList(afterOption, new V2TIMValueCallback<List<V2TIMMessage>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {// 返回的消息列表中不包括 lastMsgLog.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
// 获取当前的 message 对象V2TIMMessage *lastMsg = #搜索后的消息#;// 拉取指定消息之前的消息V2TIMMessageListGetOption *beforeOption = [[V2TIMMessageListGetOption alloc] init];beforeOption.getType = V2TIM_GET_CLOUD_OLDER_MSG; // 拉取比指定消息更早的消息beforeOption.count = 20; // 拉取 20 条beforeOption.lastMsg = lastMsg; // 从指定消息开始拉取, 不包括该消息beforeOption.groupID = #your group id#; // 拉取群聊消息[V2TIMManager.sharedInstance getHistoryMessageList:beforeOption succ:^(NSArray<V2TIMMessage *> *msgs) {// 返回的消息列表中不包括指定消息NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, desc);}];// 拉取指定消息之后的消息V2TIMMessageListGetOption *afterOption = [[V2TIMMessageListGetOption alloc] init];afterOption.getType = V2TIM_GET_CLOUD_NEWER_MSG; // 拉取比指定消息更晚的消息afterOption.count = 20; // 拉取 20 条afterOption.lastMsg = lastMsg; // 从指定消息开始拉取,不包括该消息afterOption.groupID = #your group id#; // 拉取群聊消息[V2TIMManager.sharedInstance getHistoryMessageList:afterOption succ:^(NSArray<V2TIMMessage *> *msgs) {// 返回的消息列表中不包括指定消息NSLog(@"success");} fail:^(int code, NSString *desc) {NSLog(@"failure, code:%d, desc:%@", code, 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_;};// 获取当前的 message 对象V2TIMMessage lastMsg = 搜索到的消息;// 拉取指定消息之前的消息V2TIMMessageListGetOption beforeOption;beforeOption.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_OLDER_MSG; // 拉取比指定消息更早的消息beforeOption.count = 20; // 拉取 20 条消息beforeOption.lastMsg = lastMsg; // 从指定消息开始拉取,不包括该消息beforeOption.userID = "you group id"; // 拉取群聊消息auto beforeCallback = new ValueCallback<V2TIMMessageVector>{};beforeCallback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {// 返回的消息列表中不包括 lastMsgstd::cout << "success" << std::endl;delete beforeCallback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete beforeCallback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(beforeOption, beforeCallback);// 拉取指定消息之后的消息V2TIMMessageListGetOption afterOption;afterOption.getType = V2TIMMessageGetType::V2TIM_GET_CLOUD_NEWER_MSG; // 拉取比指定消息更晚的消息afterOption.count = 20; // 拉取 20 条消息afterOption.lastMsg = lastMsg; // 从指定消息开始拉取,不包括该消息afterOption.userID = "you group id"; // 拉取群聊消息auto afterCallback = new ValueCallback<V2TIMMessageVector>{};afterCallback->SetCallback([=](const V2TIMMessageVector& messageList) mutable {// 返回的消息列表中不包括 lastMsgstd::cout << "success" << std::endl;delete afterCallback;},[=](int error_code, const V2TIMString& error_message) {std::cout << "error" << std::endl;delete afterCallback;});V2TIMManager::GetInstance()->GetMessageManager()->GetHistoryMessageList(afterOption, afterCallback);
解析信令消息
如果您使用了 SDK 的信令相关接口(Android / iOS & Mac),调用
invite/inviteInGroup
时,设置了 onlineUserOnly
为 false
(Android) / NO
(iOS),那么每次信令操作(包括 invite
、cancel
、accept
、reject
、timeout
)都会产生一条自定义消息,该消息会通过 V2TIMAdvancedMsgListener
的 onRecvNewMessage
回调抛给用户,也可以通过历史消息拉取。接口原型
/*** 获取信令信息** 如果 invite 设置 onlineUserOnly 为 false,每次信令操作(包括 invite、cancel、accept、reject、timeout)都会产生一条自定义消息,* 该消息会通过 V2TIMAdvancedMsgListener -> onRecvNewMessage 抛给用户,用户也可以通过历史消息拉取,如果需要根据信令信息做自定义化文本展示,可以调用下面接口获取信令信息。** @param msg 消息对象* @return V2TIMSignalingInfo 信令信息,如果为 null,则 msg 不是一条信令消息。*/public abstract V2TIMSignalingInfo getSignalingInfo(V2TIMMessage msg);
/*** 获取信令信息** 如果 invite 设置 onlineUserOnly 为 NO,每次信令操作(包括 invite、cancel、accept、reject、timeout)都会产生一条自定义消息,该消息会通过 V2TIMAdvancedMsgListener -> onRecvNewMessage 抛给用户,用户也可以通过历史消息拉取,如果需要根据信令信息做自定义化文本展示,可以调用下面接口获取信令信息。** @param msg 消息对象* @return V2TIMSignalingInfo 信令信息,如果为 nil,则 msg 不是一条信令消息。*/- (V2TIMSignalingInfo *)getSignallingInfo:(V2TIMMessage *)msg;
/*** 获取信令信息** 如果 Invite 设置 onlineUserOnly 为 false,每次信令操作(包括* Invite、Cancel、Accept、Reject、Timeout)都会产生一条自定义消息, 该消息会通过* V2TIMAdvancedMsgListener -> onRecvNewMessage* 抛给用户,用户也可以通过历史消息拉取,如果需要根据信令信息做自定义化文本展示,可以调用下面接口获取信令信息。** @param msg 消息对象* @return V2TIMSignalingInfo 信令信息,如果 V2TIMSignalingInfo::inviteID 为空字符串,则 msg 不是一条信令消息。*/virtual V2TIMSignalingInfo GetSignalingInfo(const V2TIMMessage& msg) = 0;
示例代码将演示:拉取到历史消息之后,解析信令消息的显示文本。
/// 信令消息对应的自定义文本public String getCallSignalingContentWithMessage(V2TIMMessage message) {if (message == null) {Log.e(TAG, " invalid param ");return "";}V2TIMSignalingInfo signalingInfo = V2TIMManager.getSignalingManager().getSignalingInfo(message);if (signalingInfo == null) {Log.e(TAG, " signalingInfo is null ");return "";}String content = "";Gson gson = new Gson();Object businessIdObj = null;String businessId = null;try {HashMap signalDataMap = gson.fromJson(signalingInfo.getData(), HashMap.class);if (signalDataMap != null) {businessIdObj = signalDataMap.get(TUIConstants.Message.CUSTOM_BUSINESS_ID_KEY);} else {Log.e(TAG, " signalDataMap is null ");return "";}} catch (JsonSyntaxException e) {Log.e(TAG, " get signalingInfoCustomJsonMap error ");}if (businessIdObj instanceof String) {businessId = (String) businessIdObj;}if (TextUtils.equals(businessId, "av_call")) {if (signalingInfo.getActionType() == V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_INVITE) {// 邀请通话content = "@邀请通话";} else if (signalingInfo.getActionType() == V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_ACCEPT_INVITE) {// 同意邀请content = "@同意通话";} else {// 其他// ...}}return content;}
/// 信令消息对应的自定义文本+ (NSString *)getCallSignalingContentWithMessage:(V2TIMMessage *)message{V2TIMSignalingInfo *info = [[V2TIMManager sharedInstance] getSignallingInfo:message];if (!info) {return nil;}// 解析透传的data字段NSError *err = nil;NSDictionary *param = nil;if (info.data != nil) {param = [NSJSONSerialization JSONObjectWithData:[info.data dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&err];}if (!param || ![param isKindOfClass:[NSDictionary class]]) {return nil;}// 判断业务类型NSArray *allKeys = param.allKeys;if (![allKeys containsObject:@"businessID"]) {return nil;}NSString *businessId = [param objectForKey:@"businessID"];// 判断是否为音视频通话信令NSString *content = nil;if ([businessId isEqualToString:@"av_call"]) {if (info.actionType == SignalingActionType_Invite) {// 邀请通话content = @"邀请通话";} else if (info.actionType == SignalingActionType_Accept_Invite) {// 同意邀请content = @"同意通话";} else {// 其他// ...}}return content;}
信令消息对应的自定义文本std::string GetCallSignalingContentWithMessage(const V2TIMMessage& message) {V2TIMSignalingInfo signalingInfo =V2TIMManager::GetInstance()->GetSignalingManager()->GetSignalingInfo(message);if (signalingInfo.inviteID.Empty()) {// 如果 V2TIMSignalingInfo::inviteID 为空,则 message 不是一条信令消息return {};}// 解析透传的 data 字段,使用开源库 RapidJSON :https://github.com/Tencent/rapidjson/rapidjson::Document document;document.Parse(signalingInfo.data.CString(), signalingInfo.data.Size());if (!document.HasMember("businessId") || !document["businessId"].IsString()) {return {};}const char* businessId = document["businessId"].GetString();// 判断是否为音视频通话信令std::string content;if (businessId == "av_call") {if (signalingInfo.actionType == V2TIMSignalingActionType::SignalingActionType_Invite) {// 邀请通话content = u8"邀请通话";} else if (signalingInfo.actionType == 2TIMSignalingActionType::SignalingActionType_Accept_Invite) {// 同意邀请content = u8"同意通话";} else {// 其他 ...}}return content;}
常见问题
拉历史消息时,日志中出现 "total count of request cloud message exceed max limit" 信息
SDK 目前的策略是:
1. 当
getType
设置成拉云端历史消息,且拉取 count 条消息时,SDK 会从云端拉取 count 条消息。2. SDK 过滤无效的消息,例如消息被删除、非当前用户关心的消息等。
3. 当云端历史消息中无效的消息过多,会触发 SDK 多次分页拉取。
为了提供系统的稳定性和健壮性,SDK 最多触发 3 次自动分页。当超过限制后,会出现 “total count of request cloud message exceed max limit” 的日志信息。
为了尽可能的减少此类限频机制对业务层的影响,您可以使用如下措施来减少无效消息的产生:
您可以使用在线消息,即发送消息时设置
onlineUserOnly
为 YES/true
。如果是群聊消息,可以使用群定向消息指定消息的接收者,避免产生无效消息。
拉云端历史消息时,消息 “丢失” ?
当
getType
设置成拉云端历史消息,且拉取 count 条消息时,SDK 会做如下操作:1. SDK 先从本地拉取 count 条消息。
2. SDK 再次云端拉取 count 条消息,过滤掉被删除等无效的消息,如果不够 count 条,SDK 内部触发分页拉取。
3. 将本地和云端消息进行合并,更新消息状态等信息。
4. 从合并的消息列表中,返回 count 条消息。
一般出现消息 “丢失” 时,指的是在第 2 步中拉取的无效消息过多,导致触发了问题 1 中的限频机制,从而导致实际拉取的云端消息不够。
建议按照问题 1 中的解决方法来处理,如果仍然无法解决,欢迎加入文末的 QQ 群反馈。
拉取的历史消息,群名片等群成员信息没有实时更新?
5. SDK 会在消息产生时,更新当前的群名片、role 等群成员信息并存储在本地数据库中。
6. 当拉取群历史消息时,会直接查询消息产生时的群成员信息,不会实时向后台请求更新。
拉取历史消息时卡顿
SDK 内部已对消息拉取做了性能优化,您如果碰到消息卡顿的情况,可以先尝试减少拉取的消息数
count
。