有奖捉虫:云通信与企业服务文档专题,速来> HOT

消息的分类

腾讯云 IM 消息按照消息的发送目标可以分为:“单聊消息”(又称 “C2C 消息”)和“群聊消息” 两种:

消息分类 API 关键词 说明
单聊消息 C2CMessage 又称 C2C 消息,在发送时需要指定消息接收者的 UserID,只有接受者可以收到该消息。
群聊消息 GroupMessage 在发送时需要指定目标群组的 groupID,该群中的所有用户均能收到消息。

按照消息承载的内容可以分为:“文本消息”、“自定义(信令)消息”,“图片消息”、“视频消息”、“语音消息”、“文件消息”、“位置消息”、“合并消息”、“群 Tips 消息”等几种类型。

消息分类 API 关键词 说明
文本消息 TextElem 即普通的文字消息。
自定义消息 CustomElem 即一段二进制 buffer,通常用于传输您应用中的自定义信令。
图片消息 ImageElem SDK 会在发送原始图片的同时,自动生成两种不同尺寸的缩略图,三张图分别被称为原图、大图、微缩图。
视频消息 VideoElem 一条视频消息包含一个视频文件和一张配套的缩略图。
语音消息 SoundElem 支持语音是否播放红点展示。
文件消息 FileElem 文件消息最大支持100MB。
位置消息 LocationElem 地理位置消息由位置描述、经度(longitude )和纬度(latitude)三个字段组成。
合并消息 MergerElem 最大支持 300 条消息合并
群 Tips 消息 GroupTipsElem 群 Tips 消息常被用于承载群中的系统性通知消息,例如有成员进出群组,群的描述信息被修改,群成员的资料发生变化等。

收发简单消息

V2TIMManager 中提供了一组简单的消息收发接口,虽只能用于文本消息和自定义(信令)消息的收发,但使用方法特别简单,只需要几分钟即可完成对接。

发送文本和信令消息

调用 sendC2CTextMessage 或者 sendGroupTextMessage 可以发送文本消息,调用 sendC2CCustomMessage 或者 sendGroupCustomMessage 可以发送 C2C 自定义(信令)消息,自定义消息本质是一段二进制 buffer,通常用于传输您应用中的自定义信令。

接收文本和信令消息

通过 addSimpleMsgListener 可以监听简单的文本和信令消息,复杂的图片、视频、语音消息则需要通过 V2TIMMessageManager 中定义的 addAdvancedMsgListener 实现。

注意:

addSimpleMsgListeneraddAdvancedMsgListener 请勿混用,以免产生逻辑 BUG。

经典示例:收发弹幕消息

直播场景下,在直播群中收发弹幕消息是非常普遍的交互方式,其实现方式非常简单,通过简单消息接口即可满足:

  1. 主播调用 createGroup 创建一个直播群(AVChatRoom),并在“正在直播”的房间列表中记录群组 ID。
  2. 观众选择自己喜欢的主播,并调用 joinGroup 加入该主播创建的直播群。
  3. 消息的发送方可以通过 sendGroupTextMessage 群发弹幕文本消息。
  4. 消息的接收方可以通过 addSimpleMsgListener 注册简单消息监听器,并通过监听回调函数 onRecvGroupTextMessage 获取文本消息。

为直播间增加“点赞飘心”的功能,“点赞飘心”属于一条指令,操作步骤如下:

  1. 定义一个的自定义消息类型,例如一个 JSON 字符串:{ "command": "favor", "value": 101 }
  2. 通过 sendGroupCustomMessage 接口进行消息的发送,并通过 onRecvGroupCustomMessage 进行接收。

收发富媒体消息

图片、视频、语音、文件、地理位置等类型的消息称为“富媒体消息”。相比于简单消息,富媒体消息的收发相对复杂:

  • 在发送时,富媒体消息需要先用对应的 create 函数创建一个 V2TIMMessage 对象,再调用对应的 send 接口发送。
  • 在接收时,富媒体消息要先判断 elemType,并根据 elemType 获取对应的 Elem 进行二次解析。

发送富媒体消息

本文以图片消息为例,介绍发送一条富媒体消息的过程:

  1. 发送方调用 createImageMessage 创建一条图片消息,拿到消息对象 V2TIMMessage
  2. 发送方调用 sendMessage 接口将刚才创建的消息对象发送出去。

接收富媒体消息

  1. 接收方调用 addAdvancedMsgListener 接口设置高级消息监听。
  2. 接收方通过监听回调 onRecvNewMessage 获取图片消息 V2TIMMessage
  3. 接收方解析 V2TIMMessage 消息中的 elemType 属性,并根据其类型进行二次解析,获取消息内部 Elem 中的具体内容。

经典示例:收发图片

发送方创建一条图片消息并发送:

// 创建图片消息
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createImageMessage("/sdcard/test.png");
// 发送图片消息
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, "toUserID", null, V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, null,  new V2TIMSendCallback<V2TIMMessage>() {
    @Override
    public void onError(int code, String desc) {
        // 图片消息发送失败
    }
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {
        // 图片消息发送成功
    }
    @Override
    public void onProgress(int progress) {
        // 图片上传进度(0-100)
    }
});

接收方识别一条图片消息并将解析中包含的原图、大图和微缩图:

@Override
public void onRecvNewMessage(V2TIMMessage msg) {
    int elemType = msg.getElemType();
    if (elemType == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE) {
        V2TIMImageElem v2TIMImageElem = msg.getImageElem();
        // 一个图片消息会包含三种格式大小的图片,分别为原图、大图、微缩图(SDK内部自动生成大图和微缩图)
        // 大图:是将原图等比压缩,压缩后宽、高中较小的一个等于720像素。
        // 缩略图:是将原图等比压缩,压缩后宽、高中较小的一个等于198像素。
        List<V2TIMImageElem.V2TIMImage> imageList = v2TIMImageElem.getImageList();
            for (V2TIMImageElem.V2TIMImage v2TIMImage : imageList) {
                String uuid = v2TIMImage.getUUID(); // 图片 ID
                int imageType = v2TIMImage.getType(); // 图片类型
                int size = v2TIMImage.getSize(); // 图片大小(字节)
                int width = v2TIMImage.getWidth(); // 图片宽度
                int height = v2TIMImage.getHeight(); // 图片高度
                // 设置图片下载路径 imagePath,这里可以用 uuid 作为标识,避免重复下载
                String imagePath = "/sdcard/im/image/" + "myUserID" + uuid;
                File imageFile = new File(imagePath);
                if (imageFile.exists()) {
                    v2TIMImage.downloadImage(imagePath, new V2TIMDownloadCallback() {
                        @Override
                        public void onProgress(V2TIMElem.V2ProgressInfo progressInfo) {
                            // 图片下载进度:已下载大小 v2ProgressInfo.getCurrentSize();总文件大小 v2ProgressInfo.getTotalSize()
                        }
                        @Override
                        public void onError(int code, String desc) {
                            // 图片下载失败
                        }
                        @Override
                        public void onSuccess() {
                            // 图片下载完成
                        }
                    });
            } else {
                // 图片已存在
            }
        }   
    }
}

说明:

更多消息解析示例代码请参考 常见问题 > 5. 各类型消息应该如何解析

收发群 @ 消息

群 @ 消息,发送方可以在输入栏监听 @ 字符输入,调用到群成员选择界面,选择完成后以 “@A @B @C......” 形式显示在输入框,并可以继续编辑消息内容,完成消息发送。接收方会在会话界面的群聊天列表,重点显示 “有人@我” 或者 “@所有人” 标识,提醒用户有人在群里 @ 自己了。

说明:

目前仅支持文本 @ 消息。

监听 @ 字符选择群成员 编辑群 @ 消息发送 收到群 @ 消息

发送群 @ 消息

  1. 发送方监听聊天界面的文本输入框,启动群成员选择界面,选择完成后回传选择群成员的 ID 和昵称信息,ID 用来构建消息对象 V2TIMMessage,昵称用来在文本框显示。
  2. 发送方调用 V2TIMMessageManagercreateTextAtMessage 创建一条 @ 文本消息,拿到消息对象 V2TIMMessage
  3. 发送方调用 sendMessage 接口将刚才创建的 @ 消息对象发送出去。

接收群 @ 消息

  1. 在加载和更新会话处,需要调用 V2TIMConversationgetGroupAtInfoList 接口获取会话的 @ 数据列表。
  2. 通过列表中 V2TIMGroupAtInfo 对象的 getAtType 接口获取 @ 数据类型,并更新到当前会话的 @ 信息。

经典示例:收发群 @ 消息

  • 发送群 @ 消息
    发送方创建一条群 @ 消息并发送:

    // 获取群成员ID数据
    List<String> atUserList = updateAtUserList(mTextInput.getMentionList(true));
    // 创建群@消息
    V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createTextAtMessage(message, atUserList);
    // 发送群@消息
    V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, null, "toGroupID",  V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, null,  new V2TIMSendCallback<V2TIMMessage>() {
        @Override
        public void onError(int code, String desc) {
            // 群@消息发送失败
        }
        @Override
        public void onSuccess(V2TIMMessage v2TIMMessage) {
            // 群@消息发送成功
        }
        @Override
        public void onProgress(int progress) {
    
        }
    });

  • 接收群 @ 消息
    加载和更新会话处,获取群 @ 数据列表:

    boolean atMe = false;
    boolean atAll = false;
    //获取群@数据列表
    List<V2TIMGroupAtInfo> atInfoList = conversation.getGroupAtInfoList();
    if (atInfoList == null || atInfoList.isEmpty()){
        return V2TIMGroupAtInfo.TIM_AT_UNKNOWN;
    }
    //获取@数据类型
    for(V2TIMGroupAtInfo atInfo : atInfoList){
        if (atInfo.getAtType() == V2TIMGroupAtInfo.TIM_AT_ME){
            atMe = true;
            continue;
        }
        if (atInfo.getAtType() == V2TIMGroupAtInfo.TIM_AT_ALL){
            atAll = true;
            continue;
        }
    }
    
    if (atAll && atMe){
        atInfoType = V2TIMGroupAtInfo.TIM_AT_ALL_AT_ME;
    } else if (atAll){
        atInfoType = V2TIMGroupAtInfo.TIM_AT_ALL;
    } else if (atMe){
        atInfoType = V2TIMGroupAtInfo.TIM_AT_ME;
    } else {
        atInfoType = V2TIMGroupAtInfo.TIM_AT_UNKNOWN;
    }
    //更新@类型到当前会话
    switch (atInfoType){
        case V2TIMGroupAtInfo.TIM_AT_ME:
            Log.d(TAG, "更新到当前会话显示[有人@我]");
            break;
        case V2TIMGroupAtInfo.TIM_AT_ALL:
            Log.d(TAG, "更新到当前会话显示[@所有人]");
            break;
        case V2TIMGroupAtInfo.TIM_AT_ALL_AT_ME:
            Log.d(TAG, "更新到当前会话显示[有人@我][@所有人]");
            break;
        default:
            break;
    
    }

收发合并转发消息

要实现类似于微信的合并转发功能,首先需要根据原始消息列表创建一条合并消息,然后把合并消息发送到对端,对端收到合并消息后再解析出原始消息列表,合并消息的展示还需要标题和摘要信息,如下图所示:

注意:

仅增强版 5.2.210 及以上版本支持。

合并转发 合并消息展示 点击合并消息下载合并消息列表展示
  • 发送合并转发消息:
    通常我们在收到一条合并消息的时候,会在聊天界面这样显示:
vinson 和 lynx 的聊天记录 title (标题)
vinson:新版本 SDK 计划什么时候上线呢? abstract1 (摘要信息1)
lynx:计划下周一,具体时间要看下这两天的系统测试情况.. abstract2 (摘要信息2)
vinson:好的 abstract3 (摘要信息3)

聊天界面只会显示合并消息的标题和摘要信息,只有用户点击合并消息的时候才会展示合并消息列表,我们在创建一条合并消息的时候不仅要设置合并消息列表,还要设置标题和摘要信息,实现流程如下:版 5.2.210 及以上版本支持。
1、调用 createMergerMessage 接口创建一条合并消息。
2、调用 sendMessage 接口发送合并消息。

经典示例:收发合并转发消息

  • 发送合并转发消息:
    发送方创建一条合并消息并发送。

    // 需要被转发的消息列表,消息列表里可以包含合并消息,不能包含群 Tips 消息
    List<V2TIMMessage> msgs = new ArrayList<>();
    msgs.add(message1);
    msgs.add(message2);
    // 合并消息标题
    String title = "vinson 和 lynx 的聊天记录"; 
    // 合并消息摘要列表
    List<String> abstactList = new ArrayList<>();
    msgs.add("abstract1");
    msgs.add("abstract2");
    msgs.add("abstract3");
    // 合并消息兼容文本,低版本 SDK 如果不支持合并消息,默认会收到一条文本消息,文本消息的内容为 compatibleText
    String compatibleText = "请升级最新版本查看合并消息"; 
    // 创建合并消息
    V2TIMMessage mergeMessage = V2TIMManager.getMessageManager().createMergerMessage(msgs, title, abstractList, compatibleText);
    // 发送合并消息给用户 "denny"
    V2TIMManager.getMessageManager().sendMessage(mergeMessage, "denny", null, V2TIMMessage.V2TIM_PRIORITY_NORMAL, false, null, new V2TIMSendCallback<V2TIMMessage>() {
                @Override
                public void onProgress(int progress) {}
    
                @Override
                public void onSuccess(V2TIMMessage v2TIMMessage) {}
    
                @Override
                public void onError(int code, String desc) {}
        })

  • 接收合并转发消息:
    接收方收到一条合并消息并解析:

    @Override
    public void onRecvNewMessage(V2TIMMessage msg) {
        if (msg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_MERGER) {
            // 获取合并消息 elem
            V2TIMMergerElem mergerElem = msg.getMergerElem();
            // 获取 title
            String title = mergerElem.getTitle();
            // 获取摘要列表
            List<String> abstractList = mergerElem.getAbstractList();
            // 用户点击合并消息的时候下载合并消息列表
            mergerElem.downloadMergerMessage(new V2TIMValueCallback<List<V2TIMMessage>>() {
                @Override
                public void onSuccess(List<V2TIMMessage> v2TIMMessages) {
                // 下载成功,v2TIMMessages 即为合并消息列表
                for (V2TIMMessage subMsg : v2TIMMessages) {
                    // 如果合并消息列表里面还有合并消息,可以继续解析
                    if (subMsg.getElemType() == V2TIMMessage.V2TIM_ELEM_TYPE_MERGER) {
                            V2TIMMergerElem mergerElem = subMsg.getMergerElem();
                            // 获取 title
                            String title = mergerElem.getTitle();
                            // 获取摘要列表
                            List<String> abstractList = mergerElem.getAbstractList();
                            // 用户点击合并消息的时候下载合并消息列表
                            ......
                        }
                    }
                }
    
                @Override
                public void onError(int code, String desc) {
                    // 下载失败
                }
            });
    }

消息已读回执

用户发送消息时可以设置消息是否需要已读回执,如果需要,接收端查看消息后才可以发送消息已读回执。

说明:

  • 该功能需要购买旗舰版套餐包。
  • 群已读回执仅增强版 6.1.2155 及以上版本支持,单聊已读回执仅增强版 6.2.2363 及以上版本支持。
  • 群已读回执需要在 即时通信 IM 控制台 >功能配置>登录与消息>群已读消息回执配置中设置支持已读回执消息的群类型。

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

发送端创建消息后,先通过消息对象 V2TIMMessageneedReadReceipt 字段设置消息需要已读回执,再发送消息到会话中。

/// 接口调用示例
V2TIMMessage v2TIMMessage = V2TIMMessageManagerImpl.getInstance().createTextMessage("群已读回执消息");
// 设置消息需要已读回执
v2TIMMessage.setNeedReadReceipt(true);
// 发送消息
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, null, "groupA", V2TIMMessage.V2TIM_PRIORITY_NORMAL, false, null, new V2TIMSendCallback<V2TIMMessage>() {
                    @Override
                    public void onProgress(int progress) {}

                    @Override
                    public void onError(int code, String desc) {}

                    @Override
                    public void onSuccess(V2TIMMessage v2TIMMessage) {}
                });

接收端发送消息已读回执

接收端收到消息后,可以通过消息对象 V2TIMMessageneedReadReceipt 字段判断消息是否需要已读回执,如果需要已读回执,当用户查看消息后,调用 sendMessageReadReceipts 接口发送消息已读回执。

/// 接口调用示例
/// 假设 msg 消息用户已经查看
if (!msg.isSelf() && msg.isNeedReadReceipt()) {
  List<V2TIMMessage> messageList = new ArrayList<>();
  messageList.add(msg);
  V2TIMManager.getMessageManager().sendMessageReadReceipts(messageList, new V2TIMCallback() {
    @Override
    public void onSuccess() {
      // 发送消息已读回执成功
    }

    @Override
    public void onError(int code, String desc) {
      // 发送消息已读回执失败
    }
  });
}

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

接收端发送消息已读回执后,发送端可以通过 V2TIMAdvancedMsgListeneronRecvMessageReadReceipts 回调监听消息已读回执通知。

/// 接口调用示例
V2TIMManager.getMessageManager().addAdvancedMsgListener(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();
      // 群组 ID
      String groupID = receipt.getGroupID();
      // 群消息最新已读数
      long readCount = receipt.getReadCount();
      // 群消息最新未读数
      long unreadCount = receipt.getUnreadCount();
    }
  }
});

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

发送端从其他界面进入消息列表后,先拉取历史消息,再调用 getMessageReadReceipts 接口拉取消息已读回执信息。

/// 接口调用示例(以 Group 消息为例)
V2TIMManager.getMessageManager().getGroupHistoryMessageList("groupA", 20, null, new V2TIMValueCallback<List<V2TIMMessage>>() {
  @Override
  public void onSuccess(final List<V2TIMMessage> v2TIMMessages) {
    List<V2TIMMessage> receiptMsgs = new ArrayList<>();
    // 自己发送的消息 && 需要已读回执,需要拉取消息的已读回执信息
    for (V2TIMMessage msg : v2TIMMessages) {
      if (msg.isSelf() && msg.isNeedReadReceipt()) {
        receiptMsgs.add(msg);
      }
    }
    V2TIMManager.getMessageManager().getMessageReadReceipts(receiptMsgs, new V2TIMValueCallback<List<V2TIMMessageReceipt>>() {
      @Override
      public void onSuccess(List<V2TIMMessageReceipt> v2TIMMessageReceipts) {
        Map<String, V2TIMMessageReceipt> messageReceiptMap = new HashMap<>();
        for (V2TIMMessageReceipt receipt : v2TIMMessageReceipts) {
          messageReceiptMap.put(receipt.getMsgID(), receipt);
        }
        for (V2TIMMessage msg : v2TIMMessages) {
          V2TIMMessageReceipt receipt = messageReceiptMap.get(msg.getMsgID());
          if (receipt != null) {
            // C2C 消息对方 ID
            String userID = receipt.getUserID();
            // C2C 消息对方已读状态
            boolean isPeerRead = receipt.isPeerRead();
            // 群组 ID
            String groupID = receipt.getGroupID();
            // 消息已读数,readCount 为 0,表示消息无人已读
            long readCount = receipt.getReadCount();
            // 消息未读数,unreadCount 为 0,表示消息全部已读
            long unreadCount = receipt.getUnreadCount();
          }
        }
      }

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

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

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

发送端在需要查看群消息已读或未读成员列表时,可以调用 getGroupMessageReadMemberList 接口分页拉取消息已读或未读群成员列表。

/// 接口调用示例
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) {
    // 拉群已读成员列表失败
  }
});

发送不计入未读数的消息

正常情况下,无论是发送 C2C 单聊消息还是发送 Group 群消息,都会计入未读消息数(通过会话对象 V2TIMConversationgetUnreadCount 接口,可以拿到一个会话的未读消息数)。当您希望发送一些不计入未读计数的消息时,比如提示类或者控制类的消息,可以按照下面的方式来发送:

仅增强版 5.3.425 及以上版本支持。

// 创建消息对象
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(content);
// 设置不计入未读消息总数的标记
v2TIMMessage.setExcludedFromUnreadCount(true);
// 发送消息
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, "userA", null, V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, null, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onError(int code, String desc) {
// 消息发送失败
}
@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {
// 消息发送成功
}
@Override
public void onProgress(int progress) {
}
});

发送不更新会话的消息

某些场景下,不希望一些提示类型的消息显示为会话的最新消息,可以按照下面的方式来发送:

仅增强版 5.4.666 及以上版本支持。

// 创建消息对象
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(content);
// 设置不计入会话 lastMsg 的标记
v2TIMMessage.setExcludedFromLastMessage(true);
// 发送消息
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, "userA", null, V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, null, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onError(int code, String desc) {
// 消息发送失败
}
@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {
// 消息发送成功
}
@Override
public void onProgress(int progress) {
}
});

发送群内定向消息

定向消息是指向群内部分成员发送消息,而其他群成员无法收到该消息,可以按照下面的方式实现:

// 创建原始消息对象
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage("这是一个群定向消息");
// 创建定向群消息对象,指定群消息接收者 "vinson" "denny"
List<String> targetGroupMemberList = new ArrayList<>();
targetGroupMemberList.add("vinson");
targetGroupMemberList.add("denny");
V2TIMMessage targetGroupMessage = V2TIMManager.getMessageManager().createTargetedGroupMessage(v2TIMMessage, targetGroupMemberList);

// 在群 "groupA" 发送群定向消息
V2TIMManager.getMessageManager().sendMessage(targetGroupMessage, null, "groupA",  V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, null, new V2TIMSendCallback<V2TIMMessage>() {
    @Override
    public void onError(int code, String desc) {
        // 发送失败
    }
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {
        // 发送成功
    }
    @Override
    public void onProgress(int progress) {

    }
});

说明:

  • 仅增强版 6.0.1975 及以上版本支持。
  • 该功能需要购买旗舰版套餐包。
  • 创建定向群消息的原始消息对象不支持群 @ 消息。
  • 社群(Community)和直播群(AVChatRoom)不支持发送定向群消息。
  • 定向群消息默认不计入群会话的未读计数。

设置离线推送

当接收方的 App 被 kill 时,IM SDK 无法通过正常的网络连接收取新消息。如需实现在此场景下接收方仍能感知到新消息,需要使用各个手机厂商提供的离线推送服务,更多详细请参见 Android 离线推送

设置离线推送的标题和内容

您可以在发送消息时,通过 sendMessage 接口中的 offlinePushInfo 字段,设置离线推送的标题和内容。

// 创建一条文本消息发送给 groupA,并且自定义推送 Title、推送内容
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createTextMessage(content);
V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
// 设置通知栏标题
v2TIMOfflinePushInfo.setTitle("offline_title");
// 设置通知栏内容
v2TIMOfflinePushInfo.setDesc("offline_desc");
// 发送消息
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, null, "groupA", V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
    @Override
    public void onError(int code, String desc) {
        // 消息发送失败
    }
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {
        // 消息发送成功
    }
    @Override
    public void onProgress(int progress) {
    }
});

点击推送消息跳转到对应的聊天窗口

如需实现该功能,发送消息时需设置离线推送对象 offlinePushInfo 的扩展字段 ext,收到消息的用户打开 App 时可以通过不同厂商提供的获取自定义内容的方式拿到这个扩展字段 ext,然后根据 ext 内容跳转到对应的聊天界面。

本文以 “denny 给 vinson 发送消息” 的场景为例。
发送方:denny 要在发送消息的时候设置推送扩展字段 ext:

// denny 在发送消息时设置 offlinePushInfo,并指定 ext 字段
JSONObject jsonObject = new JSONObject();
try {
    jsonObject.put("action", "jump to denny");
} catch (JSONException e) {
    e.printStackTrace();
}
String extContent = jsonObject.toString();
V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
v2TIMOfflinePushInfo.setExt(extContent.getBytes());

V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, "vinson", null, V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false,  v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
    @Override
    public void onError(int code, String desc) {}
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {}
    @Override
    public void onProgress(int progress) {}
});
接收方:vinson 的 App 虽然不在线,但可以接收到手机厂商(我们以 OPPO 手机为例)的离线推送,当 vinson 点击推送消息时会启动 App:

// vinson 启动 App 后在打开的 Activity 中获取自定义内容
Bundle bundle = intent.getExtras();
Set<String> set = bundle.keySet();
if (set != null) {
    for (String key : set) {
        // 其中 key 和 value 分别为发送端设置的 extKey 和 ext content
        String value = bundle.getString(key);
        if (value.equals("jump to denny")) {
            // 跳转到和 denny 的聊天界面
            ...
        }
    }
}

设置消息为只能在线接收

某些场景下,您可能希望发出去的消息只被在线用户接收,即当接收者不在线时就不会感知到该消息。您只需在 sendMessage 时,将参数 onlineUserOnly 设置为 true,此时发送出去的消息跟普通消息相比,会有如下差异点:

  • 不支持离线存储,即如果接收方不在线就无法收到。
  • 不支持多端漫游,即如果接收方在一台终端设备上一旦接收过该消息,无论是否已读,都不会在另一台终端上再次收到。
  • 不支持本地存储,即本地的云端的历史消息中均无法找回。

经典示例:实现“对方正在输入”功能
在 C2C 单聊场景下,您可以通过 sendMessage 接口发送 "自己正在输入" 的提示性消息,接收方收到该消息时可以在 UI 界面展示 "对方正在输入",示例代码如下:

// 给 userA 发送 "正在输入" 的提示消息
JSONObject jsonObject = new JSONObject();
try {
    jsonObject.put("command", "textInput");
} catch (JSONException e) {
    e.printStackTrace();
}
V2TIMMessage v2TIMMessage = V2TIMManager.getMessageManager().createCustomMessage(jsonObject.toString().getBytes());
V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, "userA", null, V2TIMMessage.V2TIM_PRIORITY_DEFAULT, true,  v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
    @Override
    public void onError(int code, String desc) {}
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {}
    @Override
    public void onProgress(int progress) {}
});

设置接收消息免打扰

SDK 支持三种类型的消息接收选项:

  • V2TIM_RECEIVE_MESSAGE:在线时正常接收消息,离线时接收离线推送通知
  • V2TIM_NOT_RECEIVE_MESSAGE:在线和离线都不接收消息
  • V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE:在线时正常接收消息,离线时不接收离线推送通知

您可以调用 setC2CReceiveMessageOpt 接口设置单聊消息免打扰,调用 setGroupReceiveMessageOpt 接口设置群聊消息免打扰。

仅增强版 5.3.425及以上版本支持。

撤回消息

发送方通过 revokeMessage 接口可以撤回一条已经发送成功的消息。默认情况下,发送者只能撤回2分钟以内的消息,您可以按需更改消息撤回时间限制,具体操作请参见 消息撤回设置
消息的撤回同时需要接收方 UI 代码的配合:当发送方撤回一条消息后,接收方会收到消息撤回通知 onRecvMessageRevoked,通知中包含了撤回消息的 msgID,您可以根据这个 msgID 判断 UI 层是哪一条消息撤回了,然后把对应的消息气泡切换成 "消息已被撤回" 状态。

发送方撤回一条消息

V2TIMManager.getMessageManager().revokeMessage(v2TIMMessage, new V2TIMCallback() {
@Override
public void onError(int code, String desc) {
    // 撤回消息失败
}
@Override
public void onSuccess() {
    // 撤回消息成功
}
});

接收方感知消息被撤回

  1. 调用 addAdvancedMsgListener 设置高级消息监听。
  2. 通过 onRecvMessageRevoked 接收消息撤回通知。

@Override
public void onRecvMessageRevoked(String msgID) {
    // msgList 为当前聊天界面的消息列表
    for (V2TIMMessage msg : msgList) {
        if (msg.getMsgID().equals(msgID)) {
                // msg 即为被撤回的消息,您需要修改 UI 上相应的消息气泡的状态
        }
    }
}

消息变更

会话里的消息如果是发送成功状态,会话的参与者都可以针对消息做二次修改。消息修改成功后会同步给会话的所有参与者。

注意:

仅增强版 6.2.2363 及以上版本支持。

变更消息

会话参与者可以调用 modifyMessage 接口对会话里的消息做二次修改。

1、localCustomData
2、localCustomInt
3、cloudCustomData
4、V2TIMTextElem
5、V2TIMCustomElem
6、V2TIMLocationElem
7、V2TIMFaceElem

修改消息时,IM SDK 仅限制了会话参与者才能修改,如果您需要更多限制,比如限制只有消息发送者才能修改,可以自行在业务层处理。

// 会话里面原始消息对象为 originMessage
// 修改消息对的 cloudCustomData 信息
originMessage.setCloudCustomData("modify_cloud_custom_data".getBytes());
// 如果是文本消息,修改文本消息内容
if (V2TIMMessage.V2TIM_ELEM_TYPE_TEXT == originMessage.getElemType()) {
  originMessage.getTextElem().setText("modify_text");
}
V2TIMManager.getMessageManager().modifyMessage(originMessage, new V2TIMCompleteCallback<V2TIMMessage>() {
  @Override
  public void onComplete(int code, String desc, V2TIMMessage message) {
    // 修改消息完成,message 为修改之后的消息对象
  }
});

监听消息变更回调

会话的参与者调用 addAdvancedMsgListener 添加高级消息监听器。
当会话里的消息被修改后,会话的参与者都会收到 onRecvMessageModified 回调,回调里面会携带修改之后的消息对象。

V2TIMAdvancedMsgListener advancedMsgListener = new V2TIMAdvancedMsgListener() {
  // 消息内容被修改通知
  @Override
  public void onRecvMessageModified(V2TIMMessage msg) {
      // msg 为被修改之后的消息对象
  }
};
// 添加消息监听
V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener);

清空未读消息数

清空单个会话的未读数

您可以调用 markC2CMessageAsReadmarkGroupMessageAsRead 分别清空某个 C2C 单聊会话或者群聊会话的未读数,接口调用成功后,SDK 会回调 onConversationChanged 方法通知界面更新。

// 清空 userA 单聊会话的未读数
V2TIMManager.getMessageManager().markC2CMessageAsRead("userA", new V2TIMCallback() {
  @Override
  public void onSuccess() {
      // 清空未读数成功
  }

  @Override
  public void onError(int code, String desc) {
      // 清空未读数失败
  }
});
// 清空 groupA 群会话的未读数
V2TIMManager.getMessageManager().markGroupMessageAsRead("groupA", new V2TIMCallback() {
  @Override
  public void onSuccess() {
      // 清空未读数成功
  }

  @Override
  public void onError(int code, String desc) {
      // 清空未读数失败
  }
});

当您调用 markC2CMessageAsRead 接口清空某个 C2C 单聊会话未读数时,对端用户会收到 onRecvC2CReadReceipt 回调,回调里面会携带清空会话未读数的时间戳。

V2TIMManager.getMessageManager().addAdvancedMsgListener(new V2TIMAdvancedMsgListener() {
  @Override
  public void onRecvC2CReadReceipt(List<V2TIMMessageReceipt> receiptList) {
    for (V2TIMMessageReceipt receipt : receiptList) {
      // 清空会话未读数的用户 userID
      String userID = receipt.getUserID();
      // 清空会话未读数的时间戳
      long timestamp = receipt.getTimestamp();
    }
  }
});

一键清空所有会话的未读数

您可以调用 markAllMessageAsRead 实现一键清空所有会话的未读数,接口调用成功后,SDK 会回调 onConversationChanged 方法通知界面更新。

V2TIMManager.getMessageManager().markAllMessageAsRead(new V2TIMCallback() {
  @Override
  public void onSuccess() {
      // 清空未读数成功
  }

  @Override
  public void onError(int code, String desc) {
      // 清空未读数失败
  }
});

仅增强版 5.8.1668 及以上版本支持。

查看历史消息

您可以调用 getC2CHistoryMessageList 获取单聊历史消息,调用 getGroupHistoryMessageList 获取群聊历史消息。如果当前设备网络连接正常,SDK 会默认从服务器拉取历史消息;如果没有网络连接,SDK 会直接从本地数据库中读取历史消息。

分页拉取历史消息

SDK 支持分页拉取历史消息,一次分页拉取的消息数量不宜太大,否则会影响拉取速度,建议一次最多拉取20条。
本文以分页拉取名为 groupA 的群的历史消息,每次分页拉取20条为例,示例代码如下:

// 第一次拉取 lastMsg 传 null,表示从最新的消息开始拉取 20 条消息
V2TIMManager.getMessageManager().getGroupHistoryMessageList("groupA", 20, null, new V2TIMValueCallback<List<V2TIMMessage>>() {
    @Override
    public void onError(int code, String desc) {
        // 拉取失败
    }
    @Override
    public void onSuccess(List<V2TIMMessage> v2TIMMessages) {
        // 分页拉取返回的消息默认是按照从新到旧排列
        if (v2TIMMessages.size() > 0) {
            // 获取下一次分页拉取的起始消息
            V2TIMMessage lastMsg = v2TIMMessages.get(v2TIMMessages.size() - 1);
            // 拉取剩下的20条消息
            V2TIMManager.getMessageManager().getGroupHistoryMessageList("groupA", 20, lastMsg, new V2TIMValueCallback<List<V2TIMMessage>>() {
                @Override
                public void onError(int code, String desc) {
                     // 拉取消息失败
                }

                @Override
                public void onSuccess(List<V2TIMMessage> v2TIMMessages) {
                    // 拉取消息结束
                }
            });
        }
    }
});

现实场景中的分页拉取,通常由用户的滑动操作触发的,用户每下拉一次消息列表就触发一次分页拉取。但原理上跟上述示例代码类似,都是以 lastMsg 作为分页的标记,以 count 控制每次拉取的消息条数。

历史消息的注意事项

  • 历史消息存储时长如下:
    • 体验版:免费存储7天,不支持延长
    • 专业版:免费存储7天,支持延长
    • 旗舰版:免费存储30天,支持延长
    延长历史消息存储时长是增值服务,您可以登录 即时通信 IM 控制台 修改相关配置,具体计费说明请参见 增值服务资费
  • 只有会议群(Meeting)(对应老版本的 ChatRoom 群)才支持拉取到用户入群之前的历史消息。
  • 直播群(AVChatRoom)中的消息均不支持本地存储和多终端漫游,因此对直播群调用 getGroupHistoryMessageList 接口是无效的。

删除消息

对于历史消息,您可以调用 deleteMessages 接口删除历史消息,消息删除后,无法再恢复。

设置消息权限

只允许好友间收发消息

SDK 默认不限制非好友之间收发消息。如果您希望仅允许好友之间发送单聊消息,您可以在 即时通信 IM 控制台 >功能配置>登录与消息>好友关系检查中开启"发送单聊消息检查关系链"。开启后,用户只能给好友发送消息,当用户给非好友发消息时,SDK 会报20009错误码。

不接收某人的消息

不接收某人消息可以选择拉黑某人或则设置某人消息免打扰,拉黑某人后再也收不到对方的任何消息,设置消息免打扰后可以更改消息 免打扰状态
拉黑某人:
调用 addToBlackList 接口把该用户加入黑名单,即拉黑该用户。
当消息发送者被拉黑后,发送者默认不会感知到“被拉黑”的状态,即发送消息后仍展示发送成功(实际上此时接收方不会收到消息)。如果需要被拉黑的发送者收到消息发送失败的提示,请在 即时通信 IM 控制台 >功能配置>登录与消息>黑名单检查中关闭"发送消息后展示发送成功",关闭后,被拉黑的发送者在发送消息时,SDK 会报20007错误码。
设置某人消息免打扰:
调用 setC2CReceiveMessageOpt 接口,设置消息接收选项为 V2TIM_NOT_RECEIVE_MESSAGE 状态。

仅增强版 5.3.425 及以上版本支持。

不接收某个群组的消息

增强版 5.3.425 以上版本,请调用 setGroupReceiveMessageOpt 接口,设置消息接收选项为 V2TIM_NOT_RECEIVE_MESSAGE 状态。
其他 SDK 版本,请调用 setReceiveMessageOpt 接口,设置群消息接收选项为 V2TIM_GROUP_NOT_RECEIVE_MESSAGE 状态。

常见问题

1. 为什么会收到重复的消息?

请检查以下逻辑是否正确:

2. App 卸载重装后已读回执为什么失效了?

在单聊场景下,接收方如果调用 markC2CMessageAsRead 设置消息已读,发送方收到的已读回执里面包含了对方已读的时间戳 timestamp,SDK 内部会根据 timestamp 判断消息对方是否已读, timestamp 目前只在本地保存,程序卸载重装后会丢失。

3. 如何发送多个 Elem 的消息?

如果您的消息需要多个 elem,可以在创建 Message 对象后,通过 Message 对象的 Elem 成员调用 appendElem 方法添加下一个 elem 成员。
以文本消息 + 自定义消息为例:

V2TIMMessage message = V2TIMManager.getMessageManager().createTextMessage("test");
V2TIMCustomElem customElem = new V2TIMCustomElem();
customElem.setData("自定义消息".getBytes());
message.getTextElem().appendElem(customElem);

4. 如何解析多个 Elem 的消息?

  1. 通过 Message 对象正常解析出第一个 Elem 对象。
  2. 通过第一个 Elem 对象的 getNextElem 方法获取下一个 Elem 对象。如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 null

@Override
public void onRecvNewMessage(V2TIMMessage msg) {
    // 查看第一个 Elem
    int elemType = msg.getElemType();
    if (elemType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) {
        // 文本消息
        V2TIMTextElem v2TIMTextElem = msg.getTextElem();
        String text = v2TIMTextElem.getText();
        // 查看 v2TIMTextElem 后面还有没有更多 elem
        V2TIMElem elem = v2TIMTextElem.getNextElem();
        while (elem != null) {
            // 判断 elem 类型,以 V2TIMCustomElem 为例
            if (elem instanceof V2TIMCustomElem) {
                V2TIMCustomElem customElem = (V2TIMCustomElem) elem;
                byte[] data = customElem.getData();
            }
            // 继续查看当前 elem 后面还有没更多 elem
            elem = elem.getNextElem();
        }
        // elem 如果为 null,表示所有 elem 都已经解析完
    }
}

5. 各种不同类型的消息应该如何解析?

解析消息相对复杂,我们提供了各种类型消息解析的 示例代码,您可以直接把相关代码拷贝到您的工程,然后根据实际需求进行二次开发。