消息收发

功能概述

SDK 提供一套完善的消息传输管理服务,包括收发消息,存储消息,上传下载附件等。支持发送文本,语音,图片,视频,文件,地理位置等类型消息,同时支持用户发送自定义类型的消息。

SDK 中用于表示消息的结构为 IMMessage,不同消息类型以 MsgTypeEnum 作区分。IMMessage 不支持继承扩展。

返回值 IMMessage接口方法 说明
String getUuid() 获取消息的客户端id
long getServerId() 获取消息的服务端id
String getSessionId() 获取聊天对象的id/群ID等
String getFromAccount() 获取该条消息发送方的帐号
SessionTypeEnum getSessionType() 获取会话类型,如群聊类型
int getFromClientType() 获取消息发送方类型
String getFromNick() 获取消息发送者的昵称
MsgTypeEnum getMsgType() 获取消息类型
int getSubtype() 获取消息的子类型
void setSubtype 设置消息的子类型
MsgStatusEnum getStatus() 获取消息接收/发送状态
MsgDirectionEnum getDirect() 获取消息方向
boolean isInBlackList() 发送消息给对方, 是不是被对方拉黑了
String getContent() 获取消息具体内容,除了文本消息和提示消息外,其他消息为空
long getTime() 获取消息时间,单位为 ms
void setAttachment 设置消息附件对象。详细见 MsgAttachment 参数说明
MsgAttachment getAttachment() 获取消息附件对象。仅当 getMsgType() 返回为非 text 时有效
AttachStatusEnum getAttachStatus() 获取消息附件接收/发送状态
CustomMessageConfig getConfig() 获取消息配置。具体见CustomMessageConfig参数说明
Map<String, Object> getRemoteExtension() 获取扩展字段
void setRemoteExtension
(Map
remoteExtension)
设置扩展字段
(该字段会发送到其他端),
最大长度 1024 。
开发者需要保证此 Map 能够转换为 JsonObject
Map<String, Object> getLocalExtension() 获取本地扩展字段(仅客户端本地有效)
void setLocalExtension
(Map
localExtension)
设置本地扩展字段
(该字段仅在本地使用有效,不会发送给其他端),
最大长度 1024 。
开发者需要保证此 Map 能够转换为 JsonObject
String getCallbackExtension 获取第三方回调返回的自定义扩展字段
String getPushContent() 获取推送文案
void setPushContent(String pushContent) 设置推送文案
Map<String, Object> getPushPayload() 获取推送属性
void setPushPayload
(Map
pushPayload)
设置推送属性,
开发者需要保证此 Map 能够转换为 JsonObject,
最大长度 2048
MemberPushOption getMemberPushOption() 获取群成员强制推送属性
boolean isRemoteRead() 单聊场景下,判断自己发送的消息对方是否已读
NIMAntiSpamOption getNIMAntiSpamOption() 获取反垃圾配置项
String getYidunAntiCheating() 获取易盾反垃圾字段
void setYidunAntiCheating 设置易盾反垃圾字段,要求是Json格式
boolean isSessionUpdate() 消息是否需要刷新到session服务
void setSessionUpdate 设置消息是否需要刷新到session服务

目前提供如下几种消息类型:

MsgTypeEnum参数 说明
text 文本消息
image 图片消息
audio 音频消息
video 视频消息
location 位置消息
file 文件消息
avchat 音视频通话事件消息
notification 通知消息
tip 提醒消息
custom 自定义消息

消息发送

先通过 MessageBuilder 提供的接口创建对应的消息对象,然后调用 MsgServicesendMessage 接口发出。

/**
 * 发送消息。
 * @param msg    待发送的消息体,由{@link MessageBuilder}构造
 * @param resend 如果是发送失败后重发,标记为true,否则填false
 * @return InvocationFuture 可以设置回调函数。消息发送完成后才会调用,如果出错,会有具体的错误代码。
 */
public InvocationFuture<Void> sendMessage(IMMessage msg, boolean resend);

文本消息发送

文本消息创建原型

/**
 * 创建一条普通文本消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param text        文本消息内容
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createTextMessage(String sessionId, SessionTypeEnum sessionType, String text);
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
text 文本消息内容
// 该帐号为示例
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
String text = "this is an example";
// 创建一个文本消息
IMMessage textMessage = MessageBuilder.createTextMessage(account, sessionType, text);
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(textMessage, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

图片消息发送

图片消息创建原型

/**
 * 创建一条图片消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param file        图片文件
 * @param displayName 图片文件的显示名,可不同于文件名
 * @param nosTokenSceneKey 文件资源场景
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createImageMessage(String sessionId, SessionTypeEnum sessionType, File file, String displayName);

// 或者:创建一条图片消息并指定图片上传时使用的文件资源场景
public static IMMessage createImageMessage(String sessionId, SessionTypeEnum sessionType, File file, String displayName, String nosTokenSceneKey); 
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
file 图片文件对象
displayName 图片文件的显示名,可不同于文件名,可设置为 null
nosTokenSceneKey 图片上传时使用的 nos scene ,默认为NimNosSceneKeyConstant.NIM_DEFAULT_IM,详见文件资源场景章节
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// 示例图片,需要开发者在相应目录下有图片
File file = new File("/sdcard/test.jpg");
// 创建一个图片消息
IMMessage message = MessageBuilder.createImageMessage(account, sessionType, file, file.getName());
// 或者:创建一个图片消息并指定图片上传时使用的文件资源场景,"nos_scene_key"请替换成开发者已经配置的
IMMessage message = MessageBuilder.createImageMessage(account, sessionType, file, file.getName(),"nos_scene_key");
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(message, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

语音消息发送

音频消息创建原型:

/**
 * 创建一条音频消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param file        音频文件对象
 * @param duration    音频文件持续时间,单位是ms
 * @param nosTokenSceneKey    文件资源场景
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createAudioMessage(String sessionId, SessionTypeEnum sessionType, File file, long duration);

//或者:创建一条音频消息并指定音频上传时使用的文件资源场景
public static IMMessage createAudioMessage(String sessionId, SessionTypeEnum sessionType, File file, long duration, String nosTokenSceneKey);
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
file 音频文件对象
duration 音频文件持续时间,单位是 ms
nosTokenSceneKey 音频文件上传时使用的 nos scene ,默认为NimNosSceneKeyConstant.NIM_DEFAULT_IM ,详见文件资源场景章节
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// 示例音频,需要开发者在相应目录下有文件
File audioFile = new File("/sdcard/testAudio.mp3");
// 音频时长,时间为示例
long audiolength = 2000;
// 创建音频消息
IMMessage audioMessage = MessageBuilder.createAudioMessage(account, sessionType, audioFile, audioLength);
// 或者:创建一个音频消息并指定音频上传时使用的文件资源场景,"nos_scene_key"请替换成开发者已经配置的
IMMessage audioMessage = MessageBuilder.createAudioMessage(account, sessionType, audioFile, "nos_scene_key");
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(audioMessage, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

视频消息发送

视频消息创建原型:

/**
 * 创建一条视频消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param file        视频文件对象
 * @param duration    视频文件持续时间
 * @param width       视频宽度
 * @param height      视频高度
 * @param displayName 视频文件显示名,可以为空
 * @param nosTokenSceneKey    文件资源场景
 * @return 视频消息
 */
public static IMMessage createVideoMessage(String sessionId, SessionTypeEnum sessionType, File file, long duration, int width, int height, String displayName);

//或者:创建一条视频消息并指定视频上传时使用的文件资源场景
public static IMMessage createVideoMessage(String sessionId, SessionTypeEnum sessionType, File file, long duration, int width, int height, String displayName, String nosTokenSceneKey)
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
file 视频文件对象
duration 视频文件持续时间,单位 ms
width 视频宽度
height 视频高度
displayName 视频文件显示名,可为 null
nosTokenSceneKey 视频文件上传时使用的 nos scene ,默认为NimNosSceneKeyConstant.NIM_DEFAULT_IM ,详见文件资源场景章节
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// 示例视频,需要开发者在相应目录下有文件
File file = new File("/sdcard/testVideo.mp4");
// 获取视频mediaPlayer
MediaPlayer mediaPlayer;
try {
    mediaPlayer = MediaPlayer.create(context, Uri.fromFile(file));
} catch (Exception e) {
    e.printStackTrace();
}
// 视频文件持续时间
long duration = mediaPlayer == null ? 0 : mediaPlayer.getDuration();
// 视频高度
int height = mediaPlayer == null ? 0 : mediaPlayer.getVideoHeight();
// 视频宽度
int width = mediaPlayer == null ? 0 : mediaPlayer.getVideoWidth();
// 创建视频消息
IMMessage message = MessageBuilder.createVideoMessage(account, sessionType, file, duration, width, height, null);
// 或者:创建一个视频消息并指定视频上传时使用的文件资源场景,"nos_scene_key"请替换成开发者已经配置的
IMMessage message = MessageBuilder.createVideoMessage(account, sessionType, file, duration, width, height, null,"nos_scene_key");
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(message, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

文件消息发送

文件消息创建原型:

/**
 * 创建一条文件消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param file        文件
 * @param displayName 文件的显示名,可不同于文件名
 * @param nosTokenSceneKey    文件资源场景
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createFileMessage(String sessionId, SessionTypeEnum sessionType, File file, String displayName);

//或者:创建一条文件消息并指定文件上传时使用的文件资源场景
public static IMMessage  createFileMessage(String sessionId, SessionTypeEnum sessionType, File file, String displayName, String nosTokenSceneKey)
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
file 文件
displayName 文件的显示名,可不同于文件名
nosTokenSceneKey 文件上传时使用的 nos scene ,默认为NimNosSceneKeyConstant.NIM_DEFAULT_IM ,详见文件资源场景章节
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// 示例文件,需要开发者在相应目录下有文件
File file = new File("/sdcard/test.txt");
// 创建文件消息
IMMessage message = MessageBuilder.createFileMessage(account, sessionType, file, file.getName());
// 或者:创建一个文件消息并指定文件上传时使用的文件资源场景,"nos_scene_key"请替换成开发者已经配置的
IMMessage message = MessageBuilder.createFileMessage(account, sessionType, file, file.getName(),"nos_scene_key");
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(message, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

位置消息发送

地理位置消息创建原型:

/**
 * 创建一条地理位置信息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param lat         纬度
 * @param lng         经度
 * @param addr        地理位置描述信息
 * @param nosTokenSceneKey    文件资源场景
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createLocationMessage(String sessionId, SessionTypeEnum sessionType, double lat, double lng, String addr);
参数 说明
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
lat 纬度
lng 经度
addr 地理位置描述信息
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// 纬度
double lat = 30.3;
// 经度
double lng = 120.2;
// 地理位置描述信息
String addr = "杭州";
// 创建地理位置信息
IMMessage message = MessageBuilder.createLocationMessage(account, sessionType, lat, lng, addr);

// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(message, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

提示消息发送

提示消息(又叫做 Tip 消息)主要用于会话内的通知提醒,可以看做是自定义消息的简化,有独立的消息类型 MsgTypeEnum.tip 。 区别于自定义消息,Tip 消息暂不支持 setAttachment,如果要使用 Attachment 请使用自定义消息。 Tip 消息使用场景例如:进入会话时出现的欢迎消息,或是会话过程中命中敏感词后的提示消息等场景,当然也可以用自定义消息实现,只是相对复杂一些。

提示消息创建原型:

/**
 * 创建一条提示消息
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @return IMMessage 生成的消息对象
 */
public static IMMessage createTipMessage(String sessionId, SessionTypeEnum sessionType);
// 向群里插入一条Tip消息,使得该群能立即出现在最近联系人列表(会话列表)中,满足部分开发者需求。
Map<String, Object> content = new HashMap<>(1);
content.put("content", "成功创建高级群");
// 创建tip消息,teamId需要开发者已经存在的team的teamId
IMMessage msg = MessageBuilder.createTipMessage(teamId, SessionTypeEnum.Team);
msg.setRemoteExtension(content);
// 自定义消息配置选项
CustomMessageConfig config = new CustomMessageConfig();
// 消息不计入未读
config.enableUnreadCount = false;
msg.setConfig(config);
// 消息发送状态设置为success
msg.setStatus(MsgStatusEnum.success);
// 保存消息到本地数据库,但不发送到服务器
NIMClient.getService(MsgService.class).saveMessageToLocal(msg, true).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

自定义消息发送

除了内建消息类型以外,SDK 还支持收发自定义消息类型。SDK 不负责定义和解析自定义消息的具体内容,解释工作由开发者完成。SDK 会将自定义消息存入消息数据库,会和内建消息一并展现在消息记录中。

为了使用更加方便,自定义消息采用附件的方式展示给开发者。体现在 IMMessage 类中,自定义消息的内容会被解析为 MsgAttachment 对象,由于 SDK 并不知道自定义消息的格式,第三方 App 需要注册一个自定义消息解析器。当第三方 App 调用查询查询消息历史的接口,或者是 SDK 收到消息通知第三方 App 时,就能将自定义消息内容转换为一个 MsgAttachment 对象,然后就可以同操作图片消息等带附件消息一样,操作自定义消息了。

创建自定义消息原型

/**
 * 创建一条APP自定义类型消息, 同时提供描述字段,可用于推送以及状态栏消息提醒的展示。
 *
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @param content     消息简要描述,可通过IMMessage#getContent()获取,主要用于用户推送展示。
 * @param attachment  消息附件对象
 * @param config      自定义消息配置
 * @param nosTokenSceneKey    文件资源场景
 * @return 自定义消息
 */
public static IMMessage createCustomMessage(String sessionId, SessionTypeEnum sessionType, String content, MsgAttachment attachment, CustomMessageConfig config);

//或者:创建一条APP自定义类型消息并指定文件(图片、音视、视频...)上传(如果有)时使用的 nos scene
public static IMMessage createCustomMessage(String sessionId, SessionTypeEnum sessionType, String content, MsgAttachment attachment, CustomMessageConfig config, String nosTokenSceneKey)

示例:剪刀石头布

在 demo 中,我们提供了一个用自定义消息实现的“剪刀石头布”的游戏,下面以此为例,详细解析其实现步骤。

首先,我们先定义一个自定义消息附件的基类,负责解析你的自定义消息的公用字段,比如类型等。还可以定义一些公共接口,用于一些便利性的调用。

注意: 实现 MsgAttachment 接口的成员都要实现 Serializable。

// 先定义一个自定义消息附件的基类,负责解析你的自定义消息的公用字段,比如类型等等。
public abstract class CustomAttachment implements MsgAttachment {

    // 自定义消息附件的类型,根据该字段区分不同的自定义消息
    protected int type;

    CustomAttachment(int type) {
        this.type = type;
    }

    // 解析附件内容。
    public void fromJson(JSONObject data) {
        if (data != null) {
            parseData(data);
        }
    }

    // 实现 MsgAttachment 的接口,封装公用字段,然后调用子类的封装函数。
    @Override
    public String toJson(boolean send) {
        return CustomAttachParser.packData(type, packData());
    }

    // 子类的解析和封装接口。
    protected abstract void parseData(JSONObject data);
    protected abstract JSONObject packData();
}

然后,继承这个基类,实现“剪刀石头布”的附件类型。注意,成员变量都要实现 Serializable。

public class GuessAttachment extends CustomAttachment {

    // 猜拳类型枚举
    public enum Guess {
        Shitou(1),
        Jiandao(2),
        Bu(3),
        ;
    }

    private Guess value;

    public GuessAttachment() {
        super(CustomAttachmentType.Guess);
        random();
    }

    // 解析猜拳类型具体数据
    @Override
    protected void parseData(JSONObject data) {
        value = Guess.enumOfValue(data.getIntValue("value"));
    }

    // 数据打包
    @Override
    protected JSONObject packData() {
        JSONObject data = new JSONObject();
        data.put("value", value.getValue());
        return data;
    }

    private void random() {
        int value = new Random().nextInt(3) + 1;
        this.value = Guess.enumOfValue(value);
    }
}

第三步,实现自定义消息的附件解析器。

public class CustomAttachParser implements MsgAttachmentParser {

    // 根据解析到的消息类型,确定附件对象类型
    @Override
    public MsgAttachment parse(String json) {
        CustomAttachment attachment = null;
        try {
            JSONObject object = JSON.parseObject(json);
            int type = object.getInteger("type");
            JSONObject data = object.getJSONObject(KEY_DATA);
            switch (type) {
                case CustomAttachmentType.Guess:
                    attachment = new GuessAttachment();
                    break;
                default:
                    attachment = new DefaultCustomAttachment();
                    break;
            }

            if (attachment != null) {
                attachment.fromJson(data);
            }
        } catch (Exception e) {

        }

        return attachment;
    }

    public static String packData(int type, JSONObject data) {
        JSONObject object = new JSONObject();
        object.put(KEY_TYPE, type);
        if (data != null) {
            object.put(KEY_DATA, data);
        }

        return object.toJSONString();
    }
}

最后,将该附件解析器注册到 SDK 中。为了保证生成历史消息时能够正确解析自定义附件,注册一般应放在 Application 的 onCreate 中 的主进程判断语句内完成。

if (NIMUtil.isMainProcess(this)) {
    // 监听的注册,必须在主进程中。
    NIMClient.getService(MsgService.class).registerCustomAttachmentParser(new CustomAttachParser()); 
}

示例:阅后即焚

若需要发送文件类型消息,例如图片等,可以参考阅后即焚的实现。具体实现步骤如下:

第一步,定义一个自定义的附件类型,并继承 FileAttachment。注意,成员变量都要实现 Serializable。

public class SnapChatAttachment extends FileAttachment {

    private static final String KEY_PATH = "path";
    private static final String KEY_SIZE = "size";
    private static final String KEY_MD5 = "md5";
    private static final String KEY_URL = "url";

    public SnapChatAttachment() {
        super();
    }

    public SnapChatAttachment(JSONObject data) {
        load(data);
    }

    @Override
    public String toJson(boolean send) {
        JSONObject data = new JSONObject();
        try {
            // 重发使用本地路径
            if (!send && !TextUtils.isEmpty(path)) {
                data.put(KEY_PATH, path);
            }

            if (!TextUtils.isEmpty(md5)) {
                data.put(KEY_MD5, md5);
            }
            // 注意:这段代码一定要写。
            // SDK在调toJson的时候 父类FileAttachemnt的url才有值。
            // 这个值是sdk自动赋值的。
            data.put(KEY_URL, url);
            data.put(KEY_SIZE, size);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return CustomAttachParser.packData(CustomAttachmentType.SnapChat, data);
    }

    private void load(JSONObject data) {
        path = data.getString(KEY_PATH);
        md5 = data.getString(KEY_MD5);
        url = data.getString(KEY_URL);
        size = data.containsKey(KEY_SIZE) ? data.getLong(KEY_SIZE) : 0;
    }
}

第二步,实现自定义消息的附件解析器。

public class CustomAttachParser implements MsgAttachmentParser {

    // 根据解析到的消息类型,确定附件对象类型
    @Override
    public MsgAttachment parse(String json) {
        CustomAttachment attachment = null;
        try {
            JSONObject object = JSON.parseObject(json);
            int type = object.getInteger("type");
            JSONObject data = object.getJSONObject(KEY_DATA);
            switch (type) {
                case CustomAttachmentType.SnapChat:
                    return new SnapChatAttachment(data);
                default:
                    attachment = new DefaultCustomAttachment();
                    break;
            }

            if (attachment != null) {
                attachment.fromJson(data);
            }
        } catch (Exception e) {

        }

        return attachment;
    }
    ...
}

最后,将该附件解析器注册到 SDK 中。为了保证生成历史消息时能够正确解析自定义附件,注册一般应放在 Application 的 onCreate 中完成。

if (NIMUtil.isMainProcess(this)) {
    // 监听的注册,必须在主进程中。
    NIMClient.getService(MsgService.class).registerCustomAttachmentParser(new CustomAttachParser()); 
}

消息属性设置

发送消息时可以设置消息配置选项 CustomMessageConfig,主要用于设定该消息是否存入云端、是否写入漫游等。

自定义消息配置 CustomMessageConfig 属性

/**
 * 该消息是否要保存到服务器
 * 默认为true。
 */
public boolean enableHistory = true;

/**
 * 该消息是否需要漫游。
 * 默认为true
 */
public boolean enableRoaming = true;

/**
 * 当发送方在多个客户端同时登录时,其中一端发送一条消息后,客户端是否需要在收消息的回调抛出该条消息。
 * 默认为true
 */
public boolean enableSelfSync = true;

/**
 * 该消息是否要推送提醒
 * 默认为true
 */
public boolean enablePush = true;

/**
 * 该消息是否需要推送昵称
 * 默认为true
 */
public boolean enablePushNick = true;

/**
 * 该消息是否要计入未读数。
 * 默认为true
 */
public boolean enableUnreadCount = true;

/**
 * 该消息是否触发抄送。
 * 默认为true
 */
public boolean enableRoute = true;

/**
 *
 * 该消息是否要存离线,若设置为false,将不会存入离线库与云端历史消息库。
 * 默认为true
   */
public boolean enablePersist = true;
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
String text = "this is an example";
// 创建一个文本消息
IMMessage textMessage = MessageBuilder.createTextMessage(account, sessionType, text);

// 消息的配置选项
CustomMessageConfig config = new CustomMessageConfig();
// 该消息不保存到服务器
config.enableHistory = false;
// 该消息不漫游
config.enableRoaming = false;
// 该消息不同步
config.enableSelfSync = false;
textMessage.setConfig(config);

// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(textMessage, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

文件资源场景

SDK支持对图片、语音、视频与文件等设置对应的存活时间。

在初始化SDK时,可做相应的文件资源场景配置,参考代码:


static SDKOptions getSDKOptions(Context context) {
        SDKOptions options = new SDKOptions();
         //其他配置
        options.mNosTokenSceneConfig = createNosTokenScene();
        return options;
}

public static final String TEST_NOS_SCENE_KEY="test_nos_scene_key";

private static NosTokenSceneConfig createNosTokenScene() {
      NosTokenSceneConfig nosTokenSceneConfig = new NosTokenSceneConfig();

      //更新默认场景(NimNosSceneKeyConstant.NIM_DEFAULT_IM)对应的过期时间(天)
      nosTokenSceneConfig.updateDefaultIMSceneExpireTime(1);

      //更新默认场景 (NimNosSceneKeyConstant.NIM_DEFAULT_PROFILE) 对应的过期时间(天)
      nosTokenSceneConfig.updateDefaultProfileSceneExpireTime(2);

      //设置自定义场景及对应的过期时间(天),0代表永不过期。
      //建议sceneKey常量化,这样使用的时候比较方便,目前支持自定义最多10种场景
      nosTokenSceneConfig.appendCustomScene(TEST_NOS_SCENE_KEY, 4);
      return nosTokenSceneConfig;
}

NimNosSceneKeyConstant主要参数一览:

NimNosSceneKeyConstant 含义
NIM_DEFAULT_IM 私聊、群聊、聊天室发送图片、音频、视频、文件消息等。
NIM_DEFAULT_PROFILE 对应使用云信上传服务上传的用户、群组..资料(eg:头像)等。

值得注意的是:

本地消息插入

当有业务场景需要单纯插入一条消息至本地数据库内,而不发出时,可以使用插入本地消息的方法来实现。

/**
* 插入消息到本地数据库,且通知更新UI界面,但不发送到服务器端。发送一条可设置他人为发送方的本地消息给自己。
* 该接口将消息保存到数据库后,会通知到UI,此时会触发{@link MsgServiceObserve#observeReceiveMessage(Observer, boolean)}通知。
*
* @param msg    待插入的消息对象
* @param fromAccount 发送者ID
* @return InvocationFuture 可以设置回调函数。在消息存入数据库后,就会回调。
*/
InvocationFuture<Void> insertLocalMessage(IMMessage msg, String fromAccount);

/**保存消息到本地数据库,但不发送到服务器端。可设置保存消息的时间 用于第三方APP保存本地提醒一类的消息。 该接口将消息保存到数据库后,如果需要通知到UI,可将notify设置为true,此时会触发{@link MsgServiceObserve.observeReceiveMessage(Observer, boolean)}通知。

* @param msg    待插入的消息对象
* @param notify 是否要通知
* @param time   设置该本地消息的时间
* @return InvocationFuture 可以设置回调函数。在消息存入数据库后,就会回调。
*/
InvocationFuture<java.lang.Void> saveMessageToLocalEx(IMMessage msg,boolean notify,long time);

消息更新

扩展字段更新

SDK支持更新客户端数据库内的消息的客户端扩展字段。

/**
* 更新消息的LocalExtension
* @param  message  待更新的消息
*/
void updateIMMessage(IMMessage message);

状态更新

/**
* 更新消息状态
* @param  message  待更新的消息
*/
void updateIMMessageStatus(IMMessage message);

示例:

msg.setStatus(MsgStatusEnum.success);
NIMClient.getService(MsgService.class).updateIMMessageStatus(msg);

注意:请不要对notification类型消息进行更新。

消息重发

消息发送失败之后,可以重发消息。消息重发和消息发送是同一个接口,只是参数的设置不同而已,当 resend 参数为 true 时,表示重发消息。

消息转发

SDK 支持消息转发功能,但不支持通知消息和音视频通话事件消息的转发,其他消息类型均支持。

首先,通过 MessageBuilder 创建一个待转发的消息,然后通过 MsgService#sendMessage 接口,将消息发送出去。

/**
 * 创建一条待转发的消息
 *
 * @param message     要转发的消息
 * @param sessionId   聊天对象ID
 * @param sessionType 会话类型
 * @return 待转发的消息
 */
public static IMMessage createForwardMessage(IMMessage message, String sessionId, SessionTypeEnum sessionType);
参数 说明
message 要转发的消息
sessionId 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
sessionType 聊天类型,SessionTypeEnum.P2P 为单聊类型,SessionTypeEnum.Team 为群聊类型
// 该帐号为示例,请先注册
String account = "testAccount";
// 以单聊类型为例
SessionTypeEnum sessionType = SessionTypeEnum.P2P;
// forwardMessage为待转发的消息, 一般由上下文获得
IMMessage message = MessageBuilder.createForwardMessage(forwardMessage, account, sessionType);
// 发送给对方
NIMClient.getService(MsgService.class).sendMessage(textMessage, false).setCallback(new RequestCallback<Void>() {
                @Override
                public void onSuccess(Void param) {

                }

                @Override
                public void onFailed(int code) {

                }

                @Override
                public void onException(Throwable exception) {

                }
            });

消息接收

添加消息接收观察者 MsgServiceObserve#observeReceiveMessage,在有新消息到达时,第三方 APP 就可以接收到通知。

/**
 * 注册/注销消息接收观察者。
 *     通知的消息列表中的消息不一定全是接收的消息,也有可能是自己发出去,比如其他端发的消息漫游过来,
 *     或者调用MsgService#saveMessageToLocal(IMMessage, boolean)后,notify参数设置为true,通知出来的消息。
 * @param observer 观察者, 参数为收到的消息列表,消息列表中的消息均保证来自同一个聊天对象。
 * @param register true为注册,false为注销
 */
public void observeReceiveMessage(Observer<List<IMMessage>> observer, boolean register);
Observer<List<IMMessage>> incomingMessageObserver =
    new Observer<List<IMMessage>>() {
        @Override
        public void onEvent(List<IMMessage> messages) {
            // 处理新收到的消息,为了上传处理方便,SDK 保证参数 messages 全部来自同一个聊天对象。
        }
    }
NIMClient.getService(MsgServiceObserve.class)
         .observeReceiveMessage(incomingMessageObserver, true);

通知类消息接收

一些特定场景的行为,云信服务器预置了一些通知消息。通知消息也是一种特定消息,开发者需要解析消息中附带的信息,来获取通知内容。如最常见的通知消息是群通知事件,如有新成员进群,则群内已有成员将收到此通知消息。目前不支持从客户端发出通知消息

通知消息属于会话内的一种消息,其对应的数据结构为 IMMessage,消息类型为 MsgTypeEnum.notification。目前用于群通知和聊天室通知事件。

通知消息需要进行解析,具体参见群组通知消息

多媒体类消息接收

多媒体类消息,如语音消息、图片消息、文件消息和视频消息,需要考虑收到消息后下载资源的情况。 针对使用云信上传服务的多媒体类消息:

若要关闭默认策略,自主选择下载时机,可以使用以下方法来完成:

修改初始化配置参数 SDKOptions - preloadAttach 为 false 。

原型

/**
 * 正常情况收到消息后附件会自动下载。如果下载失败,可调用该接口重新下载
 *
 * @param msg   附件所在的消息体
 * @param thumb 是否只下载缩略图。为true时,仅下载缩略图。
 *              该参数仅对图片和视频类消息有效
 * @return AbortableFuture 调用跟踪。可设置回调函数,可中止下载操作
 */
public AbortableFuture<Void> downloadAttachment(IMMessage msg, boolean thumb);
参数 说明
msg 附件所在的消息体
thumb 是否只下载缩略图。为 true 时,仅下载缩略图。该参数仅对图片和视频类消息有效
// 下载之前判断一下是否已经下载。若重复下载,会报错误码414。(以SnapChatAttachment为例)
private boolean isOriginImageHasDownloaded(final IMMessage message) {
    if (message.getAttachStatus() == AttachStatusEnum.transferred &&
        !TextUtils.isEmpty(((SnapChatAttachment) message.getAttachment()).getPath())) {
        return true;
    }
    return false;
}
// 因为下载的文件可能会很大,这个接口返回类型为 AbortableFuture ,允许用户中途取消下载。
AbortableFuture future = NIMClient.getService(MsgService.class).downloadAttachment(message, false);

下载完成后,可以通过对应的MsgAttachment获取到具体的附件内容。多媒体附件的基类是 FileAttachment,继承自 MsgAttachment。它的子类主要有:

- 视频消息附件 VideoAttachment

- 图片消息附件 ImageAttachment

- 音频消息附件 AudioAttachment

以 FileAttachment 为例,说明附件参数:

返回值 FileAttachment 接口 说明
String getPath() 获取文件本地路径,若文件不存在,返回 null。
语音消息的文件路径,在附件下载完成之后可以获取。
图片或视频的文件路径,需要手动下载之后获取,收到消息时,SDK 只默认自动下载缩略图文件
String getPathForSave() 获取用于保存该文件的位置
String getThumbPath() 获取缩略图文件的本地路径,若文件不存在,返回 null。
当消息状态显示缩略图下载完成之后,可获取缩略图文件路径
String getThumbPathForSave() 获取用于保存缩略图文件的位置
void setPath(String path) 设置文件路径
long getSize() 获取文件大小,单位为byte
void setSize(long size) 设置文件大小,单位为 byte
String getMd5() 获取文件内容 MD5
void setMd5(String md5) 设置文件内容 MD5
String getUrl() 获取文件在服务器上的下载 url。若文件还未上传,返回 null
void setUrl(String url) 设置文件在服务器上下载 url
String getExtension() 获取文件后缀名
void setExtension(String extension) 设置文件后缀名
String getFileName() 获取文件名
String getDisplayName() 获取文件的显示名。可以和文件名不同,仅用于界面展示
String setDisplayName() 设置文件显示名

其他类型附件属性详见客户端API文档。

监听消息状态

该接口可以监听消息接收或发送状态 MsgStatusEnum 和 消息附件接收或发送状态 AttachStatusEnum 的变化。当状态更改为 AttachStatusEnum.transferred 表示附件下载成功。

/**
 * 注册/注销消息状态变化观察者
 * @param observer 观察者, 参数为改变的消息体,更改的状态可能包含status和attachStatus
 * @param register true为注册,false为注销
 */
public void observeMsgStatus(Observer<IMMessage> observer, boolean register);

MsgStatusEnum 属性说明

MsgStatusEnum 属性说明 说明
draft 草稿
sending 正在发送中
success 发送成功
fail 发送失败
read 一般用于收到的音频消息,当前登录用户有无听过
unread 未读状态

AttachStatusEnum 属性说明

AttachStatusEnum 属性说明 说明
def 默认状态,未开始
transferring 正在传输
transferred 传输成功, 附件下载成功标志
fail 传输失败
// 监听消息状态变化
NIMClient.getService(MsgServiceObserve.class).observeMsgStatus(statusObserver, register);

private Observer<IMMessage> statusObserver = new Observer<IMMessage>() {
        @Override
        public void onEvent(IMMessage msg) {
            // 1、根据sessionId判断是否是自己的消息
            // 2、更改内存中消息的状态
            // 3、刷新界面
        }
    };

聊天室接收消息

详见聊天室章节。

消息过滤

SDK提供消息过滤忽略的功能。消息过滤后,SDK将不存储对应的消息,也不会上抛给接收回调,因此应用层不会收到对应的消息。

消息过滤仅对在线消息、离线消息、漫游消息有效。查询本地和云端历史记录是无法过滤的。

不建议在不要在消息过滤函数或方法中进行耗时操作,否则将导致线程阻塞。

请注意,注册过滤器的时机,建议放在 Application 的 onCreate 中, SDK 初始化之后。

/**
 * 注册消息过滤器
 *
 * @param filter 上层实现的消息过滤器,决定是否过滤消息(不存储到数据库中),传 null 表示注销(取消)通知消息过滤器
 */
void registerIMMessageFilter(IMMessageFilter filter);
返回值 IMMessageFilter接口 说明
boolean shouldIgnore(IMMessage message) 是否过滤消息。
返回 true 表示过滤(那么 SDK 将不存储此消息,上层也不会收到此消息),
默认 false 即不过滤(默认存储到 db 并通知上层)

SDK 过滤群头像变更通知类型消息。

// 在 Application启动时注册,保证漫游、离线消息也能够回调此过滤器进行过滤。注意,过滤器的实现不要有耗时操作。
NIMClient.getService(MsgService.class).registerIMMessageFilter(new IMMessageFilter() {
        @Override
        public boolean shouldIgnore(IMMessage message) {
            if (UserPreferences.getMsgIgnore() && message.getAttachment() != null) {
                if (message.getAttachment() instanceof UpdateTeamAttachment) {
                    UpdateTeamAttachment attachment = (UpdateTeamAttachment) message.getAttachment();
                    for (Map.Entry<TeamFieldEnum, Object> field : attachment.getUpdatedFields().entrySet()) {
                        if (field.getKey() == TeamFieldEnum.ICON) {
                            return true; // 过滤
                        }
                    }
                }
            }
            return false; // 不过滤
        }
    });

文件传输过程管理

监听文件传输进度

SDK提供文件传输管理功能,开发可以通过注册消息附件上传/下载进度观察者来监控文件传输。

/**
 * 注册/注销消息附件上传/下载进度观察者
 *
 * @param observer 观察者, 参数为附件的传输进度
 * @param register true为注册,false为注销
 */
void observeAttachmentProgress(Observer<AttachmentProgress> observer,
                               boolean register);

取消上传消息附件

MsgService 接口提供了取消上传消息附件的方法。

/**
*  取消上传消息附件(图片、视频、文件类型的),如果附件已经上传成功,操作将会失败 。如果成功取消了附件的上传,那么相应的消息会发送失败,对应的消息状态是MsgStatusEnum.fail,附件状态是AttachStatusEnum.cancel。注意:此操作暂时不支持聊天室。
* @param imMessage   要取消上传附件的消息
*/
InvocationFuture<java.lang.Void> cancelUploadAttachment(IMMessage imMessage);

消息撤回

消息撤回

允许用户撤回一定时间内发送过的消息,客户端可允许撤回时长默认2分钟,可在网易云信控制台配置。

/**
 * 消息撤回 , 并设置相应的第三方推送配置(包括IOS平台的推送)与未读数变化,如果想要关闭App内的返回消息提醒,参考{@link NIMClient#toggleRevokeMessageNotification(boolean on)}
 *
 * @param message        待撤回的消息
 * @param customApnsText 第三方透传消息推送文本,不填则不推送
 * @param pushPayload    第三方自定义的推送属性,限制json类型,长度2048
 * @param shouldNotifyBeCount 撤回通知是否更新未读数
 * @param postscript 附言
 * @return InvocationFuture 可设置回调函数,监听发送结果。
 */
InvocationFuture<Void> revokeMessage(IMMessage message, String customApnsText,
                  Map<String, Object> pushPayload, boolean shouldNotifyBeCount,
                  String postscript);
参数 说明
message 待撤回的消息
customApnsText 第三方透传消息推送文本,不填则不推送
pushPayload 第三方自定义的推送属性,限制json类型,长度2048
shouldNotifyBeCount 撤回通知是否更新未读数
postscript 附言
NIMClient.getService(MsgService.class).revokeMessage(message, null, null, true, postscript).setCallback(new RequestCallbackWrapper<Void>() {
    @Override
    public void onResult(int code, Void result, Throwable exception) {

    }
});

在撤回消息请求调用成功后, SDK 会先回调给上层成功,再自动将本地的这条消息删除。如果需要在撤回后显示一条本方已撤回的提示,开发者可以自行构造一条提醒消息并调用插入本地消息的方法。

以下情况消息撤回会失败:

针对撤回场景的通知栏内容覆盖需求:如A发消息给B,产生APNs推送,文案内容为“你好“。然后A撤回了这条消息,此时通知栏中的“你好”变为预设的“对方撤回了一条消息”。

一种建议的实现方式是:

监听消息撤回

被撤回方会收到消息撤回通知。

/**
 * 注册/注销消息撤回的观察者
 * @param observer 观察者,参数为被撤回的消息。
 * @param register true为注册,false为注销
 */
public void observeRevokeMessage(Observer<RevokeMsgNotification> observer, boolean register);
方法 说明
getMessage() 获取撤回消息
getRevokeAccount() 获取撤回者
getCustomInfo() 获取消息撤回时设置的msg 字段(eg: 通过服务端API撤回)。
getNotificationType() 获取通知类型: 1表示是离线通知,2表示是漫游通知 , 默认 0
getRevokeType() 获取撤回类型:分为点对点双向撤回, 群双向撤回, 超大群双向撤回, 点对点单向撤回和未定义
Observer<RevokeMsgNotification> revokeMessageObserver = new Observer<RevokeMsgNotification>() {
    @Override
    public void onEvent(RevokeMsgNotification notification) {
       // 监听到消息撤回的通知,可以在界面做相应的操作
       // 获取撤回消息
       notification.getMessage();
       // 获取撤回者
       notification.getRevokeAccount();
    }
};

NIMClient.getService(MsgServiceObserve.class).observeRevokeMessage(revokeMessageObserver, true);

此外,若启用消息提醒功能,则收到撤回通知时,也会触发产生提醒通知栏。若不需要,可以通过

NIMClient.toggleRevokeMessageNotification(false);

来关闭,详见撤回通知消息提醒

消息删除

单向删除

允许用户删除一条或多条云端历史消息。

账号为A的用户单向删除了消息后,这些消息不再能被A账号拉取到,但是其他账号的用户不受此操作影响

单向删除单条消息

/**
 * 单向删除一条消息
 *
 * @param msg 被单向删除的消息
 * @param ext 扩展字段
 */
InvocationFuture<Long> deleteMsgSelf(IMMessage msg, String ext);
参数 说明
msg 被单向删除的消息
ext 扩展字段
NIMClient.getService(MsgService.class).deleteMsgSelf(selectedItem, "").setCallback(new RequestCallback<Long>() {
    @Override
    public void onSuccess(Long param) {
        NimLog.i(TAG, "delete self succeed");
        deleteItem(selectedItem, true, false);
    }

    @Override
    public void onFailed(int code) {
        NimLog.i(TAG, "failed to delete self, code=" + code);
    }

    @Override
    public void onException(Throwable exception) {
        NimLog.i(TAG, "delete self error, e=" + exception);
    }
});

单向删除多条消息

/**
 * 单向删除多条消息,需要来自同一会话
 *
 * @param msgList 被单向删除的消息列表
 * @param ext 扩展字段
 */
InvocationFuture<Long> deleteMsgSelf(List<IMMessage> msgList, String ext);
参数 说明
msgList 被单向删除的消息列表
ext 扩展字段
NIMClient.getService(MsgService.class).deleteMsgSelf(checked, "").setCallback(new RequestCallback<Long>() {
    @Override
    public void onSuccess(Long param) {
        ToastHelper.showToast(MsgSelectActivity.this, "单向删除成功 " + param);
        mMsgAdapter.deleteItems(checked, true);
        finish();
    }

    @Override
    public void onFailed(int code) {
        ToastHelper.showToast(MsgSelectActivity.this, "单向删除失败 code=" + code);
    }

    @Override
    public void onException(Throwable exception) {
        ToastHelper.showToast(MsgSelectActivity.this, "单向删除错误 msg=" + exception.getMessage());
        exception.printStackTrace();
    }
});

单向清空会话中的消息

/**
 * 清空历史消息
 *
 * @param sessionId     用户帐号
 * @param sessionType 聊天类型
 * @param sync 是否同步给其他端
 * @param ext 扩展字段
 */
void clearServerHistory(String sessionId, SessionTypeEnum sessionType, boolean sync, String ext);
参数 说明
sessionId 用户帐号
sessionType 聊天类型
sync 是否同步给其他端
ext 扩展字段
NIMClient.getService(MsgService.class).clearServerHistory(sessionId, sessionType, true, "");

已读回执

当发送方需要知道接收方是否已经阅读了自己发送的消息时,需要使用已读回执的功能。

单聊消息已读回执

发送已读回执:

在会话界面中调用发送已读回执的接口并传入当前会话的最后一条消息,即表示这之前的消息本方都已读。

/**
* 发送消息已读回执
* @param sessionId 会话ID(聊天对象账号)
* @param message 已读的消息(一般是当前接收的最后一条消息)
*/
NIMClient.getService(MsgService.class).sendMessageReceipt(sessionId, message);
// 该帐号为示例,请先注册
String account = "testAccount";
// message为会话中已读的最后一条消息
NIMClient.getService(MsgService.class).sendMessageReceipt(account, message);

监听已读回执:

/**
 * 注册/注销消息已读回执观察者
 * @param observer 观察者, 参数为已读回执集合。
 * @param register true为注册,false为注销
 */
public void observeMessageReceipt(Observer<List<MessageReceipt>> observer, boolean register);
返回值 MessageReceipt接口 说明
String getSessionId() 聊天对象的 ID,如果是单聊,为用户帐号,如果是群聊,为群组 ID
long getTime() 该会话最后一条已读消息的时间,比该时间早的消息都视为已读
// 注册/注销观察者
NIMClient.getService(MsgServiceObserve.class).observeMessageReceipt(messageReceiptObserver, register);
private Observer<List<MessageReceipt>> messageReceiptObserver = new Observer<List<MessageReceipt>>() {
        @Override
        public void onEvent(List<MessageReceipt> messageReceipts) {
            receiveReceipt();
        }
};

此外,可以根据 IMMessage 中的 isRemoteRead() 方法来判断该条消息对方是否已读。

群聊消息已读回执

群消息已读回执功能,需要联系商务顾问申请开通后才能使用。同时,使用该功能时需要将群成员控制在100人以内。

初始化SDK时,配置SDKOptions - enableTeamMsgAck参数为 true 来启用群消息已读回执功能。

发送群消息时,通过IMMessage - setMsgAck()方法标记该消息需要已读回执。接收群消息时,通过IMMessage - needMsgAck属性获取该消息是否需要已读回执。

示例

// 创建待发送消息
IMMessage message = MessageBuilder.createTextMessage(sessionId, SessionTypeEnum.Team, "content");
// 标记该消息需要已读回执反馈
message.setMsgAck();
// 发送消息
NIMClient.getService(MsgService.class).sendMessage(message, false);

原型

/**
 * (群消息接收方)标记群组消息已读
 *
 * @param message 群组消息
 * @return InvocationFuture 可设置回调函数,监听发送结果。
 */
InvocationFuture<Void> sendTeamMessageReceipt(IMMessage message);

示例

NIMSDK.getTeamService().sendTeamMessageReceipt(message)

此外,可以通过IMMessage - hasSendAck()来判断是否已经发送过群消息已读回执。

原型

/**
 * 注册/注销群消息已读回执观察者(群成员发出需要已读回执的消息时,当有群成员已读后,该观察者会回调)
 *
 * @param observer 观察者,参数为已读回执信息集合
 * @param register true为注册,false为注销
 */
public void observeTeamMessageReceipt(Observer<List<TeamMessageReceipt>> observer, boolean register);

参数说明

TeamMessageReceipt 接口说明

返回值 TeamMessageReceipt 接口 说明
String getMsgId() 获取消息 id
int getAckCount() 获取已读人数
int getUnAckCount() 获取未读人数

示例

// 注册监听器
NIMClient.getService(MsgServiceObserve.class).observeTeamMessageReceipt(teamMessageReceiptObserver, register);
// 监听器的实现
private Observer<List<TeamMessageReceipt>> teamMessageReceiptObserver = new Observer<List<TeamMessageReceipt>>() {
    @Override
    public void onEvent(List<TeamMessageReceipt> teamMessageReceipts) {
      ...
    }
};

原型

/**
 * (群消息发送方)批量刷新群组消息已读、未读的数量信息,没有异步回调
 * 如果已读、未读数有变更,会通过 {@link com.netease.nimlib.sdk.msg.MsgServiceObserve#observeTeamMessageReceipt(Observer, boolean)}来批量通知,没有变更则不会通知
 *
 * @param messages 请求刷新的群组消息集合
 */
void refreshTeamMessageReceipt(List<IMMessage> messages);

一般在加载消息进行批量刷新。反复刷新,SDK实际上不会发出网络请求。只有已读、未读数量有变更,才会通过观察者接口进行通知。该API为异步无回调接口。

示例

// messages为接收到的批量消息
NIMSDK.getTeamService().refreshTeamMessageReceipt(messages);

原型

/**
 * (群消息发送方)查询单条群组消息已读、未读账号列表
 *
 * @param message 待查询的消息
 * @return 该消息的已读、未读账号列表
 */
InvocationFuture<TeamMsgAckInfo> fetchTeamMessageReceiptDetail(IMMessage message);

示例

//  message为待查询的消息
NIMSDK.getTeamService().fetchTeamMessageReceiptDetail(message).setCallback(new RequestCallback<TeamMsgAckInfo>() {
    @Override
    public void onSuccess(TeamMsgAckInfo param) {
        // 获取成功
    }

    @Override
    public void onFailed(int code) {
        // 获取失败
    }

    @Override
    public void onException(Throwable exception) {
        // 异常
    }
});

通过 IMMessage 接口的 getTeamMsgAckCount()方法来获取群消息已读账号数量,通过 getTeamMsgUnAckCount() 方法来获取群消息未读账号数量。

广播消息接收

网易云信支持全员广播消息,广播消息由服务端接口发起,对应用内的所有用户发送一条广播消息。客户端不支持发送, SDK 收到广播之后直接往上层通知,不支持客户端存储。

/**
 * 注册/注销全员广播消息观察者
 * @param observer 观察者,参数全员广播消息。
 * @param register true为注册,false为注销
 */
public void observeBroadcastMessage(Observer<BroadcastMessage> observer, boolean register);
参数 说明
id 广播id
fromAccount 广播发送者账号
time 广播消息时间戳
content 广播消息内容
/**
 * 注册云信全服广播接收器
 *
 * @param register
 */
private void registerNimBroadcastMessage(boolean register) {
    NIMClient.getService(MsgServiceObserve.class).observeBroadcastMessage(new Observer<BroadcastMessage>() {
        @Override
        public void onEvent(BroadcastMessage broadcastMessage) {
            // 处理
        }
    }, register);
}

语音消息处理

云信 SDK 提供了高清语音的录制与播放的功能,用于处理语音消息。

播放

云信提供 AudioPlayer 类来支持语音播放的功能。

AudioPlayer 接口说明:

返回值 AudioPlayer 接口 说明
long getCurrentPosition() 获取当前音频播放进度
long getDuration() 获取音频持续时间长度
OnPlayListener getOnPlayListener() 获取 AudioPlayer 的播放进度监听
boolean isPlaying() 查询是否正在播放
void seekTo(int msec) 让播放器跳转到指定位置继续播放
void setDataSource(String audioFile) 设置音频来源
void setOnPlayListener(OnPlayListener listener) 设置播放监听
void start(int audioStreamType) 开始播放
void stop() 停止播放

构造播放实例

/**
 * 音频播放器构造函数
 * @param context 上下文参数
 * @param audioFile 待播放音频的文件路径
 * @param listener 播放进度监听者
 */
public AudioPlayer(Context context, String audioFile, OnPlayListener listener);
参数 说明
context 上下文参数
audioFile 待播放音频的文件路径
listener 播放进度监听者

OnPlayListener 接口说明:

OnPlayListener 接口 说明
onCompletion() 播放完成
onError(String error) 播放过程中出错。参数为出错原因描述
onInterrupt() 中断播放
onPlaying(long curPosition) 播放进度报告,每隔 500ms 会回调一次,告诉当前进度。 参数为当前进度,单位为毫秒,可用于更新 UI
onPrepared() 文件解码完成,准备播放
// 定义一个播放进程回调类
OnPlayListener listener = new OnPlayListener() {

    // 音频转码解码完成,会马上开始播放了
    public void onPrepared() {}

    // 播放结束
    public void onCompletion() {}

    // 播放被中断了
    public void onInterrupt() {}

    // 播放过程中出错。参数为出错原因描述
    public void onError(String error){}

    // 播放进度报告,每隔 500ms 会回调一次,告诉当前进度。 参数为当前进度,单位为毫秒,可用于更新 UI
    public void onPlaying(long curPosition) {}
};

// 构造播放器对象
AudioPlayer player = new AudioPlayer(context, filePath, listener);

播放控制

// 开始播放。需要传入一个 audioStreamType 参数,表示是用听筒播放还是扬声器。
// AudioManager.STREAM_VOICE_CALL    表示使用听筒模式
// AudioManager.STREAM_MUSIC         表示使用扬声器模式
player.start(audioStreamType);
// 如果中途切换播放设备,重新调用 start,传入指定的 audioStreamType 即可。player 会自动停止播放,然后再以新的 streamType 重新开始播放。
// 如果需要从中断的地方继续播放,需要开发者记住已经播放到的位置,然后在 onPrepared 回调中调用 seekTo
player.seekTo(pausedPosition);
player.stop();
// 获取当前音频播放进度
player.getCurrentPosition();

// 获取音频持续时间长度
player.getDuration();

// 查询是否正在播放
player.isPlaying();

// 设置音频来源
player.setDataSource(audioFile);

录制

SDK 提供 AudioRecorder 来支持语音的录制。

AudioRecorder 接口说明:

返回值 AudioRecorder 接口 说明
void completeRecord(boolean cancel) 完成(结束)录音,根据参数 cancel,做不同的回调。
如果 cancel 为 true,回调 IAudioRecordCallback#onRecordCancel, 为 false,回调 IAudioRecordCallback#onRecordSuccess
void destroyAudioRecorder() 释放资源
int getCurrentRecordMaxAmplitude() 获取当前录音时最大振幅, 40ms 更新一次数据
void handleEndRecord(boolean isSuccess, int duration) 处理录制结束后的操作,回调 IAudioRecordCallback#onRecordSuccess
boolean isRecording() 是否正在录音
void startRecord() 启动(开始)录音,如果成功,会按照顺序回调 IAudioRecordCallback#onRecordReady 和 IAudioRecordCallback#onRecordStart

构造录制实例

/**
 * 构造函数
 *
 * @param context     上下文
 * @param recordType  录制音频类型(aac/amr)
 * @param maxDuration 最长录音时长,到该长度后,会自动停止录音
 * @param cb          录音过程回调
 */
public AudioRecorder(Context context, RecordType recordType,int maxDuration, IAudioRecordCallback cb);
参数 说明
context 上下文
recordType 录制音频类型(aac/amr)
maxDuration 最长录音时长,到该长度后,会自动停止录音
cb 录音过程回调
IAudioRecordCallback 接口 说明
onRecordCancel() 录音结束, 用户主动取消录音
onRecordFail() 录音结束,出错
onRecordReachedMaxTime(int maxTime) 到达指定的最长录音时间
onRecordReady() 录音器已就绪,提供此接口用于在录音前关闭本地音视频播放(可选)
onRecordStart(File audioFile, RecordType recordType) 开始录音回调
onRecordSuccess(File audioFile, long audioLength, RecordType recordType) 录音结束,成功
// 定义录音过程回调对象
IAudioRecordCallback callback = new IAudioRecordCallback () {

    void onRecordReady() {
        // 初始化完成回调,提供此接口用于在录音前关闭本地音视频播放(可选)
    }

    void onRecordStart(File audioFile, RecordType recordType) {
        // 开始录音回调
    }

    void onRecordSuccess(File audioFile, long audioLength, RecordType recordType) {
        // 录音结束,成功
    }

    void onRecordFail() {
        // 录音结束,出错
    }

    void onRecordCancel() {
        // 录音结束, 用户主动取消录音
    }

    void onRecordReachedMaxTime(int maxTime) {
        // 到达指定的最长录音时间
    }
};

// 初始化recorder
AudioRecorder recorder = new AudioRecorder(
    context,
    RecordType.AAC, // 录制音频类型(aac/amr)
    maxDuration, // 最长录音时长,到该长度后,会自动停止录音, 默认120s
    callback // 录音过程回调
);

录制控制

// 启动(开始)录音,如果成功,会按照顺序回调onRecordReady和onRecordStart。
recorder.startRecord();
// 结束录音, 正常结束,或者取消录音
// cancel为true,代表取消;为false,代表结束录音。
recorder.completeRecord(cancel);

// 是否正在录音
recorder.isRecording();

// 获取当前录音时最大振幅, 40ms更新一次数据。
recorder.getCurrentRecordMaxAmplitude();

// 录制达到最长录音时长时,确认当前录制
recorder.handleEndRecord(true, maxTime) 

语音转文字

要使用语音转文字功能,请联系商务顾问申请开通「语音识别」。如果未开通功能的情况下调用接口,将返回403。

语音转文字的原理:

使用 MsgService 接口中如下方法进行语音转文字:

/**
*  语音转文字并指定上传文件的场景以及是否要强制重新上传文件
* @param  voiceUrl - 语音url。可选项。如果没有,SDK自动做nos上传工作。
* @param  path - 语音path, 用于获得语音采样率。APP必须保证音频已经下载到本地
* @param  duration - 语音时长
* @param  sceneKey - 上传文件时用的nos sceneKey ,默认值:NimNosSceneKeyConstant#NIM_DEFAULT_IM , nos token scene 配置参考文件资源场景章节
* @param  enableForceUploadFile - 如果服务器存在相同的文件,是否强制重新上传文件 ,默认false
* AbortableFuture 调用跟踪。可设置回调函数,可中止下载操作
*/
AbortableFuture<java.lang.String> transVoiceToTextEnableForce(java.lang.String voiceUrl,
                                                              java.lang.String path,
                                                              long duration,
                                                              java.lang.String sceneKey,
                                                              boolean enableForceUploadFile);

示例:

public void voiceToText(IMMessage msg, String sceneKey) {
        AudioAttachment attachment = (AudioAttachment) msg.getAttachment();
        String voiceUrl = attachment.getUrl();
        String path = attachment.getPath();
        refreshStartUI();
        callFuture = NIMClient.getService(MsgService.class).transVoiceToTextEnableForce(voiceUrl, path, attachment.getDuration(), sceneKey, false);
        callFuture.setCallback(new RequestCallback<String>() {
            @Override
            public void onSuccess(String param) {
                voiceTransText.setText(param);
                updateUI();
            }

            @Override
            public void onFailed(int code) {
                LogUtil.e(TAG, "voice to text failed, code=" + code);
                voiceTransText.setText(R.string.trans_voice_failed);
                failIcon.setVisibility(View.VISIBLE);
                updateUI();
            }

            @Override
            public void onException(Throwable exception) {
                LogUtil.e(TAG, "voice to text throw exception, e=" + exception.getMessage());
                voiceTransText.setText("参数错误");
                failIcon.setVisibility(View.VISIBLE);
                updateUI();
            }
        });
        show();
    }