功能描述
通过
addSimpleMsgListener
监听接收文本、自定义消息,相关回调在 V2TIMSimpleMsgListener
协议中定义。通过
addAdvancedMsgListener
监听接收所有类型消息(文本、自定义、富媒体消息),相关回调在 V2TIMAdvancedMsgListener
协议中定义。设置消息监听器
SDK 提供了 2 种消息监听器,简单消息监听器
V2TIMSimpleMsgListener
和高级消息监听器 V2TIMAdvancedMsgListener
。
两者的区别在于:1. 简单消息监听器只能接收文本、自定义消息。如果您的业务只需要这两种消息,可以仅使用简单消息监听器。
2. 高级消息监听器可以接收所有类型的消息。如果您的业务还需要支持富媒体、合并消息等其他类型,请使用高级消息监听器。
注意
1.
addSimpleMsgListener
和 addAdvancedMsgListener
请使用其中之一,切勿混用,以免产生不可预知的逻辑 bug。2. 如果想要正常接收下面各种类型的消息,必须先添加消息监听器,否则无法正常接收。
简单消息监听器
添加监听器
接收方调用
addSimpleMsgListener
(Android / iOS & Mac / Windows) 添加简单消息监听器。一般建议在比较靠前的时间点调用,例如聊天消息界面初始化后,确保 App 能及时收到消息。示例代码如下:
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);
// self 为 id<V2TIMSignalingListener>[[V2TIMManager sharedInstance] addSimpleMsgListener:self];
class SimpleMsgListener final : public V2TIMSimpleMsgListener {// 成员 ...};// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调SimpleMsgListener simpleMsgListener;V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);
监听器回调事件
public abstract class V2TIMSimpleMsgListener {// 收到 C2C 文本消息public void onRecvC2CTextMessage(String msgID, V2TIMUserInfo sender, String text) {}// 收到 C2C 自定义(信令)消息public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {}// 收到群文本消息public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {}// 收到群自定义(信令)消息public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {}}
/// IMSDK 基本消息回调@protocol V2TIMSimpleMsgListener <NSObject>@optional/// 收到 C2C 文本消息- (void)onRecvC2CTextMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info text:(NSString *)text;/// 收到 C2C 自定义(信令)消息- (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data;/// 收到群文本消息- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text;/// 收到群自定义(信令)消息- (void)onRecvGroupCustomMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info customData:(NSData *)data;@end
class SimpleMsgListener final : public V2TIMSimpleMsgListener {public:SimpleMsgListener() = default;~SimpleMsgListener() override = default;// 收到 C2C 文本消息void OnRecvC2CTextMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,const V2TIMString& text) override {}// 收到 C2C 自定义(信令)消息void OnRecvC2CCustomMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,const V2TIMBuffer& customData) override {}// 收到群文本消息void OnRecvGroupTextMessage(const V2TIMString& msgID, const V2TIMString& groupID,const V2TIMGroupMemberFullInfo& sender, const V2TIMString& text) override {}// 收到群自定义(信令)消息void OnRecvGroupCustomMessage(const V2TIMString& msgID, const V2TIMString& groupID,const V2TIMGroupMemberFullInfo& sender,const V2TIMBuffer& customData) override {}};
移除监听器
示例代码如下:
V2TIMManager.getInstance().removeSimpleMsgListener(simpleMsgListener);
// self 为 id<V2TIMSignalingListener>[[V2TIMManager sharedInstance] removeSimpleMsgListener:self];
class SimpleMsgListener final : public V2TIMSimpleMsgListener {// 成员 ...};// simpleMsgListener 是 SimpleMsgListener 的实例V2TIMManager::GetInstance()->RemoveSimpleMsgListener(&simpleMsgListener);
高级消息监听器
添加监听器
接收方调用
addAdvancedMsgListener
(Android / iOS & Mac / Windows) 添加高级消息监听器。一般建议在比较靠前的时间点调用,例如聊天消息界面初始化后,确保 App 能及时收到消息。示例代码如下:
V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);
// self 为 id<V2TIMAdvancedMsgListener>[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {// 成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
监听器回调事件
public abstract class V2TIMAdvancedMsgListener {// 收到新消息public void onRecvNewMessage(V2TIMMessage msg) {}// C2C 对端用户会话已读通知(对端用户调用 markC2CMessageAsRead,自己会收到该通知)public void onRecvC2CReadReceipt(List<V2TIMMessageReceipt> receiptList) {}// 消息已读回执通知(如果自己发送的消息支持已读回执,消息接收端调用 sendMessageReadReceipts,自己会收到该通知)public void onRecvMessageReadReceipts(List<V2TIMMessageReceipt> receiptList) {}// 收到消息撤回的通知public void onRecvMessageRevoked(String msgID) {}// 消息内容被修改public void onRecvMessageModified(V2TIMMessage msg) {}}
/// 高级消息监听器@protocol V2TIMAdvancedMsgListener <NSObject>@optional/// 收到新消息- (void)onRecvNewMessage:(V2TIMMessage *)msg;/// 消息已读回执通知(如果自己发的消息支持已读回执,消息接收端调用了 sendMessageReadReceipts 接口,自己会收到该回调)- (void)onRecvMessageReadReceipts:(NSArray<V2TIMMessageReceipt *> *)receiptList;/// C2C 对端用户会话已读通知(如果对端用户调用 markC2CMessageAsRead 接口,自己会收到该通知)- (void)onRecvC2CReadReceipt:(NSArray<V2TIMMessageReceipt *> *)receiptList;/// 收到消息撤回- (void)onRecvMessageRevoked:(NSString *)msgID;/// 消息内容被修改- (void)onRecvMessageModified:(V2TIMMessage *)msg;@end
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:AdvancedMsgListener() = default;~AdvancedMsgListener() override = default;// 收到新消息void OnRecvNewMessage(const V2TIMMessage& message) override {}// C2C 对端用户会话已读通知(对端用户调用 markC2CMessageAsRead,自己会收到该通知)void OnRecvC2CReadReceipt(const V2TIMMessageReceiptVector& receiptList) override {}// 消息已读回执通知(如果自己发送的消息支持已读回执,消息接收端调用// sendMessageReadReceipts,自己会收到该通知)void OnRecvMessageReadReceipts(const V2TIMMessageReceiptVector& receiptList) override {}// 收到消息撤回的通知void OnRecvMessageRevoked(const V2TIMString& messageID) override {}// 消息内容被修改void OnRecvMessageModified(const V2TIMMessage& message) override {}};
移除监听器
示例代码如下:
V2TIMManager.getMessageManager().removeAdvancedMsgListener(advancedMsgListener);
// self 为 id<V2TIMAdvancedMsgListener>[[V2TIMManager sharedInstance] removeAdvancedMsgListener:self];
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {// 成员 ...};// advancedMsgListener 是 AdvancedMsgListener 的实例V2TIMManager::GetInstance()->GetMessageManager()->RemoveAdvancedMsgListener(&advancedMsgListener);
接收文本消息
使用简单消息监听器接收
单聊文本消息
接收方使用简单消息监听器接收单聊文本消息,需要以下几步:
1. 调用
addSimpleMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeSimpleMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。代码示例如下:
// 设置事件监听器V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);// 接收单聊文本消息/*** 收到 C2C 文本消息** @param msgID 消息唯一标识* @param sender 发送方信息* @param text 发送内容*/public void onRecvC2CTextMessage(String msgID, V2TIMUserInfo sender, String text) {// 可解析消息并展示到 UI}
// 设置事件监听器[[V2TIMManager sharedInstance] addSimpleMsgListener:self];/// 接收单聊文本消息/// @param msgID 消息 Id/// @param info 发送者信息/// @param text 文本内容- (void)onRecvC2CTextMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info text:(NSString *)text {// 可解析消息并展示到 UI}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {public:/*** 收到 C2C 文本消息** @param msgID 消息唯一标识* @param sender 发送方信息* @param text 发送内容*/void OnRecvC2CTextMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,const V2TIMString& text) override {// 可以解析消息并展示到 UI,比如:std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;}// 其他成员 ...};// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调SimpleMsgListener simpleMsgListener;V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);
群聊文本消息
接收方使用简单消息监听器接收群聊文本消息,需要以下几步:
1. 调用
addSimpleMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeSimpleMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。代码示例如下:
// 设置事件监听器V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);// 接收群聊文本消息/*** 收到群文本消息** @param msgID 消息唯一标识* @param groupID 群 ID* @param sender 发送方群成员信息* @param text 发送内容*/public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {// 可解析消息并展示到 UI}
// 设置事件监听器[[V2TIMManager sharedInstance] addSimpleMsgListener:self];/// 接收群聊文本消息/// @param msgID 消息 Id/// @param groupID 群组 ID/// @param info 发送者信息/// @param text 文本内容- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text {// 可解析消息并展示到 UI}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {public:/*** 收到群文本消息** @param msgID 消息唯一标识* @param groupID 群 ID* @param sender 发送方群成员信息* @param text 发送内容*/void OnRecvGroupTextMessage(const V2TIMString& msgID, const V2TIMString& groupID,const V2TIMGroupMemberFullInfo& sender, const V2TIMString& text) override {// 可以解析消息并展示到 UI,比如:std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;}// 其他成员 ...};// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调SimpleMsgListener simpleMsgListener;V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);
使用高级消息监听器接收
接收方使用高级消息监听器接收单聊、群聊文本消息,需要以下几步:
1. 调用
addAdvancedMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeAdvancedMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。代码示例如下:
// 设置事件监听器V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);/*** 收到新消息* @param msg 消息*/public void onRecvNewMessage(V2TIMMessage msg) {// 解析出 groupID 和 userIDString groupID = msg.getGroupID();String userID = msg.getUserID();// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 msg 中的文本消息if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) {V2TIMTextElem textElem = msg.getTextElem();String text = textElem.getText();Log.i("onRecvNewMessage", "text:" + text);}}
// 设置事件监听器[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];/// 接收消息/// @param msg 消息对象- (void)onRecvNewMessage:(V2TIMMessage *)msg {// 解析出 groupID 和 userIDNSString *groupID = msg.groupID;NSString *userID = msg.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 msg 中的文本消息if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {V2TIMTextElem *textElem = msg.textElem;NSString *text = textElem.text;NSLog(@"onRecvNewMessage, text: %@", text);}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的文本消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_TEXT) {// 文本消息auto textElem = static_cast<V2TIMTextElem*>(elem);// 消息文本V2TIMString text = textElem->text;// 可以解析消息并展示到 UI,比如:std::cout << "text:" << std::string{text.CString(), text.Size()} << std::endl;}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
接收自定义消息
使用简单消息监听器接收
单聊自定义消息
接收方使用简单消息监听器接收单聊自定义消息,需要以下几步:
1. 调用
addSimpleMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeSimpleMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。代码示例如下:
/*** 接收单聊自定义消息* @param msgID 消息 ID* @param sender 发送方信息* @param customData 发送内容*/public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {Log.i("onRecvC2CCustomMessage", "msgID:" + msgID + ", from:" + sender.getNickName() + ", content:" + new String(customData));}
/// 接收单聊自定义消息/// @param msgID 消息 ID/// @param info 发送者信息/// @param data 自定义消息二进制内容- (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data {NSLog(@"onRecvC2CCustomMessage, msgID: %@, sender: %@, customData: %@", msgID, info, data);}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {public:/*** 收到 C2C 自定义(信令)消息** @param msgID 消息唯一标识* @param sender 发送方信息* @param customData 发送内容*/void OnRecvC2CCustomMessage(const V2TIMString& msgID, const V2TIMUserFullInfo& sender,const V2TIMBuffer& customData) override {// 可以解析消息并展示到 UI,比如当 customData 为文本时:std::cout << "customData:"<< std::string{reinterpret_cast<const char*>(customData.Data()), customData.Size()}<< std::endl;}// 其他成员 ...};// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调SimpleMsgListener simpleMsgListener;V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);
群聊自定义消息
接收方使用简单消息监听器接收群聊自定义消息,需要以下几步:
1. 调用
addSimpleMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeSimpleMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。/*** 接收群聊自定义消息* @param msgID 消息 ID* @param groupID 群 ID* @param sender 发送方群成员信息* @param customData 发送内容*/public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {Log.i("onRecvGroupCustomMessage", "msgID:" + msgID + ", groupID:" + groupID + ", from:" + sender.getNickName() + ", content:" + new String(customData));}
/// 接收群聊自定义消息/// @param msgID 消息 ID/// @param groupID 群组 ID/// @param info 发送者信息/// @param text 自定义消息二进制内容- (void)onRecvGroupCustomMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info customData:(NSData *)data {NSLog(@"onRecvGroupCustomMessage, msgID: %@, groupID: %@, sender: %@, customData: %@", msgID, groupID, info, data);}
class SimpleMsgListener final : public V2TIMSimpleMsgListener {public:/*** 收到群自定义(信令)消息** @param msgID 消息唯一标识* @param groupID 群 ID* @param sender 发送方群成员信息* @param customData 发送内容*/void OnRecvGroupCustomMessage(const V2TIMString& msgID, const V2TIMString& groupID,const V2TIMGroupMemberFullInfo& sender,const V2TIMBuffer& customData) override {// 可以解析消息并展示到 UI,比如当 customData 为文本时:std::cout << "customData:"<< std::string{reinterpret_cast<const char*>(customData.Data()), customData.Size()}<< std::endl;}// 其他成员 ...};// 添加基本消息的事件监听器,注意在移除监听器之前需要保持 simpleMsgListener 的生命期,以免接收不到事件回调SimpleMsgListener simpleMsgListener;V2TIMManager::GetInstance()->AddSimpleMsgListener(&simpleMsgListener);
使用高级消息监听器接收
接收方使用高级消息监听器接收单聊、群聊自定义消息,需要以下几步:
1. 调用
addAdvancedMsgListener
设置事件监听器。3. 希望停止接收消息,调用
removeAdvancedMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。代码示例如下:
// 设置事件监听器V2TIMManager.getMessageManager().addAdvancedMsgListener(v2TIMAdvancedMsgListener);// 接收消息public void onRecvNewMessage(V2TIMMessage msg) {// 解析出 groupID 和 userIDString groupID = msg.getGroupID();String userID = msg.getUserID();// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 msg 中的自定义消息if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_CUSTOM) {V2TIMCustomElem customElem = msg.getCustomElem();String data = new String(customElem.getData());Log.i("onRecvNewMessage", "customData:" + data);}}
// 设置事件监听器[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];/// 接收消息/// @param msg 消息对象- (void)onRecvNewMessage:(V2TIMMessage *)msg {// 解析出 groupID 和 userIDNSString *groupID = msg.groupID;NSString *userID = msg.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 msg 中的自定义消息if (msg.elemType == V2TIM_ELEM_TYPE_CUSTOM) {V2TIMCustomElem *customElem = msg.customElem;NSData *customData = customElem.data;NSLog(@"onRecvNewMessage, customData: %@", customData);}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的自定义消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_CUSTOM) {// 自定义消息auto customElem = static_cast<V2TIMCustomElem*>(elem);// 自定义消息二进制数据V2TIMBuffer data = customElem->data;// 可以解析消息并展示到 UI,比如当 data 为文本时:std::cout << "data:"<< std::string{reinterpret_cast<const char*>(data.Data()), data.Size()}<< std::endl;}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
接收富媒体消息
接收富媒体消息只能使用高级消息监听器,需要以下几步:
1. 接收方调用
addAdvancedMsgListener
接口设置高级消息监听。3. 接收方解析
V2TIMMessage
消息中的 elemType
属性,并根据其类型进行二次解析,获取消息内部 Elem 中的具体内容。4. 希望停止接收消息,调用
removeAdvancedMsgListener
移除监听。该步骤不是必须的,客户可以按照业务需求调用。图片消息
一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心):
大图:将原图等比压缩。压缩后宽、高中较小的一个等于 720 像素。
缩略图:将原图等比压缩。压缩后宽、高中较小的一个等于 198 像素。
为了避免重复下载,节省资源,我们推荐您将
V2TIMImage
对象的 uuid
属性值设置到图片的下载路径中,作为图片的标识。示例代码向您演示如何从
V2TIMMessage
中解析出图片消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE) {// 图片消息V2TIMImageElem v2TIMImageElem = msg.getImageElem();// 一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心)// 大图:是将原图等比压缩,压缩后宽、高中较小的一个等于720像素。// 缩略图:是将原图等比压缩,压缩后宽、高中较小的一个等于198像素。List<V2TIMImageElem.V2TIMImage> imageList = v2TIMImageElem.getImageList();for (V2TIMImageElem.V2TIMImage v2TIMImage : imageList) {// 图片 ID,内部标识,可用于外部缓存 keyString uuid = v2TIMImage.getUUID();// 图片类型,三种类型,分别为 V2TIM_IMAGE_TYPE_ORIGIN(原图),V2TIM_IMAGE_TYPE_THUMB(缩略图)和 V2TIM_IMAGE_TYPE_LARGE(大图)int imageType = v2TIMImage.getType();// 图片大小(字节)int size = v2TIMImage.getSize();// 图片宽度int width = v2TIMImage.getWidth();// 图片高度int height = v2TIMImage.getHeight();// 图片的原始下载地址String url = v2TIMImage.getUrl();// 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载String imagePath = "/sdcard/im/image/" + "myUserID" + uuid;File imageFile = new File(imagePath);// 判断 imagePath 下有没有已经下载过的图片文件if (!imageFile.exists()) {// 下载图片v2TIMImage.downloadImage(imagePath, new V2TIMDownloadCallback() {@Overridepublic void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()}@Overridepublic void onError(int code, String desc) {// 下载失败}@Overridepublic void onSuccess() {// 下载完成}});} else {// 图片已存在}}}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_IMAGE) {V2TIMImageElem *imageElem = msg.imageElem;// 原图、大图、微缩图列表NSArray<V2TIMImage *> *imageList = imageElem.imageList;for (V2TIMImage *timImage in imageList) {// 图片 ID,内部标识,可用于外部缓存 keyNSString *uuid = timImage.uuid;// 图片类型V2TIMImageType type = timImage.type;// 图片大小(字节)int size = timImage.size;// 图片宽度int width = timImage.width;// 图片高度int height = timImage.height;// 图片的原始下载地址NSString * url = timImage.url;// 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载NSString *imagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testImage%@", timImage.uuid]];// 判断 imagePath 下有没有已经下载过的图片文件if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {// 下载图片[timImage downloadImage:imagePath progress:^(NSInteger curSize, NSInteger totalSize) {// 下载进度NSLog(@"下载图片进度:curSize:%lu,totalSize:%lu",curSize,totalSize);} succ:^{// 下载成功NSLog(@"下载图片完成");} fail:^(int code, NSString *msg) {// 下载失败NSLog(@"下载图片失败:code:%d,msg:%@",code,msg);}];} else {// 图片已存在}NSLog(@"图片信息:uuid:%@, type:%ld, size:%d, width:%d, height:%d", uuid, (long)type, size, width, height);}}}
class DownloadCallback final : public V2TIMDownloadCallback {public:using SuccessCallback = std::function<void()>;using ErrorCallback = std::function<void(int, const V2TIMString&)>;using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;DownloadCallback() = default;~DownloadCallback() override = default;void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,DownLoadProgressCallback download_progress_callback) {success_callback_ = std::move(success_callback);error_callback_ = std::move(error_callback);download_progress_callback_ = std::move(download_progress_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);}}void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {if (download_progress_callback_) {download_progress_callback_(currentSize, totalSize);}}private:SuccessCallback success_callback_;ErrorCallback error_callback_;DownLoadProgressCallback download_progress_callback_;};class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的图片消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_IMAGE) {// 图片消息auto imageElem = static_cast<V2TIMImageElem*>(elem);// 一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK// 会在发送图片消息的时候自动生成微缩图、大图,客户不需要关心)// 大图:是将原图等比压缩,压缩后宽、高中较小的一个等于720像素。// 缩略图:是将原图等比压缩,压缩后宽、高中较小的一个等于198像素。V2TIMImageVector imageList = imageElem->imageList;for (size_t i = 0; i < imageList.Size(); ++i) {V2TIMImage& image = imageList[i];/// 图片 ID,内部标识,可用于外部缓存 keyV2TIMString uuid = image.uuid;/// 图片类型V2TIMImageType type = image.type;/// 图片大小(type == V2TIMImageType::V2TIM_IMAGE_TYPE_ORIGIN 有效)uint64_t size = image.size;/// 图片宽度uint32_t width = image.width;/// 图片高度uint32_t height = image.height;// 图片的原始下载地址V2TIMString url = image.url;// 设置图片下载路径 path,这里可以用 uuid 作为标识,避免重复下载std::filesystem::path imagePath = u8"./File/Image/"s + uuid.CString();// 判断 imagePath 下有没有已经下载过的图片文件if (!std::filesystem::exists(imagePath)) {std::filesystem::create_directories(imagePath.parent_path());auto callback = new DownloadCallback{};callback->SetCallback([=]() {// 下载完成delete callback;},[=](int error_code, const V2TIMString& error_message) {// 下载失败delete callback;},[=](uint64_t currentSize, uint64_t totalSize) {// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize});image.DownloadImage(imagePath.string().c_str(), callback);} else {// 图片已存在}}}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
视频消息
接收方收到视频消息后,一般需要在聊天界面显示一个视频预览图,当用户点击消息后,才会触发视频的播放。
所以这里需要两步:
为了避免重复下载,节省资源,我们推荐您将
V2TIMVideoElem
对象的 videoUUID
属性值设置到视频的下载路径中,作为视频的标识。示例代码向您演示如何从
V2TIMMessage
中解析出视频消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {// 视频消息V2TIMVideoElem v2TIMVideoElem = msg.getVideoElem();// 视频截图 ID,内部标识,可用于外部缓存 keyString snapshotUUID = v2TIMVideoElem.getSnapshotUUID();// 视频截图文件大小int snapshotSize = v2TIMVideoElem.getSnapshotSize();// 视频截图宽int snapshotWidth = v2TIMVideoElem.getSnapshotWidth();// 视频截图高int snapshotHeight = v2TIMVideoElem.getSnapshotHeight();// 视频 ID,内部标识,可用于外部缓存 keyString videoUUID = v2TIMVideoElem.getVideoUUID();// 视频文件大小int videoSize = v2TIMVideoElem.getVideoSize();// 视频时长int duration = v2TIMVideoElem.getDuration();// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载String snapshotPath = "/sdcard/im/snapshot/" + "myUserID" + snapshotUUID;File snapshotFile = new File(snapshotPath);if (!snapshotFile.exists()) {v2TIMVideoElem.downloadSnapshot(snapshotPath, new V2TIMDownloadCallback() {@Overridepublic void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()}@Overridepublic void onError(int code, String desc) {// 下载失败}@Overridepublic void onSuccess() {// 下载完成}});} else {// 文件已存在}// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载String videoPath = "/sdcard/im/video/" + "myUserID" + videoUUID;File videoFile = new File(videoPath);if (!videoFile.exists()) {v2TIMVideoElem.downloadVideo(videoPath, new V2TIMDownloadCallback() {@Overridepublic void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()}@Overridepublic void onError(int code, String desc) {// 下载失败}@Overridepublic void onSuccess() {// 下载完成}});} else {// 文件已存在}}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_VIDEO) {V2TIMVideoElem *videoElem = msg.videoElem;// 视频截图 ID,内部标识,可用于外部缓存 keyNSString *snapshotUUID = videoElem.snapshotUUID;// 视频截图文件大小int snapshotSize = videoElem.snapshotSize;// 视频截图宽int snapshotWidth = videoElem.snapshotWidth;// 视频截图高int snapshotHeight = videoElem.snapshotHeight;// 视频 ID,内部标识,可用于外部缓存 keyNSString *videoUUID = videoElem.videoUUID;// 视频文件大小int videoSize = videoElem.videoSize;// 视频时长int duration = videoElem.duration;// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载NSString *snapshotPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testVideoSnapshot%@",snapshotUUID]];if (![[NSFileManager defaultManager] fileExistsAtPath:snapshotPath]) {// 下载视频截图[videoElem downloadSnapshot:snapshotPath progress:^(NSInteger curSize, NSInteger totalSize) {// 下载进度NSLog(@"%@", [NSString stringWithFormat:@"下载视频截图进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);} succ:^{// 下载成功NSLog(@"下载视频截图完成");} fail:^(int code, NSString *msg) {// 下载失败NSLog(@"%@", [NSString stringWithFormat:@"下载视频截图失败:code:%d,msg:%@",code,msg]);}];} else {// 视频截图已存在}NSLog(@"视频截图信息:snapshotUUID:%@, snapshotSize:%d, snapshotWidth:%d, snapshotWidth:%d, snapshotPath:%@", snapshotUUID, snapshotSize, snapshotWidth, snapshotHeight, snapshotPath);// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载NSString *videoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testVideo%@",videoUUID]];if (![[NSFileManager defaultManager] fileExistsAtPath:videoPath]) {// 下载视频[videoElem downloadVideo:videoPath progress:^(NSInteger curSize, NSInteger totalSize) {// 下载进度NSLog(@"%@", [NSString stringWithFormat:@"下载视频进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);} succ:^{// 下载成功NSLog(@"下载视频完成");} fail:^(int code, NSString *msg) {// 下载失败NSLog(@"%@", [NSString stringWithFormat:@"下载视频失败:code:%d,msg:%@",code,msg]);}];} else {// 视频已存在}NSLog(@"视频信息:videoUUID:%@, videoSize:%d, duration:%d, videoPath:%@", videoUUID, videoSize, duration, videoPath);}}
class DownloadCallback final : public V2TIMDownloadCallback {public:using SuccessCallback = std::function<void()>;using ErrorCallback = std::function<void(int, const V2TIMString&)>;using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;DownloadCallback() = default;~DownloadCallback() override = default;void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,DownLoadProgressCallback download_progress_callback) {success_callback_ = std::move(success_callback);error_callback_ = std::move(error_callback);download_progress_callback_ = std::move(download_progress_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);}}void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {if (download_progress_callback_) {download_progress_callback_(currentSize, totalSize);}}private:SuccessCallback success_callback_;ErrorCallback error_callback_;DownLoadProgressCallback download_progress_callback_;};class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的视频消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_VIDEO) {// 视频消息auto videoElem = static_cast<V2TIMVideoElem*>(elem);// 视频 ID,内部标识,可用于外部缓存 keyV2TIMString videoUUID = videoElem->videoUUID;// 视频大小uint64_t videoSize = videoElem->videoSize;// 视频类型V2TIMString videoType = videoElem->videoType;// 视频时长uint32_t duration = videoElem->duration;// 截图 ID,内部标识,可用于外部缓存 keyV2TIMString snapshotUUID = videoElem->snapshotUUID;// 截图 sizeuint64_t snapshotSize = videoElem->snapshotSize;// 截图宽uint32_t snapshotWidth = videoElem->snapshotWidth;// 截图高uint32_t snapshotHeight = videoElem->snapshotHeight;// 设置视频文件路径,这里可以用 uuid 作为标识,避免重复下载std::filesystem::path videoPath = u8"./File/Video/"s + videoUUID.CString();// 判断 videoPath 下有没有已经下载过的视频文件if (!std::filesystem::exists(videoPath)) {std::filesystem::create_directories(videoPath.parent_path());auto callback = new DownloadCallback{};callback->SetCallback([=]() {// 下载完成delete callback;},[=](int error_code, const V2TIMString& error_message) {// 下载失败delete callback;},[=](uint64_t currentSize, uint64_t totalSize) {// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize});videoElem->DownloadVideo(videoPath.string().c_str(), callback);} else {// 视频文件已存在}// 设置视频截图文件路径,这里可以用 uuid 作为标识,避免重复下载std::filesystem::path snapshotPath = u8"./File/Snapshot/"s + snapshotUUID.CString();// 判断 snapshotPath 下有没有已经下载过的视频截图文件if (!std::filesystem::exists(snapshotPath)) {std::filesystem::create_directories(snapshotPath.parent_path());auto callback = new DownloadCallback{};callback->SetCallback([=]() {// 下载完成delete callback;},[=](int error_code, const V2TIMString& error_message) {// 下载失败delete callback;},[=](uint64_t currentSize, uint64_t totalSize) {// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize});videoElem->DownloadSnapshot(snapshotPath.string().c_str(), callback);} else {//视频截图文件已存在}}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
语音消息
为了避免重复下载,节省资源,我们推荐您将
V2TIMSoundElem
对象的 uuid
属性值设置到语音的下载路径中,作为语音的标识。示例代码向您演示如何从
V2TIMMessage
中解析出语音消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_SOUND) {// 语音消息V2TIMSoundElem v2TIMSoundElem = msg.getSoundElem();// 语音 ID,内部标识,可用于外部缓存 keyString uuid = v2TIMSoundElem.getUUID();// 语音文件大小int dataSize = v2TIMSoundElem.getDataSize();// 语音时长int duration = v2TIMSoundElem.getDuration();// 设置语音文件路径 soundPath,这里可以用 uuid 作为标识,避免重复下载String soundPath = "/sdcard/im/sound/" + "myUserID" + uuid;File imageFile = new File(soundPath);// 判断 soundPath 下有没有已经下载过的语音文件if (!imageFile.exists()) {v2TIMSoundElem.downloadSound(soundPath, new V2TIMDownloadCallback() {@Overridepublic void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()}@Overridepublic void onError(int code, String desc) {// 下载失败}@Overridepublic void onSuccess() {// 下载完成}});} else {// 文件已存在}}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_SOUND) {V2TIMSoundElem *soundElem = msg.soundElem;// 语音 ID,内部标识,可用于外部缓存 keyNSString *uuid = soundElem.uuid;// 语音文件大小int dataSize = soundElem.dataSize;// 语音时长int duration = soundElem.duration;// 设置语音文件路径 soundPath,这里可以用 uuid 作为标识,避免重复下载NSString *soundPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testSound%@",uuid]];// 判断 soundPath 下有没有已经下载过的语音文件if (![[NSFileManager defaultManager] fileExistsAtPath:soundPath]) {// 下载语音[soundElem downloadSound:soundPath progress:^(NSInteger curSize, NSInteger totalSize) {// 下载进度NSLog(@"下载语音进度:curSize:%lu,totalSize:%lu",curSize,totalSize);} succ:^{// 下载成功NSLog(@"下载语音完成");} fail:^(int code, NSString *msg) {// 下载失败NSLog(@"下载语音失败:code:%d,msg:%@",code,msg);}];} else {// 语音已存在}NSLog(@"语音信息:uuid:%@, dataSize:%d, duration:%d, soundPath:%@", uuid, dataSize, duration, soundPath);}}
class DownloadCallback final : public V2TIMDownloadCallback {public:using SuccessCallback = std::function<void()>;using ErrorCallback = std::function<void(int, const V2TIMString&)>;using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;DownloadCallback() = default;~DownloadCallback() override = default;void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,DownLoadProgressCallback download_progress_callback) {success_callback_ = std::move(success_callback);error_callback_ = std::move(error_callback);download_progress_callback_ = std::move(download_progress_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);}}void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {if (download_progress_callback_) {download_progress_callback_(currentSize, totalSize);}}private:SuccessCallback success_callback_;ErrorCallback error_callback_;DownLoadProgressCallback download_progress_callback_;};class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的语音消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_SOUND) {// 语音消息auto soundElem = static_cast<V2TIMSoundElem*>(elem);// 语音消息内部 IDV2TIMString uuid = soundElem->uuid;// 语音数据大小uint64_t dataSize = soundElem->dataSize;// 语音长度(秒)uint32_t duration = soundElem->duration;// 设置语音文件路径,这里可以用 uuid 作为标识,避免重复下载std::filesystem::path soundPath = u8"./File/Sound/"s + uuid.CString();// 判断 soundPath 下有没有已经下载过的语音文件if (!std::filesystem::exists(soundPath)) {std::filesystem::create_directories(soundPath.parent_path());auto callback = new DownloadCallback{};callback->SetCallback([=]() {// 下载完成delete callback;},[=](int error_code, const V2TIMString& error_message) {// 下载失败delete callback;},[=](uint64_t currentSize, uint64_t totalSize) {// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize});soundElem->DownloadSound(soundPath.string().c_str(), callback);} else {// 语音文件已存在}}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
文件消息
为了避免重复下载,节省资源,我们推荐您将
V2TIMFileElem
对象的 uuid
属性值设置到文件的下载路径中,作为文件的标识。示例代码向您演示如何从
V2TIMMessage
中解析出文件消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_FILE) {// 文件消息V2TIMFileElem v2TIMFileElem = msg.getFileElem();// 文件 ID,内部标识,可用于外部缓存 keyString uuid = v2TIMFileElem.getUUID();// 文件名称String fileName = v2TIMFileElem.getFileName();// 文件大小int fileSize = v2TIMFileElem.getFileSize();// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载String filePath = "/sdcard/im/file/" + "myUserID" + uuid;File file = new File(filePath);if (!file.exists()) {v2TIMFileElem.downloadFile(filePath, new V2TIMDownloadCallback() {@Overridepublic void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {// 下载进度回调:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()}@Overridepublic void onError(int code, String desc) {// 下载失败}@Overridepublic void onSuccess() {// 下载完成}});} else {// 文件已存在}}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_FILE) {V2TIMFileElem *fileElem = msg.fileElem;// 文件 ID,内部标识,可用于外部缓存 keyNSString *uuid = fileElem.uuid;// 文件名称NSString *filename = fileElem.filename;// 文件大小int fileSize = fileElem.fileSize;// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"testFile%@",uuid]];if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {// 下载文件[fileElem downloadFile:filePath progress:^(NSInteger curSize, NSInteger totalSize) {// 下载进度NSLog(@"%@", [NSString stringWithFormat:@"下载文件进度:curSize:%lu,totalSize:%lu",curSize,totalSize]);} succ:^{// 下载成功NSLog(@"下载文件完成");} fail:^(int code, NSString *msg) {// 下载失败NSLog(@"%@", [NSString stringWithFormat:@"下载文件失败:code:%d,msg:%@",code,msg]);}];} else {// 文件已存在}NSLog(@"文件信息:uuid:%@, filename:%@, fileSize:%d, filePath:%@", uuid, filename, fileSize, filePath);}}
class DownloadCallback final : public V2TIMDownloadCallback {public:using SuccessCallback = std::function<void()>;using ErrorCallback = std::function<void(int, const V2TIMString&)>;using DownLoadProgressCallback = std::function<void(uint64_t, uint64_t)>;DownloadCallback() = default;~DownloadCallback() override = default;void SetCallback(SuccessCallback success_callback, ErrorCallback error_callback,DownLoadProgressCallback download_progress_callback) {success_callback_ = std::move(success_callback);error_callback_ = std::move(error_callback);download_progress_callback_ = std::move(download_progress_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);}}void OnDownLoadProgress(uint64_t currentSize, uint64_t totalSize) override {if (download_progress_callback_) {download_progress_callback_(currentSize, totalSize);}}private:SuccessCallback success_callback_;ErrorCallback error_callback_;DownLoadProgressCallback download_progress_callback_;};class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的文件消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_FILE) {// 文件消息auto fileElem = static_cast<V2TIMFileElem*>(elem);// 文件 ID,内部标识,可用于外部缓存 keyV2TIMString uuid = fileElem->uuid;// 文件显示名称V2TIMString filename = fileElem->filename;// 文件大小uint64_t fileSize = fileElem->fileSize;// 设置文件路径,这里可以用 uuid 作为标识,避免重复下载std::filesystem::path filePath = u8"./File/File/"s + uuid.CString();// 判断 filePath 下有没有已经下载过的文件if (!std::filesystem::exists(filePath)) {std::filesystem::create_directories(filePath.parent_path());auto callback = new DownloadCallback{};callback->SetCallback([=]() {// 下载完成delete callback;},[=](int error_code, const V2TIMString& error_message) {// 下载失败delete callback;},[=](uint64_t currentSize, uint64_t totalSize) {// 下载进度回调:已下载大小 currentSize; 总文件大小 totalSize});fileElem->DownloadFile(filePath.string().c_str(), callback);} else {// 文件已存在}}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
地理位置消息
接收到地理位置消息后,接收方可直接从
V2TIMLocationElem
中解析出经纬度信息。
示例代码向您演示如何从 V2TIMMessage
中解析出地理位置消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_LOCATION) {// 地理位置消息V2TIMLocationElem v2TIMLocationElem = msg.getLocationElem();// 地理位置信息描述String desc = v2TIMLocationElem.getDesc();// 经度double longitude = v2TIMLocationElem.getLongitude();// 纬度double latitude = v2TIMLocationElem.getLatitude();}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_LOCATION) {V2TIMLocationElem *locationElem = msg.locationElem;// 地理位置信息描述NSString *desc = locationElem.desc;// 经度double longitude = locationElem.longitude;// 纬度double latitude = locationElem.latitude;NSLog(@"地理位置信息:desc:%@, longitude:%f, latitude:%f", desc, longitude, latitude);}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的地理位置消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_LOCATION) {// 地理位置消息auto locationElem = static_cast<V2TIMLocationElem*>(elem);// 地理位置描述信息V2TIMString desc = locationElem->desc;// 经度,发送消息时设置double longitude = locationElem->longitude;// 纬度,发送消息时设置double latitude = locationElem->latitude;}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
表情消息
SDK 仅为表情消息提供消息透传的通道,消息内容字段参考
V2TIMFaceElem
(Android / iOS & Mac / Windows) 定义。其中 index
和 data
的内容由客户自定义。示例:
发送方可设置 index = 1, data = "x12345",表示 “微笑” 表情。
接收方收到表情消息后解析出 1 和 "x12345",按照预设的规则将其展示为 “微笑” 表情。
示例代码向您演示如何从
V2TIMMessage
中解析出表情消息内容:public void onRecvNewMessage(V2TIMMessage msg) {if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_FACE) {// 表情消息V2TIMFaceElem v2TIMFaceElem = msg.getFaceElem();// 表情所在的位置int index = v2TIMFaceElem.getIndex();// 表情自定义数据byte[] data = v2TIMFaceElem.getData();}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.elemType == V2TIM_ELEM_TYPE_FACE) {V2TIMFaceElem *faceElem = msg.faceElem;// 表情所在的位置int index = faceElem.index;// 表情自定义数据NSData *data = faceElem.data;NSLog(@"表情信息:index: %d, data: %@", index, data);}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的表情消息if (message.elemList.Size() == 1) {V2TIMElem* elem = message.elemList[0];if (elem->elemType == V2TIMElemType::V2TIM_ELEM_TYPE_FACE) {// 表情消息auto faceElem = static_cast<V2TIMFaceElem*>(elem);// 表情所在的位置uint32_t index = faceElem->index;// 表情自定义数据V2TIMBuffer data = faceElem->data;}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
接收广播消息
您只能调用 REST API 直播群广播消息 向同一个 AppID 下面所有的直播群发送广播消息,所有直播群在线群成员均能收到此消息。
SDK 不能发送广播消息,只能接收来自于 REST API 的消息。
接收消息依然是在上文所述的
onRecvNewMessage
通知里。您可以在收到消息后,根据消息的 isBroadcastMessage
判断该消息是否是广播消息。示例代码如下:
public void onRecvNewMessage(V2TIMMessage msg) {if (msg.isBroadcastMessage) {// 收到了广播消息}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {if (msg.isBroadcastMessage) {// 收到了广播消息}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {if (message.isBroadcastMessage) {// 收到了广播消息}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener);
接收多个 Elem 的消息
1. 通过 Message 对象正常解析出第一个 Elem 对象。
2. 通过第一个 Elem 对象的
nextElem
方法获取下一个 Elem 对象,如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 nil/null。示例代码如下:
@Overridepublic void onRecvNewMessage(V2TIMMessage msg) {// 查看第一个 Elemint elemType = msg.getElemType();if (elemType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) {// 文本消息V2TIMTextElem v2TIMTextElem = msg.getTextElem();String text = v2TIMTextElem.getText();// 查看 v2TIMTextElem 后面还有没有更多 elemV2TIMElem elem = v2TIMTextElem.getNextElem();while (elem != null) {// 判断 elem 类型,以 V2TIMCustomElem 为例if (elem instanceof V2TIMCustomElem) {V2TIMCustomElem customElem = (V2TIMCustomElem) elem;byte[] data = customElem.getData();}// 继续查看当前 elem 后面还有没更多 elemelem = elem.getNextElem();}// elem 如果为 null,表示所有 elem 都已经解析完}}
- (void)onRecvNewMessage:(V2TIMMessage *)msg {// 查看第一个 Elemif (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {V2TIMTextElem *textElem = msg.textElem;NSString *text = textElem.text;NSLog(@"文本信息 : %@", text);// 查看 textElem 后面还有没更多 ElemV2TIMElem *elem = textElem.nextElem;while (elem != nil) {// 判断 elem 类型if ([elem isKindOfClass:[V2TIMCustomElem class]]) {V2TIMCustomElem *customElem = (V2TIMCustomElem *)elem;NSData *customData = customElem.data;NSLog(@"自定义信息 : %@",customData);}// 继续查看当前 elem 后面还有没更多 elemelem = elem.nextElem;}// elem 如果为 nil,表示所有 elem 都已经解析完}}
class AdvancedMsgListener final : public V2TIMAdvancedMsgListener {public:/*** 收到新消息** @param message 消息*/void OnRecvNewMessage(const V2TIMMessage& message) override {// 解析出 groupID 和 userIDV2TIMString groupID = message.groupID;V2TIMString userID = message.userID;// 判断当前是单聊还是群聊:// 如果 groupID 不为空,表示此消息为群聊;如果 userID 不为空,表示此消息为单聊// 解析出 message 中的多个 Elem 消息// 所有的 Elem 都保存在 message.elemList 中,访问这些 Elem 需要遍历 message.elemListfor (size_t i = 0; i < message.elemList.Size(); ++i) {V2TIMElem* elem = message.elemList[i];switch (elem->elemType) {case V2TIMElemType::V2TIM_ELEM_TYPE_NONE: {// 未知消息} break;case V2TIMElemType::V2TIM_ELEM_TYPE_TEXT: {// 文本消息auto textElem = static_cast<V2TIMTextElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_CUSTOM: {// 自定义消息auto customElem = static_cast<V2TIMCustomElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_IMAGE: {// 图片消息auto imageElem = static_cast<V2TIMImageElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_SOUND: {// 语音消息auto soundElem = static_cast<V2TIMSoundElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_VIDEO: {// 视频消息auto videoElem = static_cast<V2TIMVideoElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_FILE: {// 文件消息auto fileElem = static_cast<V2TIMFileElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_LOCATION: {// 地理位置消息auto locationElem = static_cast<V2TIMLocationElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_FACE: {// 表情消息auto faceElem = static_cast<V2TIMFaceElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_GROUP_TIPS: {// 群 Tips 消息auto mergerElem = static_cast<V2TIMMergerElem*>(elem);} break;case V2TIMElemType::V2TIM_ELEM_TYPE_MERGER: {// 合并消息auto groupTipsElem = static_cast<V2TIMGroupTipsElem*>(elem);} break;default: {} break;}}}// 其他成员 ...};// 添加高级消息的事件监听器,注意在移除监听器之前需要保持 advancedMsgListener 的生命期,以免接收不到事件回调AdvancedMsgListener advancedMsgListener;V2TIMManager::GetInstance()->GetMessageManager()->AddAdvancedMsgListener(&advancedMsgListener)