Android IMLib SDK 开发指南

    前期准备

    在您阅读此文档之前,我们假定您已具备基础的 Android 应用开发经验,并能够理解相关基础概念。

    IMLib 快速集成

    您可以阅读前期准备文档,了解注册开发者账号、下载 SDK、创建应用的具体步骤。

    您可以阅读集成环境文档,了解集成所需的环境要求。

    如果您已经阅读了上面的文档内容,并准备好工程和 AppKey 相关事宜,则可以参考本章节内容,快速便捷的集成融云即时通讯绝大部分功能(启动会话列表,进入会话,发送文本、语音、图片、位置消息等)。

    本章节将介绍如何将 IMLib 快速集成到您的 App。

    工程准备

    下面以 Android Studio 为例说明如何将 IMLib 导入到您的工程。

    创建一个 Project,点击 next ,如图:

    image

    默认点击 next,如图:

    image

    选择 Activity 点击 next,如图:

    image

    点击 finish 完成,如图:

    image

    个人习惯,把工程结构由 Android 改成 Project,如图:

    image

    打开下载好的 IMLib 文件夹,将 IMLibAndroidManifest.xml 中的部分代码复制到您新建工程的 AndroidManifest.xml 中去。如图,第一部分。

    image

    第二部分。请务必把代码复制到对应的位置上去。第一部分代码在 <Application> 标签之外,第二部分代码在 <Application> 标签之内。

    image

    AndroidManifest 中搜索 RONG_CLOUD_APP_KEY,修改为您自己的 App Key 。如图:

    image

    IMLib/libs 下面的所有文件和文件夹 copy 到新建工程的 app/libs 路径,如图:

    image

    同样,将 IMLib/res 下面的所有文件夹 copy 到新建工程的 app/src/main/res 路径。

    打开 App 下的 build.gradle ,添加如下代码:

    image

    初始化

    AndroidManifest<Application> 标签里添加属性 android:name=".app" ,然后在工程中新建 java class > App 。下面是初始化代码。

    请注意,在此之前,请再次确保您在 AndroidManifest 里配置好了 App Key

    在整个应用程序全局,您只需要调用一次 init 方法。对于快速集成,我们建议您在 App 主进程初始化,您只需要实现一句函数,以下为融云 Demo 代码示例:

    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
    
            /**
             * OnCreate 会被多个进程重入,这段保护代码,确保只有您需要使用 RongIMClient 的进程和 Push 进程执行了 init。
             * io.rong.push 为融云 push 进程名称,不可修改。
             */
            if (getApplicationInfo().packageName.equals(getCurProcessName(getApplicationContext())) ||
                    "io.rong.push".equals(getCurProcessName(getApplicationContext()))) {
                RongIMClient.init(this);
            }
        }
    
        public static String getCurProcessName(Context context) {
    
            int pid = android.os.Process.myPid();
    
            ActivityManager activityManager = (ActivityManager) context
                    .getSystemService(Context.ACTIVITY_SERVICE);
    
            for (ActivityManager.RunningAppProcessInfo appProcess : activityManager
                    .getRunningAppProcesses()) {
    
                if (appProcess.pid == pid) {
                    return appProcess.processName;
                }
            }
            return null;
        }
    }
    

    连接服务器

    连接服务器前,确认已通过融云 Server API 接口获取 Token ,将获得的 Token 传入 connect 方法,开始连接服务器。在整个应用程序全局,只需要调用一次 connect 方法,SDK 会负责自动重连。

    您可以在 Activity 合适的地方调用 connect

    /**
     * 建立与融云服务器的连接
     *
     * @param token
     */
    private void connect(String token) {
    
        if (getApplicationInfo().packageName.equals(App.getCurProcessName(getApplicationContext()))) {
    
            /**
             * IMKit SDK调用第二步,建立与服务器的连接
             */
            RongIMClient.connect(token, new RongIMClient.ConnectCallback() {
    
                /**
                 * Token 错误,在线上环境下主要是因为 Token 已经过期,您需要向 App Server 重新请求一个新的 Token
                 */
                @Override
                public void onTokenIncorrect() {
    
                    Log.d("LoginActivity", "--onTokenIncorrect");
                }
    
                /**
                 * 连接融云成功
                 * @param userid 当前 token
                 */
                @Override
                public void onSuccess(String userid) {
    
                    Log.d("LoginActivity", "--onSuccess---" + userid);
                }
    
                /**
                 * 连接融云失败
                 * @param errorCode 错误码,可到官网 查看错误码对应的注释
                 */
                @Override
                public void onError(RongIMClient.ErrorCode errorCode) {
    
                    Log.d("LoginActivity", "--onError" + errorCode);
                }
            });
        }
    }
    

    发送与接收消息

    连接服务器成功之后,您就可以收发消息了,下面以文本消息为例,说明消息的收发。

    发送一条文本消息:

    /**
     * 发送消息。
     * @param conversationType  会话类型
     * @param targetId          会话ID
     */
    RongIMClient.getInstance().sendMessage(conversationType, targetId,
        TextMessage.obtain("我是消息内容"), null, null, new RongIMClient.SendMessageCallback() {
            @Override
            public void onSuccess(Integer integer) {
                Log.d(TAG, "发送成功");
            }
    
            @Override
            public void onError(Integer integer, RongIMClient.ErrorCode errorCode) {
                Log.d(TAG, "发送失败");
            }
        }, null);
    

    您可以设置监听器来监听接收到的消息。请在调用 connect 方法前进行设置。

    RongIMClient.setOnReceiveMessageListener(new MyReceiveMessageListener());
    

    接收消息监听器的实现,所有接收到的消息、通知、状态都经由此处设置的监听器处理。包括私聊消息、讨论组消息、群组消息、聊天室消息以及各种状态。

    private class MyReceiveMessageListener implements RongIMClient.OnReceiveMessageListener {
    
        /**
         * 收到消息的处理。
         * @param message 收到的消息实体。
         * @param left 剩余未拉取消息数目。
         * @return
         */
        @Override
        public boolean onReceived(Message message, int left) {
            //开发者根据自己需求自行处理
            return false;
        }
    }
    

    获取未读消息数

    您可以通过以下代码获取当前所有的未读消息数。

    RongIMClient.getInstance().getTotalUnreadCount(new RongIMClient.ResultCallback<Integer>() {
        @Override
        public void onSuccess(Integer integer) {
            int totalUnreadCount = integer;
            //开发者根据自己需求自行处理接下来的逻辑
        }
    
        @Override
        public void onError(RongIMClient.ErrorCode errorCode) {
    
        }
    });
    

    您也可以获取具体会话或某些类型的未读消息数,以及清空未读消息数。

    /**
     * 获取某个会话内未读消息条数
     * conversationType 会话类型
     * targetId         会话目标ID
     */
    RongIMClient.getInstance().getUnreadCount(conversationType, targetId,
        new RongIMClient.ResultCallback<Integer>() {
            @Override
            public void onSuccess(Integer integer) {
                int unreadCount = integer;
                //开发者根据自己需求自行处理接下来的逻辑
            }
    
            @Override
            public void onError(RongIMClient.ErrorCode errorCode) {
    
            }
        });
    
    /**
     * 获取某个类型的会话中所有的未读消息条数
     * conversationType 会话类型
     */
    RongIMClient.getInstance().getUnreadCount(new RongIMClient.ResultCallback<Integer>() {
            @Override
            public void onSuccess(Integer integer) {
                int unreadCount = integer;
                //开发者根据自己需求自行处理接下来的逻辑
            }
    
            @Override
            public void onError(RongIMClient.ErrorCode errorCode) {
    
            }
        }, conversationType);
    
    /**
     * 清除某个会话中的未读消息数
     * conversationType 会话类型
     * targetId         会话目标ID
     */
    RongIMClient.getInstance().clearMessagesUnreadStatus(conversationType, targetId, new RongIMClient.ResultCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean aBoolean) {
    
            }
    
            @Override
            public void onError(RongIMClient.ErrorCode errorCode) {
    
            }
        });
    

    获取会话列表

    您可以通过以下代码获取本地存储的会话列表。

    RongIMClient.getInstance().getConversationList(new RongIMClient.ResultCallback<List<Conversation>>() {
        @Override
        public void onSuccess(List<Conversation> conversations) {
    
        }
    
        @Override
        public void onError(RongIMClient.ErrorCode errorCode) {
    
        }
    });
    

    消息接口

    消息实体和消息内容

    消息实体(Message)是本地存储的实体对象,其中包含消息的各种属性(如消息 ID消息的方向接收状态接收时间发送者等)与消息内容。 消息内容(MessageContent)是与消息属性无关的一段 json 数据,可以承载任何内容。 融云SDK针对IM的使用场景,内置了常用类型的消息(如文本消息图片消息语音消息位置消息富文本消息等)。您也可以自定义消息,在消息内容中传输和存储任何 json 格式的内容。 消息在本地存储是以消息实体(Message)的形式存在,在传输时以内容(MessageContent)的形式存在。

    发送消息

    SDK 针对普通消息、图片消息提供了不同的接口,1 秒钟最多只允许发送 5 条消息。

    /**
     * 发送普通消息
     *
     * @param conversationType      会话类型
     * @param targetId              会话ID
     * @param content               消息的内容,一般是MessageContent的子类对象
     * @param pushContent           接收方离线时需要显示的push消息内容
     * @param pushData              接收方离线时需要在push消息中携带的非显示内容
     * @param SendMessageCallback   发送消息的回调
     * @param ResultCallback        消息存库的回调,可用于获取消息实体
     *
     */
    RongIMClient.getInstance().sendMessage(conversationType, targetId, TextMessage.obtain("我是消息内容"), pushContent, pushData,
        new RongIMClient.SendMessageCallback() {
            @Override
            public void onError(Integer messageId, RongIMClient.ErrorCode e) {
    
            }
    
            @Override
            public void onSuccess(Integer integer) {
    
            }
    
        }, new RongIMClient.ResultCallback<Message>() {
            @Override
            public void onError(RongIMClient.ErrorCode errorCode) {
    
            }
    
            @Override
            public void onSuccess(Message message) {
    
            }
    
        });
    
    //发送图片消息
    File imageFileSource = new File(getCacheDir(), "source.jpg");
    File imageFileThumb = new File(getCacheDir(), "thumb.jpg");
    
    try {
        // 读取图片。
        InputStream is = getAssets().open("emmy.jpg");
    
        Bitmap bmpSource = BitmapFactory.decodeStream(is);
    
        imageFileSource.createNewFile();
    
        FileOutputStream fosSource = new FileOutputStream(imageFileSource);
    
        // 保存原图。
        bmpSource.compress(Bitmap.CompressFormat.JPEG, 100, fosSource);
    
        // 创建缩略图变换矩阵。
        Matrix m = new Matrix();
        m.setRectToRect(new RectF(0, 0, bmpSource.getWidth(), bmpSource.getHeight()), new RectF(0, 0, 160, 160), Matrix.ScaleToFit.CENTER);
    
        // 生成缩略图。
        Bitmap bmpThumb = Bitmap.createBitmap(bmpSource, 0, 0, bmpSource.getWidth(), bmpSource.getHeight(), m, true);
    
        imageFileThumb.createNewFile();
    
        FileOutputStream fosThumb = new FileOutputStream(imageFileThumb);
    
        // 保存缩略图。
        bmpThumb.compress(Bitmap.CompressFormat.JPEG, 60, fosThumb);
    
    } catch (IOException e) {
            e.printStackTrace();
    }
    
    ImageMessage imgMsg = ImageMessage.obtain(Uri.fromFile(imageFileThumb), Uri.fromFile(imageFileSource));
    
    /**
    * 发送图片消息。
    *
    * @param conversationType         会话类型。
    * @param targetId                 会话目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
    * @param imgMsg                   消息内容。
    * @param pushContent              接收方离线时需要显示的push消息内容。
    * @param pushData                 接收方离线时需要在push消息中携带的非显示内容。
    * @param SendImageMessageCallback 发送消息的回调。
    */
    RongIMClient.getInstance().sendImageMessage(conversationType, targetId, imgMsg, pushContent, pushData, new RongIMClient.SendImageMessageCallback() {
    
            @Override
            public void onAttached(Message message) {
                    //保存数据库成功
            }
    
            @Override
            public void onError(Message message, RongIMClient.ErrorCode code) {
                    //发送失败
            }
    
            @Override
            public void onSuccess(Message message) {
                    //发送成功
            }
    
            @Override
            public void onProgress(Message message, int progress) {
                    //发送进度
            }
    });
    
    注:图片消息包括两个主要部分:缩略图和大图,缩略图直接 Base64 编码后放入 content 中,大图首先上传到文件服务器,然后将云存储上的大图地址放入消息体中。
    融云 SDK 中默认上传文件存储有效期为 1 个月,不支持文件迁移。

    插入消息

    您也可以在本地存储中插入一条消息,但是不向外发送。

    /**
     * 模拟消息,向本地目标 Id 中插入一条消息。
     *
     * @param type         会话类型。
     * @param targetId     目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param senderUserId 发送用户 Id。
     * @param content      消息内容。
     * @param callback     获得消息发送实体的回调。
     */
    public void insertMessage(final Conversation.ConversationType type, final String targetId, final String senderUserId, final MessageContent content, final ResultCallback<Message> callback)
    

    您可以使用 RongIMClient.getInstance().insertMessage 的方式调用。如无特殊说明,下面的接口都是通过这样的方式进行调用。

    读取本地存储消息

    /**
     * 根据会话类型的目标 Id,回调方式获取最新的 N 条消息实体。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param count            要获取的消息数量。
     * @param callback         获取最新消息记录的回调,按照时间顺序从新到旧排列。
     */
    public void getLatestMessages(final Conversation.ConversationType conversationType, final String targetId, final int count, final ResultCallback<List<Message>> callback)
    
    /**
     * 获取会话中,从指定消息之前、指定数量的最新消息实体
     *
     * @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param oldestMessageId  最后一条消息的 Id,获取此消息之前的 count 条消息,没有消息第一次调用应设置为:-1。
     * @param count            要获取的消息数量。
     * @param callback         获取历史消息记录的回调,按照时间顺序从新到旧排列。
     */
    public void getHistoryMessages(final Conversation.ConversationType conversationType, final String targetId, final int oldestMessageId, final int count, final ResultCallback<List<Message>> callback)
    
    /**
     * 获取会话中,从指定消息之前、指定数量的、指定消息类型的最新消息实体
     *
     * @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param objectName       消息类型标识。
     * @param oldestMessageId  最后一条消息的 Id,获取此消息之前的 count 条消息,没有消息第一次调用应设置为:-1。
     * @param count            要获取的消息数量
     * @param callback         获取历史消息记录的回调,按照时间顺序从新到旧排列。
     */
    public void getHistoryMessages(final Conversation.ConversationType conversationType, final String targetId, final String objectName, final int oldestMessageId, final int count, final ResultCallback<List<Message>> callback)
    

    读取远程服务器的单群聊消息

    提供单聊、群聊、讨论组、客服的历史消息获取,您每次可以从服务器获取之前 20 条以内的消息历史记录(需要先开通历史消息漫游功能),最多获取前 6 个月的历史消息。

    /**
     * 根据会话类型的目标 Id,回调方式获取某消息类型标识的N条历史消息记录。
     *
     * @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM 聊天室会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 。
     * @param dateTime         从该时间点开始获取消息。即:消息中的 sentTime;第一次可传 0,获取最新 count 条。
     * @param count            要获取的消息数量,最多 20 条。
     * @param callback         获取历史消息记录的回调,按照时间顺序从新到旧排列。
     */
    public void getRemoteHistoryMessages(final Conversation.ConversationType conversationType, final String targetId, final long dateTime, final int count, final ResultCallback<List<Message>> callback)
    

    读取远程服务器的聊天室消息

    开通聊天室消息存储功能后,融云内置的文字、语音、图片、图文、位置、文件等消息会自动在服务器端进行存储,如果您的聊天室中用到了自定义类消息,可通过定义 MessageTag.ISPERSISTED 来设置消息是否进行存储。

    从服务器端获取聊天室历史消息的接口如下:

    /**
    * 获取聊天室历史消息记录。
    * 此方法从服务器端获取之前的历史消息,但是必须先开通聊天室消息云存储功能。
    * 如果指定时间 0,则从存储的第一条消息开始拉取。
    * @param targetId 目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
    * @param recordTime 起始的消息发送时间戳,单位: 毫秒。
    * @param count 要获取的消息数量,0 < count <= 200。
    * @param order 拉取顺序: 降序, 按照时间戳从大到小; 升序, 按照时间戳从小到大。
    */
    public void getChatroomHistoryMessages(final String targetId, final long recordTime, final int count, final TimestampOrder order, final IRongCallback.IChatRoomHistoryMessageCallback callback)
    
    public enum TimestampOrder {
           /**
            * 降序, 按照时间戳从大到小.
            */
           RC_TIMESTAMP_DESC,
           /**
            * 升序, 按照时间戳从小到大.
            */
           RC_TIMESTAMP_ASC;
       }
    

    位置共享消息

    如果开发者需要使用位置共享功能, IMLib 提供了相关的位置共享的实现接口。如下:

    /**
     * 获取 RealTimeLocation 实例,每发起一次位置共享业务,就要获取一个实例。
     * 如果获取实例失败,返回 error code,对应具体的失败信息。
     * 使用时,每次进入会话,获取该会话对应的实例,以此判断位置共享业务是否可用或者正在进行中。
     * 如果返回成功,使用者可以设置监听,发起位置共享。
     * 如果返回正在进行中,则是对方已发起位置共享,使用者可以设置监听,加入。
     * 如果返回其他失败信息,使用者可以据此做出相应的提示。
     *
     * @param conversationType 发起位置共享的所在会话的会话类型。
     * @param targetId         发起位置共享的 target id。
     * @return 是否获取实例成功。
     */
    public RealTimeLocationErrorCode getRealTimeLocation(Conversation.ConversationType conversationType, String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (mLibHandler == null) {
            return RealTimeLocationErrorCode.RC_REAL_TIME_LOCATION_NOT_INIT;
        }
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "getRealTimeLocation", "Type or id is null!");
            return null;
        }
    
        int code = -1;
        try {
            code = mLibHandler.setupRealTimeLocation(conversationType.getValue(), targetId);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return RealTimeLocationErrorCode.valueOf(code);
    }
    
    /**
     * 发起位置共享。
     *
     * @param conversationType 发起位置共享的会话类型。
     * @param targetId         发起位置共享的 targetId。
     */
    public RealTimeLocationErrorCode startRealTimeLocation(final Conversation.ConversationType conversationType, final String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (mLibHandler == null) {
            return RealTimeLocationErrorCode.RC_REAL_TIME_LOCATION_NOT_INIT;
        }
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "startRealTimeLocation", "Type or id is null!");
            return null;
        }
    
        int code = -1;
        try {
            code = mLibHandler.startRealTimeLocation(conversationType.getValue(), targetId);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return RealTimeLocationErrorCode.valueOf(code);
    }
    
    /**
     * 加入位置共享。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的 targetId。
     */
    public RealTimeLocationErrorCode joinRealTimeLocation(final Conversation.ConversationType conversationType, final String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (mLibHandler == null) {
            return RealTimeLocationErrorCode.RC_REAL_TIME_LOCATION_NOT_INIT;
        }
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "joinRealTimeLocation", "Type or id is null!");
            return null;
        }
    
        int code = -1;
        try {
            code = mLibHandler.joinRealTimeLocation(conversationType.getValue(), targetId);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return RealTimeLocationErrorCode.valueOf(code);
    }
    
    /**
     * 退出位置共享。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的 targetId。
     */
    public void quitRealTimeLocation(final Conversation.ConversationType conversationType, final String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "quitRealTimeLocation", "Type or id is null!");
            return;
        }
    
        mWorkHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mLibHandler == null) {
                    return;
                }
                try {
                    mLibHandler.quitRealTimeLocation(conversationType.getValue(), targetId);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    
    /**
     * 获取参与该位置共享的所有成员。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的 targetId。
     * @return 参与成员 id 列表。
     */
    public List<String> getRealTimeLocationParticipants(Conversation.ConversationType conversationType, String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (mLibHandler == null) {
            return null;
        }
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "getRealTimeLocationParticipants", "Type or id is null!");
            return null;
        }
    
        List<String> list = null;
        try {
            list = mLibHandler.getRealTimeLocationParticipants(conversationType.getValue(), targetId);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return list;
    }
    
    
    /**
     * 获取位置共享状态。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的 targetId。
     * @return 正在进行的位置共享状态。
     */
    public RealTimeLocationStatus getRealTimeLocationCurrentState(Conversation.ConversationType conversationType, String targetId) {
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (mLibHandler == null) {
            return null;
        }
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "getRealTimeLocationCurrentState", "Type or id is null!");
            return null;
        }
    
        int state = 0;
        try {
            state = mLibHandler.getRealTimeLocationCurrentState(conversationType.getValue(), targetId);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    
        return RealTimeLocationStatus.valueOf(state);
    }
    
    /**
     * 添加位置共享观察者。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的 targetId。
     * @param listener         位置共享监听。
     */
    public void addRealTimeLocationListener(final Conversation.ConversationType conversationType, final String targetId, final RealTimeLocationListener listener) {
    
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        if (conversationType == null || targetId == null) {
            RLog.e(this, "addRealTimeLocationListener", "Type or id is null!");
            return;
        }
    
        mWorkHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mLibHandler == null) {
                    return;
                }
                try {
                    mLibHandler.addRealTimeLocationListener(conversationType.getValue(), targetId, new IRealTimeLocationListener.Stub() {
                        @Override
                        public void onStatusChange(final int status) {
                            if (listener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listener.onStatusChange(RealTimeLocationStatus.valueOf(status));
                                    }
                                });
                            }
                        }
    
                        @Override
                        public void onReceiveLocation(final double latitude, final double longitude, final String userId) {
                            if (listener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listener.onReceiveLocation(latitude, longitude, userId);
                                    }
                                });
                            }
                        }
    
                        @Override
                        public void onParticipantsJoin(final String userId) {
                            if (listener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listener.onParticipantsJoin(userId);
                                    }
                                });
                            }
                        }
    
                        @Override
                        public void onParticipantsQuit(final String userId) {
                            if (listener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listener.onParticipantsQuit(userId);
                                    }
                                });
                            }
                        }
    
                        @Override
                        public void onError(final int errorCode) {
                            if (listener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listener.onError(RealTimeLocationErrorCode.valueOf(errorCode));
                                    }
                                });
                            }
                        }
                    });
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    
    /**
     * 使用者调用此方法更新坐标位置。
     *
     * @param conversationType 位置共享的会话类型。
     * @param targetId         位置共享的会话 targetId。
     * @param latitude         维度
     * @param longitude        经度
     */
    public void updateRealTimeLocationStatus(Conversation.ConversationType conversationType,
                                             String targetId,
                                             double latitude,
                                             double longitude) {
    
        if (sS == null)
            throw new RuntimeException("RongIMClient 尚未初始化!");
    
        RLog.d(this, "updateRealTimeLocationStatus", "latitude=" + latitude);
        if (conversationType == null || targetId == null) {
            RLog.e(this, "updateRealTimeLocationStatus", "Type or id is null!");
            return;
        }
    
        if (mLibHandler != null) {
            try {
                mLibHandler.updateRealTimeLocationStatus(conversationType.getValue(), targetId, latitude, longitude);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 实时位置共享监听。
     */
    public interface RealTimeLocationListener {
        /**
         * 位置共享状态发生改变。
         *
         * @param status 空闲、加入、退出、建立连接等状态
         */
        void onStatusChange(RealTimeLocationStatus status);
    
        /**
         * 接收到位置共享信息。
         *
         * @param latitude  维度
         * @param longitude 经度
         * @param userId    发送者 id
         */
        void onReceiveLocation(double latitude, double longitude, String userId);
    
        /**
         * 对方加入位置共享。
         *
         * @param userId 加入者 id
         */
        void onParticipantsJoin(String userId);
    
        /**
         * 对方退出位置共享
         *
         * @param userId 退出者 id
         */
        void onParticipantsQuit(String userId);
    
        /**
         * 位置共享过程出现错误。
         *
         * @param errorCode 错误码
         */
        void onError(RealTimeLocationErrorCode errorCode);
    }
    

    文件消息

    1. 构造文件消息

    可以通过下面的方法生成 FileMessage 对象。

    /**
     * 生成 FileMessage 对象。
     * @param localUrl 文件的本地地址,必须以file://开头。
     * */
    public static FileMessage obtain(Uri localUrl)
    

    例如:

    Uri filePath = Uri.parse("file://" + fileInfo.getFilePath());
    FileMessage fileMessage = FileMessage.obtain(filePath);
    

    2. 文件消息相关功能说明

    发送文件消息

    发送文件消息有下面两种方法:

    • 融云 SDK 中默认上传文件存储有效期为 1 个月,不支持文件迁移,发送成功的回调参数 Message 里会携带服务器的存储地址,您可以通过 FileMessage.getFileUrl() 获得。
    /**
     * <p>发送多媒体消息</p>
     * <p>发送前构造 {@link Message} 消息实体,目前仅支持 FileMessage</p>
     *
     * @param message     发送消息的实体。
     * @param pushContent 当下发 push 消息时,在通知栏里会显示这个字段。
     *                    如果发送的是自定义消息,该字段必须填写,否则无法收到 push 消息。
     *                    如果发送 sdk 中默认的消息类型,例如 RC:TxtMsg, RC:VcMsg, RC:ImgMsg,则不需要填写,默认已经指定。
     * @param pushData    push 附加信息。如果设置该字段,用户在收到 push 消息时,能通过 {@link io.rong.push.notification.PushNotificationMessage#getPushData()} 方法获取。
     * @param callback    发送消息的回调 {@link io.rong.imlib.RongIMClient.SendMediaMessageCallback}。
     */
    public void sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallback callback)
    
    • 将文件上传到您自己的服务器
    /**
     * <p>发送多媒体消息,可以使用该方法将多媒体文件上传到自己的服务器。
     * 使用该方法在上传多媒体文件时,会回调 {@link io.rong.imlib.IRongCallback.ISendMediaMessageCallbackWithUploader#onAttached(Message, IRongCallback.MediaMessageUploader)}
     * 此回调中会携带 {@link IRongCallback.MediaMessageUploader} 对象,
     * 使用者只需要在此回调中实现上传文件到自己的服务器,然后把上传状态通过调用 MediaMessageUploader 中的方法通知 SDK 更新,
     * {@link IRongCallback.MediaMessageUploader#update(int)} 更新进度
     * {@link IRongCallback.MediaMessageUploader#success(Uri)} 更新成功状态,并告知上传成功后的文件地址
     * {@link IRongCallback.MediaMessageUploader#error()} 更新失败状态
     * </p>
     *
     * @param message     发送消息的实体,目前仅支持 {@link io.rong.message.FileMessage}。
     * @param pushContent 当下发 push 消息时,在通知栏里会显示这个字段。
     *                    如果发送的是自定义消息,该字段必须填写,否则无法收到 push 消息。
     *                    如果发送 sdk 中默认的消息类型,例如 RC:TxtMsg, RC:VcMsg, RC:ImgMsg, RC:FileMsg,则不需要填写,默认已经指定。
     * @param pushData    push 附加信息。如果设置该字段,用户在收到 push 消息时,能通过 {@link io.rong.push.notification.PushNotificationMessage#getPushData()} 方法获取。
     * @param callback    发送消息的回调,回调中携带 {@link IRongCallback.MediaMessageUploader} 对象,用户调用该对象中的方法更新状态。
     */
    public void sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallbackWithUploader callback)
    

    下载文件

    /**
     * 下载多媒体消息。
     * 可以调用 cancelDownloadMediaMessage 实现取消下载中的媒体消息,取消下载成功后,通过 IDownloadMediaMessageCallback 中的 onCanceled(Message) 方法通知用户。
     *
     * @param message  文件消息。
     * @param callback 下载文件的回调。
     */
    public void downloadMediaMessage(final Message message, final IRongCallback.IDownloadMediaMessageCallback callback)
    

    取消文件下载

    /**
     * 取消多媒体消息下载。
     * 取消下载成功后,downloadMediaMessage 中的 IDownloadMediaMessageCallback#onCanceled(Message) 方法被回调。
     *
     * @param message  包含多媒体文件的消息。
     * @param callback 取消下载多媒体文件时的回调。
     */
    public void cancelDownloadMediaMessage(final Message message, final OperationCallback callback)
    

    3. 自定义文件保存位置

    下载文件消息,即调用 downloadMediaMessage 后,该文件默认保存在 SD 卡的 /RongCloud/Media/ 下。

    您可以通过更改 SDK 的 res/values/rc_configuration.xml 里面的 rc_media_message_default_save_path 的值,来自定义文件的存储路径。

    1、使用说明

    • 本地消息搜索,在 RongIMClient 中提供接口,使用时直接调用即可;
    • 内置的消息类型中 TextMessage,FileMessage 和 RichContentMessage 这三种消息类型默认实现了 MessageContent#getSearchableWord()方法,可以直接调用消息搜索接口实现搜索;
    • 自定义消息类型,需要在 Message 中自己实现 MessageContent#getSearchableWord() 方法,然后可以调用接口实现自定义消息搜索;

    2、API 说明

    搜索本地消息中符合条件的会话,在回调中返回符合条件的会话列表;

    /**
     * 搜索本地历史消息。
     * 此接口可快速返回匹配的会话列表,并且会话中包含已匹配的消息数量。通过 {@link SearchConversationResult#getMatchCount()} 得到。
     * 如果需要自定义消息也能被搜索到,需要在自定义消息中实现 {@link MessageContent#getSearchableWord()} 方法;
     *
     * @param keyword           搜索的关键字。
     * @param conversationTypes 搜索的会话类型。
     * @param objectNames       搜索的消息类型,例如:RC:TxtMsg。
     * @param resultCallback    搜索结果回调。
     */
    public void searchConversations(final String keyword, final Conversation.ConversationType[] conversationTypes, final String[] objectNames, final RongIMClient.ResultCallback<List<SearchConversationResult>> resultCallback)
    

    搜索本地某个会话中符合条件的消息记录,在回调中返回符合条件的消息列表;

    /**
     * 根据会话,搜索本地历史消息。
     * 搜索结果可分页返回。
     * 如果需要自定义消息也能被搜索到,需要在自定义消息中实现 {@link MessageContent#getSearchableWord()} 方法;
     *这里需要注意一点,如果当count值设置的过大时,比如说超过1000条,可能会有异常;
    *所以避免一次返回消息条数过多,可以一次返回50条,依次去加载搜索记录;
    *
     *
     * @param conversationType 指定的会话类型。
     * @param targetId         指定的会话 id。
     * @param keyword          搜索的关键字。
     * @param count            返回的搜索结果数量, 传0时会返回所有搜索到的消息, 非0时,逐页返回。
     * @param beginTime        查询记录的起始时间, 传0时从最新消息开始搜索。
     * @param resultCallback   搜索结果回调。
     */
    public void searchMessages(final Conversation.ConversationType conversationType, final String targetId, final String keyword, final int count, final long beginTime, final RongIMClient.ResultCallback<List<Message>> resultCallback)
    

    自定义消息时,实现 MessageContent#getSearchableWord()方法,在这个方法中返回想要被搜索的消息体内容;

    /**
    * 下面是以TextMessage为例,content为TextMessage的内容content,将它加入list中然后返回即可;
    */
    @Override
        public List<String> getSearchableWord() {
            List<String> words = new ArrayList<>();
            words.add(content);
            return words;
        }
    

    3、消息搜索示例:

    搜索本地消息中符合条件的会话,在回调中返回符合条件的会话列表;

    RongIMClient.getInstance().searchConversations(mFilterString,
                            new Conversation.ConversationType[] {Conversation.ConversationType.PRIVATE, Conversation.ConversationType.GROUP},
                    new String[] {"RC:TxtMsg", "RC:ImgTextMsg", "RC:FileMsg"}, new RongIMClient.ResultCallback<List<SearchConversationResult>>() {
                        @Override
                        public void onSuccess(List<SearchConversationResult> searchConversationResults) {
                        SearchConversationResult result=searchConversationResults.get(0);
                        Conversation conversation=result.getConversation();
                        int count=result.getMatchCount();
                        }
                        @Override
                        public void onError(RongIMClient.ErrorCode e) {
                        }
                    });
    

    搜索本地某个会话中符合条件的消息记录,在回调中返回符合条件的消息列表:

    /**
    *返回50条符合条件的消息记录,这里需要注意一点,如果当count值设置的过大,比如说超过1000条,可能会有异常;
    *需要,注意这一点,避免一次返回消息条数过多,可以一次返回50条,依次去加载搜索记录;
    *依次去加载的方法是:后一次搜索中把参数beginTime设置成前一次返回结果的最后一条消息的发送时间
    */
    Message mLastMessage;
    RongIMClient.getInstance().searchMessages(conversation.getConversationType(),
                    conversation.getTargetId(), mFilterString, 50, 0, new RongIMClient.ResultCallback<List<Message>>() {
                        @Override
                        public void onSuccess(List<Message> messages) {
                        if(messages!=null&&messages.size()!=0)
                            mLastMessage = messages.get(messages.size() - 1);  
                        }
                        @Override
                        public void onError(RongIMClient.ErrorCode e) {
                       }
                    });
    
    /**
    *接着上边,来加载更多聊天记录
    */
    private void loadMoreChattingRecords() {
            RongIMClient.getInstance().searchMessages(conversation.getConversationType(),
            conversation.getTargetId(), mFilterString, 50, mLastMessage.getSentTime(), new RongIMClient.ResultCallback<List<Message>>() {
                @Override
                public void onSuccess(List<Message> messages) {
    
                }
    
                @Override
                public void onError(RongIMClient.ErrorCode e) {
    
                }
            });
    
        }
    

    删除消息

    /**
     * 删除指定的一条或者一组消息,回调获取删除是否成功。
     *
     * @param messageIds 要删除的消息 Id 数组。
     * @param callback   是否删除成功的回调。
     */
    public void deleteMessages(final int[] messageIds, final ResultCallback<Boolean> callback)
    
    
    /**
     * 根据会话类型,清空某一会话的所有聊天消息记录,回调方式获取清空是否成功。
     *
     * @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param callback         清空是否成功的回调。
     */
    public void clearMessages(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<Boolean> callback)
    

    设置消息的状态

    /**
     * 根据消息 Id,设置接收到的消息状态,回调方式获取设置是否成功。
     *
     * @param messageId      消息 Id。
     * @param receivedStatus 接收到的消息状态。
     * @param callback       是否设置成功的回调。
     */
    public void setMessageReceivedStatus(final int messageId, final Message.ReceivedStatus receivedStatus, final ResultCallback<Boolean> callback)
    
    /**
     * 根据消息 Id,设置发送的消息状态,回调方式获取设置是否成功。
     *
     * @param messageId  消息 Id。
     * @param sentStatus 发送的消息状态。
     * @param callback   是否设置成功的回调。
     */
    public void setMessageSentStatus(final int messageId, final Message.SentStatus sentStatus, final ResultCallback<Boolean> callback)
    

    会话接口

    会话

    会话实体类 Conversation 是本地存储的会话对象,其中主要包含几类属性:通道相关(conversationTypetargetId)、存储和显示相关(isTopunreadMessageCountconversationTitledraft等)、会话中的最后一条消息相关(lastestMessageIdlastestMessagereceivedStatus 等)。

    读取本地存储的会话

    /**
     * 获取会话列表。
     * 此方法会从本地数据库中,读取会话列表。
     * 返回的会话列表按照时间从前往后排列,如果有置顶的会话,则置顶的会话会排列在前面。
     * @param callback 获取会话列表的回调。
     */
    public void getConversationList(final ResultCallback<List<Conversation>> callback)
    
    /**
     * 根据会话类型,回调方式获取会话列表。
     * 此方法会从本地数据库中,读取会话列表。
     * 返回的会话列表按照时间从前往后排列,如果有置顶的会话,则置顶的会话会排列在前面。
     * @param callback          获取会话列表的回调。
     * @param conversationTypes 会话类型列表。
     */
    public void getConversationList(final ResultCallback<List<Conversation>> callback, final Conversation.ConversationType... conversationTypes)
    
    /**
     * 根据不同会话类型的目标 Id,回调方式获取某一会话信息。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param callback         获取会话信息的回调。
     */
    public void getConversation(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<Conversation> callback)
    

    删除本地存储的会话

    /**
     * 根据会话类型列表清空所有会话及会话消息,回调方式通知是否清空成功。
     *
     * @param callback          是否清空成功的回调。
     * @param conversationTypes 会话类型。
     */
    public void clearConversations(final ResultCallback callback, final Conversation.ConversationType... conversationTypes)
    
    /**
     * 从会话列表中移除某一会话,但是不删除会话内的消息。
     * <p/>
     * 如果此会话中有新的消息,该会话将重新在会话列表中显示,并显示最近的历史消息。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param callback         移除会话是否成功的回调。
     */
    public void removeConversation(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<Boolean> callback)
    

    设置置顶

    /**
     * 设置某一会话为置顶或者取消置顶,回调方式获取设置是否成功。
     *
     * @param conversationType 会话类型。
     * @param id               目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param isTop            是否置顶。
     * @param callback         设置置顶或取消置顶是否成功的回调。
     */
    public void setConversationToTop(final Conversation.ConversationType conversationType, final String id, final boolean isTop, final ResultCallback<Boolean> callback)
    

    会话的草稿

    /**
     * 根据会话类型,获取某一会话的文字消息草稿。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param callback         获取草稿文字内容的回调。
     */
    public void getTextMessageDraft(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<String> callback)
    
    /**
     * 保存文字消息草稿,回调方式获取保存是否成功。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param content          草稿的文字内容。
     * @param callback         是否保存成功的回调。
     */
    public void saveTextMessageDraft(Conversation.ConversationType conversationType, String targetId, final String content, final ResultCallback<Boolean> callback)
    
    /**
     * 清除某一会话的文字消息草稿,回调方式获取清除是否成功。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id 或聊天室 Id。
     * @param callback         是否清除成功的回调。
     */
    public void clearTextMessageDraft(Conversation.ConversationType conversationType, String targetId, final ResultCallback<Boolean> callback)
    

    会话的消息提醒状态

    您可以针对会话设置屏蔽消息提醒。

    /**
     * 设置会话消息提醒状态。
     *
     * @param conversationType   会话类型。
     * @param targetId           目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param notificationStatus 是否屏蔽。
     * @param callback           设置状态的回调。
     */
    public void setConversationNotificationStatus(final Conversation.ConversationType conversationType, final String targetId, final Conversation.ConversationNotificationStatus notificationStatus, final ResultCallback<Conversation.ConversationNotificationStatus> callback)
    
    /**
     * 获取会话消息提醒状态。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param callback         获取状态的回调。
     */
    public void getConversationNotificationStatus(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<Conversation.ConversationNotificationStatus> callback)
    

    正在输入的状态提醒

    您可以在用户正在输入的时候,向对方发送正在输入的状态。目前该功能只支持单聊。

    其中,您可以在 typingContentType 中传入消息的类型名,会话中的其他用户输入状态监听中会收到此消息类型。您可以通过此消息类型,自定义不同的输入状态提示(如:正在输入、正在讲话、正在拍摄等)。

    6 秒之内,如果同一个用户在同一个会话中多次调用此接口发送正在输入的状态,为保证产品体验和网络优化,将只有最开始的一次生效。

    /**
     * 向会话中发送正在输入的状态
     *
     * @param conversationType 会话类型
     * @param targetId         会话id
     * @param typingContentType 正在输入的消息的类型名
     * typingContentType为用户当前正在编辑的消息类型名,即message中getObjectName的返回值。
     * 如文本消息,应该传类型名"RC:TxtMsg"。
     * 目前只支持单聊
     */
    public void sendTypingStatus(Conversation.ConversationType conversationType, String targetId, String typingContentType)
    

    开发者可以用下面代码来发送正在输入的状态。

    RongIMClient.getInstance().sendTypingStatus(mCurrentConversation.getConversationType(),
          mCurrentConversation.getTargetId(), objectName);
    

    在接收端,您可以设置输入状态的监听器。

    当前会话正在输入的用户有变化时,会触发监听中的 onTypingStatusChanged(),回调里携带有当前正在输入的用户列表和消息类型。对于单聊而言,当对方正在输入时,监听会触发一次;当对方不处于输入状态时,该监听还会触发一次,但是回调里上来的输入用户列表为空,开发者需要在此时取消正在输入的显示。

    RongIMClient.setTypingStatusListener(new RongIMClient.TypingStatusListener() {
        @Override
        public void onTypingStatusChanged(Conversation.ConversationType type, String targetId, Collection<TypingStatus> typingStatusSet) {
            //当输入状态的会话类型和targetID与当前会话一致时,才需要显示
            if (type.equals(mConversationType) && targetId.equals(mTargetId)) {
                //count表示当前会话中正在输入的用户数量,目前只支持单聊,所以判断大于0就可以给予显示了
                int count = typingStatusSet.size();
                if (count > 0) {
                    Iterator iterator = typingStatusSet.iterator();
                    TypingStatus status = (TypingStatus) iterator.next();
                    String objectName = status.getTypingContentType();
    
                    MessageTag textTag = TextMessage.class.getAnnotation(MessageTag.class);
                    MessageTag voiceTag = VoiceMessage.class.getAnnotation(MessageTag.class);
                    //匹配对方正在输入的是文本消息还是语音消息
                    if (objectName.equals(textTag.value())) {
                        //显示“对方正在输入”
                        mHandler.sendEmptyMessage(SET_TEXT_TYPING_TITLE);
                    } else if (objectName.equals(voiceTag.value())) {
                        //显示"对方正在讲话"
                        mHandler.sendEmptyMessage(SET_VOICE_TYPING_TITLE);
                    }
                } else {
                    //当前会话没有用户正在输入,标题栏仍显示原来标题
                    mHandler.sendEmptyMessage(SET_TARGETID_TITLE);
                }
            }
        }
    });
    

    群组、讨论组 @ 功能

    从 2.6.8 版本开始,群组、讨论组中支持 @ 功能,满足您 @ 指定用户或 @ 所有人的需求。

    1、新增 MentionedInfo 类,说明如下:

    public class MentionedInfo implements Parcelable {
        /*@消息类型,可以@部分人或者@所有人*/
        private MentionedType type;
        /*@消息的@成员列表。如果 MentionedType 为 All, 该 List 传 null 即可。如果 MentionedType 为 PART, 该 List 为需要 @ 的成员列表,不能为 null。*/
        private List<String> userIdList;
        /*@消息的提醒内容。如果填写了该内容,那被 @ 的人在收到该 @ 消息的通知时,通知内容展示为此处填写的内容。如果没有填写,则通知展示为 SDK 默认内容*/
        private String mentionedContent;
    
        public enum MentionedType {
        ALL(1),
        PART(2);
    }
    

    2、MessageContent 里增加了设置 @ 信息的方法:

    void setMentionedInfo(MentionedInfo info)
    

    您可以通过该方法将@信息设置到具体的消息内容里去。如:

    MentionedInfo mentionedInfo = new
    MentionedInfo(MentionedInfo.MentionedType.ALL, null, null);
    textMessage.setMentionedInfo(mentionedInfo);
    

    3、Conversation 类里增加了方法来获取该会话的未读 @ 消息个数。

    /**
     * 获取本会话里自己被@的消息数量。
     * @return 返回该会话里的@消息个数。
     */
    public int getMentionedCount() {
        return mentionedCount;
    }
    

    4、RongIMClient 里新增加获取会话里所有未读 @ 消息的方法。

    /**
     * 获取某会话里未读的@消息。
     *
     * @param conversationType 会话类型。
     * @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
     * @param callback         获取未读@消息的回调。回调里返回的消息列表,按照时间顺序从旧到新。最多返回最近的十条未读 @ 消息。
     * */
    public void getUnreadMentionedMessages(final Conversation.ConversationType conversationType, final String targetId, final ResultCallback<List<Message>> callback)
    
    @ 消息推送会越过所有免打扰逻辑,给用户推送 Push 通知。
    如果是自定义消息,在使用 @ 功能时,发送消息必须设置 pushContent ,否则不会发送 Push 。

    消息撤回

    通过调用 recallMessage 接口来撤回一条已发送的消息,接口定义:

    /**
     * 撤回消息
     *
     * @param message 将被撤回的消息
     * @param callback onSuccess里回调{@link RecallNotificationMessage},IMLib 已经在数据库里将被撤回的消息用{@link RecallNotificationMessage} 替换,
     *                 用户需要在界面上对{@link RecallNotificationMessage} 进行展示。
     */
    public void recallMessage(final Message message, final RongIMClient.ResultCallback<RecallNotificationMessage> callback)
    

    通过下面方法来调用:

    RongIMClient.getInstance().recallMessage(message, new RongIMClient.ResultCallback<RecallNotificationMessage>() {
        @Override
        public void onSuccess(RecallNotificationMessage recallNotificationMessage) {
            //撤回成功的处理,根据 recallNotificationMessage 的内容进行界面刷新
        }
    
        @Override
        public void onError(RongIMClient.ErrorCode errorCode) {
            //撤回失败的处理
        }
    });
    

    其中,RecallNotificationMessage 的结构如下:

    /**
     * 撤回通知消息,当用户撤回消息或者收到一条撤回信令消息时,需要根据此通知消息在界面上进行展示。
     *
     */
    @MessageTag(value = "RC:RcNtf", flag = MessageTag.ISPERSISTED)
    public class RecallNotificationMessage extends MessageContent {
        /**
         * 发起撤回消息的用户id
         */
        public String getOperatorId();
    
        /**
         * 撤回的时间(毫秒)
         */
        public long getRecallTime();
    
        /**
         * 原消息的消息类型名
         */
        public String getOriginalObjectName();
    }
    

    通过 RecallNotificationMessage 的三个 get 方法来获取必要的信息。

    您还需要设置撤回指令的监听器,以便在接收端收到撤回指令时刷新界面。

    /**
     * 撤回消息监听器
     */
    public interface RecallMessageListener {
        /**
         * 撤回消息回调
         * @param messageId 被撤回消息的消息id
         * @param recallNotificationMessage 用于界面展示的{@link RecallNotificationMessage}
         */
        void onMessageRecalled(int messageId, RecallNotificationMessage recallNotificationMessage);
    }
    
    /**
     * 设置撤回消息监听器
     * @param listener 撤回消息监听器
     */
    public static void setRecallMessageListener (final RecallMessageListener listener)
    

    同样,您需要 onMessageRecalled 回调里根据 recallNotificationMessage 的内容来进行界面刷新。

    RongIMClient.setRecallMessageListener(new RongIMClient.RecallMessageListener() {
        @Override
        public void onMessageRecalled(int messageId, RecallNotificationMessage recallNotificationMessage) {
            //根据 recallNotificationMessage 的内容进行界面刷新
        }
    });
    

    消息阅读回执

    单聊消息阅读回执

    您可以在用户查看了单聊会话中的未读消息之后,向会话中发送阅读回执,会话中的用户可以根据此回执,在 UI 中更新消息的显示。

    其中,timestamp 为会话中用户已经阅读的最后一条消息的发送时间戳(MessagesentTime 属性),代表用户已经阅读了该会话中此消息之前的所有消息。

    /**
     * 发送某个会话中消息阅读的回执
     * @param conversationType 会话类型
     * @param targetId 目标会话ID
     * @param timestamp 该会话中已阅读点最后一条消息的发送时间戳
     * 目前只支持单聊, 如果使用Lib可以注册监听 setReadReceiptListener ,使用kit直接设置rc_config.xml 中 rc_read_receipt为true
     */
    public void sendReadReceiptMessage(Conversation.ConversationType conversationType, String targetId, long timestamp)
    

    在接收端,您可以监听已读回执的监听来更新消息的相关显示。

    /**
     * 消息回执监听器
     */
    public interface ReadReceiptListener {
        /**
         * 单聊会话收到消息回执
         * @param message 封装了一个ReadReceiptMessage
         */
        void onReadReceiptReceived (Message message);
    
        /**
         * 群组和讨论组中,某人发起了回执请求,会话中其余人会收到该请求,并回调此方法。
         * 接收方需要在合适的时机(读取了消息之后)调用 {@link RongIMClient#sendReadReceiptResponse(Conversation.ConversationType, String, List, OperationCallback)} 回复响应。
         *
         * @param type           会话类型
         * @param targetId       会话目标 id
         * @param messageUId     请求已读回执的消息 uId
         */
        void onMessageReceiptRequest(Conversation.ConversationType type, String targetId, String messageUId);
    
        /**
         * 在群组和讨论组中发起了回执请求的用户,当收到接收方的响应时,会回调此方法。
         *
         * @param type              会话类型
         * @param targetId          会话 id
         * @param messageUId        收到回执响应的消息的 uId
         * @param respondUserIdList 会话中响应了此消息的用户列表。其中 key: 用户 id ; value: 响应时间
         */
        void onMessageReceiptResponse(Conversation.ConversationType type, String targetId, String messageUId, HashMap<String, Long> respondUserIdList);
    }
    
    /**
    * 设置消息回执监听器
    * @param listener 消息回执监听器
    */
    RongIMClient.setReadReceiptListener(new RongIMClient.ReadReceiptListener() {
       @Override
       public void onReadReceiptReceived(final Message message) {
       }
    
       @Override
       public void onMessageReceiptRequest(Conversation.ConversationType type, String targetId, String messageUId) {
       }
    
       @Override
       public void onMessageReceiptResponse(Conversation.ConversationType type, String targetId, String messageUId, HashMap<String, Long> respondUserIdList) {
       }
    });
    

    onReadReceiptReceived() 回调里,请先判断 message.getConversationType()message.getTargetId() 和当前会话一致,然后在UI里把该会话中发送时间戳之前的所有已发送消息状态置为已读(底层数据库消息状态已经改为已读)。

    void onReadReceiptReceived (Message message) {
        if (mConversation != null && mConversation.getTargetId().equals(message.getTargetId()) && mConversation.getConversationType() == message.getConversationType()) {
    
            ReadReceiptMessage content = (ReadReceiptMessage) message.getContent();
            //获取发送时间戳
            long ntfTime = content.getLastMessageSendTime();
            //自行进行UI处理,把会话中发送时间戳之前的所有已发送消息状态置为已读
            ...
        }
    }
    

    群组、讨论组消息阅读回执

    此功能目前仅在 GROUP 和 DISCUSSION 类型的会话中开放。用户可以对自己发送的消息发起阅读回执请求,发起后可以看到有多少人阅读过这条消息。

    发起阅读回执请求

    /**
     * 发起群组消息回执请求。
     * 只能对自己发送的消息发起消息回执请求。
     *
     * @param message       需要请求回执的那条消息,io.rong.imlib.model.Message对象
     * @param callback      回调函数
     */
    RongIMClient.getInstance().sendReadReceiptRequest(message, new RongIMClient.OperationCallback() {
        @Override
        public void onSuccess() {
        }
    
        @Override
        public void onError(RongIMClient.ErrorCode errorCode) {
            RLog.e(TAG, "sendReadReceiptRequest failed, errorCode = " + errorCode);
        }
    });
    

    响应消息回执请求

    如果在会话中收到了回执请求,接收者需要在合适的时机响应该请求,以通知发送者自己已经阅读了该消息。

    可以一次响应同一会话中的多条消息,调用下面接口:

    /**
     * 发送群消息已读回执
     *
     * @param type          会话类型,Conversation.ConversationType对象
     * @param targetId      会话 id
     * @param messageList   会话中需要发送回执的消息列表,List<io.rong.imlib.model.Message>对象
     * @param callback      回调函数
     */
     RongIMClient.getInstance().sendReadReceiptResponse(type, targetId, messageList, new RongIMClient.OperationCallback() {
         @Override
         public void onSuccess() {
         }
    
         @Override
         public void onError(RongIMClient.ErrorCode errorCode) {
             RLog.e(TAG, "sendReadReceiptResponse failed, errorCode = " + errorCode);
         }
     });
    

    设置消息回执监听

    您需要设置消息回执监听,以此来接收回执消息并更新消息的显示。

    /**
     * 消息回执监听器。
     */
    public interface ReadReceiptListener {
        /**
         * 单聊中收到消息回执的回调。
         *
         * @param message 封装了一个{@link ReadReceiptMessage}
         */
        void onReadReceiptReceived(Message message);
    
        /**
         * 群组和讨论组中,某人发起了回执请求,会话中其余人会收到该请求,并回调此方法。
         * 接收方需要在合适的时机(读取了消息之后)调用 {@link RongIMClient#sendReadReceiptResponse(Conversation.ConversationType, String, List, OperationCallback)} 回复响应。
         *
         * @param type           会话类型
         * @param targetId       会话目标 id
         * @param messageUId     请求已读回执的消息 uId
         */
        void onMessageReceiptRequest(Conversation.ConversationType type, String targetId, String messageUId);
    
        /**
         * 在群组和讨论组中发起了回执请求的用户,当收到接收方的响应时,会回调此方法。
         *
         * @param type              会话类型
         * @param targetId          会话 id
         * @param messageUId        收到回执响应的消息的 uId
         * @param respondUserIdList 会话中响应了此消息的用户列表。其中 key: 用户 id ; value: 响应时间
         */
        void onMessageReceiptResponse(Conversation.ConversationType type, String targetId, String messageUId, HashMap<String, Long> respondUserIdList);
    }
    
    /**
     * 设置消息回执监听器
     * @param listener 消息回执监听器
     */
     RongIMClient.setReadReceiptListener(new RongIMClient.ReadReceiptListener() {
         @Override
         public void onReadReceiptReceived(final Message message) {
         }
    
         @Override
         public void onMessageReceiptRequest(Conversation.ConversationType type, String targetId, String messageUId) {
         }
    
         @Override
         public void onMessageReceiptResponse(Conversation.ConversationType type, String targetId, String messageUId, HashMap<String, Long> respondUserIdList) {
         }
      });
    

    多端阅读消息数同步

    多端登录时,通知其它终端同步某个会话的阅读状态,请调用下面接口:

    /**
     * 多端登录时,通知其它终端清除某个会话的未读消息数
     *
     * @param type      会话类型,Conversation.ConversationType对象
     * @param targetId  目标会话 ID
     * @param timestamp 该会话中已读的最后一条消息的发送时间戳{@link Message#getSentTime()}
     * @param callback  回调函数
     */
    RongIMClient.getInstance().syncConversationReadStatus(type, targetId, timestamp, new RongIMClient.OperationCallback() {
        @Override
        public void onSuccess() {
        }
    
        @Override
        public void onError(RongIMClient.ErrorCode errorCode) {
        }
    });
    

    另外,请设置同步阅读状态监听器。

    /**
     * 同步阅读状态监听
     * 多端登录,收到其它端清除某一会话未读数通知的时候,回调 onSyncMessageReadStatus
     */
    public interface SyncConversationReadStatusListener {
        void onSyncConversationReadStatus(Conversation.ConversationType type, String targetId);
    }
    
    RongIMClient.getInstance().setSyncConversationReadStatusListener(new RongIMClient.SyncConversationReadStatusListener() {
        @Override
        public void onSyncConversationReadStatus(Conversation.ConversationType type, String targetId) {
            //重新获取会话的未读数,并且显示到界面上
        }
    });
    

    黑名单

    您可以将用户加入、移出黑名单,也可以查询当前已经设置的黑名单。

    /**
     * 将某个用户加到黑名单中。
     *
     * @param userId   用户 Id。
     * @param callback 加到黑名单回调。
     */
    public void addToBlacklist(final String userId, final OperationCallback callback)
    
    /**
     * 将个某用户从黑名单中移出。
     *
     * @param userId   用户 Id。
     * @param callback 移除黑名单回调。
     */
    public void removeFromBlacklist(final String userId, final OperationCallback callback)
    
    /**
     * 获取某用户是否在黑名单中。
     *
     * @param userId   用户 Id。
     * @param callback 获取用户是否在黑名单回调。
     */
    public void getBlacklistStatus(final String userId, final ResultCallback<BlacklistStatus> callback)
    
    /**
     * 获取当前用户的黑名单列表。
     *
     * @param callback 获取黑名单回调。
     */
    public void getBlacklist(final GetBlacklistCallback callback)
    

    黑名单针对用户 ID 生效,即使换设备也依然生效。

    讨论组

    讨论组是用户自发创建的多人聊天,在客户端您可以完成讨论组的创建、加入、退出、踢人、设置等。

    /**
     * 创建讨论组。
     *
     * @param name       讨论组名称,如:当前所有成员的名字的组合。
     * @param userIdList 讨论组成员 Id 列表。
     * @param callback   创建讨论组成功后的回调。
     */
    public void createDiscussion(final String name, final List<String> userIdList, final CreateDiscussionCallback callback)
    
    /**
     * 添加一名或者一组用户加入讨论组。
     *
     * @param discussionId 讨论组 Id。
     * @param userIdList   邀请的用户 Id 列表。
     * @param callback     执行操作的回调。
     */
    public void addMemberToDiscussion(final String discussionId, final List<String> userIdList, final OperationCallback callback)
    
    /**
     * 供创建者将某用户移出讨论组。
     * <p/>
     * 移出自己或者调用者非讨论组创建者将产生。
     * {@link RongIMClient.ErrorCode#UNKNOWN}
     * 错误。
     *
     * @param discussionId 讨论组 Id。
     * @param userId       用户 Id。
     * @param callback     执行操作的回调。
     */
    public void removeMemberFromDiscussion(final String discussionId, final String userId, final OperationCallback callback)
    
    /**
     * 退出当前用户所在的某讨论组。
     *
     * @param discussionId 讨论组 Id。
     * @param callback     执行操作的回调。
     */
    public void quitDiscussion(final String discussionId, final OperationCallback callback)
    
    /**
     * 获取讨论组信息和设置。
     *
     * @param discussionId 讨论组 Id。
     * @param callback     获取讨论组的回调。
     */
    public void getDiscussion(final String discussionId, final ResultCallback<Discussion> callback)
    
    /**
     * 设置讨论组名称。
     *
     * @param discussionId 讨论组 Id。
     * @param name         讨论组名称。
     * @param callback     设置讨论组的回调。
     */
    public void setDiscussionName(final String discussionId, final String name, final OperationCallback callback)
    
    /**
     * 设置讨论组成员邀请权限。
     * 讨论组默认开放加人权限,即所有成员都可以加人。
     * 如果关闭加人权限之后,只有讨论组的创建者有加人权限。
     * @param discussionId 讨论组 Id。
     * @param status       邀请状态,默认为开放。
     * @param callback     设置权限的回调。
     */
    public void setDiscussionInviteStatus(final String discussionId, final DiscussionInviteStatus status, final OperationCallback callback)
    

    群组

    群组关系和群组列表由您的 App 维护,客户端的所有群组操作都需要请求您的 App Server , 您的 App Server 可以根据自己的逻辑进行管理和控制,然后通过 Server API 接口进行群组操作,并将结果返回给客户端。

    以下展示了客户端进行群组操作的流程。

    创建群组

    App -> App Server: App 向自己应用服务器发起创建群组请求。 App Server -> RongCloud Server: 授权成功后,在融云服务端同步创建群组。\n /group/create RongCloud Server -> App Server: 创建成功,返回状态。 App Server -> App: 创建成功,可以发送群组信息。

    加入群组

    App -> App Server: App 向自己应用服务器发起加入群组请求。 App Server -> RongCloud Server: 授权成功后,调用融云服务端加入群组接口。\n /group/join RongCloud Server -> App Server: 加入成功,返回状态。 App Server -> App: 加入成功,用户可在群组中发送信息。

    退出群组

    App -> App Server: App 向自己应用服务器发起退出群组请求。 App Server -> RongCloud Server: 授权成功后,调用融云服务端退出群组接口。\n /group/quit RongCloud Server -> App Server: 退出成功,返回状态。 App Server -> App: 退出群组,用户不会再收到此群组信息。

    解散群组

    App -> App Server: App 向自己应用服务器发起解散群组请求。 App Server -> RongCloud Server: 授权成功后,调用融云服务端解散群组接口。\n /group/dismiss RongCloud Server -> App Server: 解散成功,返回状态。 App Server -> App: 成功解散群组。

    设置群组信息

    App -> App Server: App 向自己应用服务器发起设置群组信息请求。 App Server -> RongCloud Server: 授权成功后,调用融云服务端设置群组信息接口。\n /group/refresh RongCloud Server -> App Server: 设置成功,返回状态。 App Server -> App: 群组信息设置成功。

    获取群组成员列表

    App -> App Server: App 向自己应用服务器发起查询群组成员请求。 App Server -> App: 成功,返回成员信息。

    获取群组列表

    App -> RongCloud Server: 连接融云服务器。 connect RongCloud Server -> App: 连接成功,返回状态信息。 App --> App Server: 请求获取群组列表 App Server -> RongCloud Server: 如有变更,需要向融云服务端同步群组信息。 RongCloud Server -> App Server: 成功,返回状态信息。 App Server --> App: 成功,返回群组列表。
    建议在登录成功之后从 App 服务器获取一次群组列表信息,以保证客户端和服务器的群组信息同步,提升用户体验。

    聊天室

    您在客户端可以加入和退出聊天室。

    /**
     * 加入聊天室。
     *
     * @param chatroomId      聊天室 Id。
     * @param defMessageCount 进入聊天室拉取消息数目,为 -1 时不拉取任何消息,默认拉取 10 条消息。
     * @param callback        状态回调。
     */
    public void joinChatRoom(final String chatroomId, final int defMessageCount, final OperationCallback callback)
    
    /**
     * 退出聊天室。
     *
     * @param chatroomId 聊天室 Id。
     * @param callback   状态回调。
     */
    public void quitChatRoom(final String chatroomId, final OperationCallback callback)
    

    客服

    对界面要求较高、有定制要求的用户,可以使用 IMLib 进行集成,IMLib 中客服功能集成使用说明:

    • 在进入到客服聊天界面时,调用 startCustomService() 来启动客服服务。这个方法没有回调,有一个监听 ICustomServiceListener,启动的状态要在监听的回调里面处理,启动成功后会回调 onSuccess ,并携带配置信息 CustomServiceConfig
    • 根据 onModeChanged 的回调来处理不同的键盘输入。在机器人优先模式下,需要在界面上加上转人工的按钮。
    • onQuit 时,离开客服会话或者提示客服服务已经结束。
    • 当用户按下转人工服务时,调用 switchToHumanMode 来切换到人工服务。如果调用成功,onModeChanged 回调返回服务类型。
    • 当离开界面时,调用 stopCustomeService 来结束客服。
    • 在适当的时机对客服进行评价,调用 evaluateCustomService ,根据参数不同评价机器人或者人工。

    RongIMClient 类文件中的客服相关接口:

    #pragma mark - 客服方法
    
    /**
     * <p>启动客服服务</p>
     *
     * @param kefuId            客服 id,用户去融云开发者后台申请开通后获得客服Id
     * @param listener          客服监听,监听客服的状态 {@link io.rong.imlib.ICustomServiceListener}
     * @param customServiceInfo 客服用户信息,包括用户基本信息,用户联系信息以及请求信息
     *                          其中 nickName 不能为空, 如果为空,则上传当前用户userId
     *                          {@link io.rong.imlib.model.CSCustomServiceInfo}
     */
    public void startCustomService(String kefuId, ICustomServiceListener listener, CSCustomServiceInfo customServiceInfo)
    
    /**
     * <p>切换到人工客服模式,切换的结果需要在 {@link ICustomServiceListener#onModeChanged(CustomServiceMode)} 方法回调中处理</p>
     * 如果客服没有分组, 则直接切人工模式;如果客服有分组,那么需要在回调{@link ICustomServiceListener#onSelectGroup(List)}
     * 中去弹出分组选择窗口并选择分组,之后在这个回调中调用 @see{@link RongIMClient#selectCustomServiceGroup(String, String)}
     * 根据客服 Id 和 分组 Id 去切换到人工模式
     * <p>客服模式 分为无服务,机器人模式,人工模式,机器人优先模式,人工优先模式</p>
     *
     * @param kefuId 客服 id,用户去融云开发者后台申请开通后获得客服Id
     *               {@link io.rong.imlib.model.CustomServiceMode}
     */
    public void switchToHumanMode(final String kefuId)
    
    /**
     * 根据客服ID和分组ID转换人工模式
     *
     * @param kefuId  客服ID
     * @param groupId 分组ID
     */
    public void selectCustomServiceGroup(String kefuId, String groupId) {
        sendChangeModelMessage(kefuId, groupId);
    }
    
    
    /**
     * <p>对机器人客服评价,在机器人模式下使用此方法进行评价</p>
     *
     * @param kefuId          客服 id,用户去融云开发者后台申请开通后获得客服Id
     * @param isRobotResolved robot 客服是否解决了您的问题. true 表示解决 ,false 表示未解决
     * @param knowledgeId     机器人评价的消息id,同时满足以下4个条件,此参数有效,其余情况可以传空字符串.
     *                        当参数有效时, 取出4中描述的 “sid” 对应的值就是需要传入的knowledgeId,
     *                        <p>1.机器人模式
     *                        <p>2.新收到的消息,不是从数据库中加载的历史消息
     *                        <p>3.CustomServiceConfig 的 robotSessionNoEva 为true  @see {@link io.rong.imlib.CustomServiceConfig }
     *                        这个CustomServiceConfig 是客服启动成功后的回调onSuccess()带回的参数 @see {@link io.rong.imlib.ICustomServiceListener}
     *                        <p>4.MessageContent 的 Extra 中有“robotEva”和“sid”两个字段.
     */
    public void evaluateCustomService(String kefuId, boolean isRobotResolved, String knowledgeId)
    
    /**
     * <p>对人工客服评价,在人工模式下使用此方法进行评价</p>
     *
     * @param kefuId   客服 id,用户去融云开发者后台申请开通后获得客服Id
     * @param source   星级,范围 1-5,5为最高,1为最低
     * @param suggest  客户的针对人工客服的意见和建议
     * @param dialogId 会话 Id. 客服后台主动拉评价的时候这个参数有效,其余情况传空字符串即可.
     *                 客服主动拉评价的时候, 会走 ICustomServiceListener 的 onPullEvaluation 回调,并带回 dialogId
     * {@link io.rong.imlib.ICustomServiceListener}
     */
    public void evaluateCustomService(String kefuId, int source, String suggest, String dialogId)
    
    /**
     * <p>结束客服. 调用此方法后,将向客服发起结束请求</p>
     *
     * @param kefuId 客服 id,用户去融云开发者后台申请开通后获得客服Id
     */
    public void stopCustomService(String kefuId)
    
    /**
    * 开启客服成功后,回调时携带的配置信息
    */
    public class CustomServiceConfig {
    
      /**
       * 是否被加入黑名单
       */
      public boolean isBlack;
    
      /**
       * 描述
       */
      public String msg;
    
      /**
       * 公司名称
       */
      public String companyName;
    
      /**
       * 公司logo
       */
      public String companyIcon;
    
      /**
       * 机器人是否需要评价会话
       */
      public boolean robotSessionNoEva;
    
      /**
       * 满意度评价列表
       *
       * CSHumanEvaluateItem getValue():取满意度评价值
       * CSHumanEvaluateItem getDescription():取满意度评价描述
       * {@link io.rong.message.CSHumanEvaluateItem}
       */
      public ArrayList<CSHumanEvaluateItem> humanEvaluateList;
    
      public CustomServiceConfig() {
      }
    }
    
    /**
    * 开启客服时,传入监听
    */
    public interface ICustomServiceListener {
      /**
       * 开启客服成功
       *
       * @param config    开启客服成功后,返回的配置信息
       *                  @see {@link io.rong.imlib.CustomServiceConfig}
       */
      void onSuccess(CustomServiceConfig config);
    
      /**
       * 客服开启失败
       *
       * @param code      错误码
       * @param msg       错误描述
       */
      void onError(int code, String msg);
    
      /**
       * 客服模式发生变化
       *
       * @param mode      变化后的客服模式, 以下模式之一 : 无服务,机器人,人工,机器人优先,人工优先
       *                  @see {@link io.rong.imlib.model.CustomServiceMode}
       */
      void onModeChanged(CustomServiceMode mode);
    
      /**
       * 离开客服
       *
       * @param msg 离开描述
       */
      void onQuit(String msg);
    
      /**
       * 客服主动下发满意度评价
       *
       * @param dialogId  会话Id. 客服主动下发评价时,提交评价要带上此参数
       */
      void onPullEvaluation(String dialogId);
    
      /**
       * 客服有分组时,选择分组.
       * 当初始化客服的握手响应消息(@see {@link io.rong.message.CSChangeModeResponseMessage})中带回的分组数据不为空的时候,触发此回调。
       * 需要在此回调中弹出分组选择列表,并选择一个分组。根据选择的groupId调用 {@link RongIMClient#selectCustomServiceGroup(String, String)}转人工模式
       *
       * @param groups 客服的分组列表 @see {@link io.rong.imlib.model.CSGroupItem}
       */
      void onSelectGroup(List<CSGroupItem> groups);
    }
    

    自定义消息

    MessageContent 是消息内容类,是所有消息的基类。您可以继承此类,重写其中的方法,来实现自定义消息。

    1. 继承 MessageContent

    新建自定义消息类,继承MessageContent,如下面示例代码:

    public class CustomizeMessage extends MessageContent {
        private String content;//消息属性,可随意定义
    }
    

    2. 重写和实现方法

    实现 encode() 方法,该方法的功能是将消息属性封装成 json 串,再将 json 串转成 byte 数组,该方法会在发消息时调用,如下面示例代码:

    @Override
    public byte[] encode() {
        JSONObject jsonObj = new JSONObject();
    
        try {
            jsonObj.put("content", "这是一条消息内容");
        } catch (JSONException e) {
            Log.e("JSONException", e.getMessage());
        }
    
        try {
            return jsonObj.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    
        return null;
    }
    

    覆盖父类的 MessageContent(byte[] data) 构造方法,该方法将对收到的消息进行解析,先由 byte 转成 json 字符串,再将 json 中内容取出赋值给消息属性。

    public CustomizeMessage(byte[] data) {
        String jsonStr = null;
    
        try {
            jsonStr = new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
    
        }
    
        try {
            JSONObject jsonObj = new JSONObject(jsonStr);
    
            if (jsonObj.has("content"))
                content = jsonObj.optString("content");
    
        } catch (JSONException e) {
            RLog.e(this, "JSONException", e.getMessage());
        }
    
    }
    

    MessageContent 已实现 Parcelable 接口,下面需要实现 Parcelable 中的方法:

    //给消息赋值。
    public CustomizeMessage(Parcel in) {
        content=ParcelUtils.readFromParcel(in);//该类为工具类,消息属性
        ...
        //这里可继续增加你消息的属性
      }
    
      /**
       * 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。
       */
      public static final Creator<CustomizeMessage> CREATOR = new Creator<CustomizeMessage>() {
    
          @Override
          public CustomizeMessage createFromParcel(Parcel source) {
              return new CustomizeMessage(source);
          }
    
          @Override
          public CustomizeMessage[] newArray(int size) {
              return new CustomizeMessage[size];
          }
      };
    
      /**
       * 描述了包含在 Parcelable 对象排列信息中的特殊对象的类型。
       *
       * @return 一个标志位,表明Parcelable对象特殊对象类型集合的排列。
       */
      public int describeContents() {
          return 0;
      }
    
      /**
       * 将类的数据写入外部提供的 Parcel 中。
       *
       * @param dest  对象被写入的 Parcel。
       * @param flags 对象如何被写入的附加标志。
       */
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          ParcelUtils.writeToParcel(dest, content);//该类为工具类,对消息中属性进行序列化
          ...
          //这里可继续增加你消息的属性
      }
    

    3. 增加注解信息

    注解名:MessageTag ;属性:valueflagvalueObjectName 是消息的唯一标识不可以重复,开发者命名时不能以 RC 开头,避免和融云内置消息冲突;flag 是用来定义消息的可操作状态。

    如下面代码段,自定义消息名称 CustomizeMessagevauleapp:customflagMessageTag.ISCOUNTED | MessageTag.ISPERSISTED 表示消息计数且存库。

    @MessageTag(value = "app:custom", flag = MessageTag.ISCOUNTED | MessageTag.ISPERSISTED)
    public class CustomizeMessage extends MessageContent {
      ...
    }
    

    flag 值如下表:

    枚举值 说明
    MessageTag.NONE 为空值,不表示任何意义,发送的自定义消息不会在会话页面和会话列表中展示。
    MessageTag.ISCOUNTED 表示客户端收到消息后,要进行未读消息计数(未读消息数增加 1),所有内容型消息都应该设置此值。非内容类消息暂不支持消息计数。
    MessageTag.ISPERSISTED 表示客户端收到消息后,要进行存储,并在之后可以通过接口查询,存储后会在会话界面中显示。

    4. 注册自定义消息

    自定义消息应在 init 后注册,只有注册了该消息类型之后,SDK 才能识别和编码、解码该类型的消息,代码如下:

    RongIMClient.registerMessageType(CustomizeMessage.class);
    

    用户信息显示

    用户信息由 UserInfo 类来承载,包含用户 id、昵称、头像三部分。聊天室由于用户流动性大,建议将用户信息写入发送的消息体中。调用 MessageContent 的接口:

    /**
    * 将用户信息写入消息体内
    *
    * @param info 要写入的用户信息。
    */
    public void setUserInfo(UserInfo info);
    

    当收到信息后,通过调用 getUserInfo,得到 UserInfo 类,进而得到发送者的信息。

    /**
    * 获取消息体内的用户信息。
    */
    public UserInfo getUserInfo()
    

    如果您以后需要集成 IMKit 来扩展更多功能,则还需要实现用户信息提供者和群组信息提供者等协议,更多内容可以参考 Android IMKit 开发指南

    连接状态监听

    您可以设置监听连接状态。

    /**
     * 设置连接状态变化的监听器。
     *
     * @param listener 连接状态变化的监听器。
     */
    public static void setConnectionStatusListener(final ConnectionStatusListener listener)
    
    /**
     * 连接状态监听器,以获取连接相关状态。
     */
    public interface ConnectionStatusListener {
    
        /**
         * 连接状态枚举。
         */
        public enum ConnectionStatus {
            /**
             * 网络不可用。
             */
            NETWORK_UNAVAILABLE(-1, "Network is unavailable."),
            /**
             * 连接成功。
             */
            CONNECTED(0, "Connect Success."),
    
            /**
             * 连接中。
             */
            CONNECTING(1, "Connecting"),
    
            /**
             * 断开连接。
             */
            DISCONNECTED(2, "Disconnected"),
    
            /**
             * 用户账户在其他设备登录,本机会被踢掉线。
             */
            KICKED_OFFLINE_BY_OTHER_CLIENT(3, "Login on the other device, and be kicked offline."),
    
            /**
             * Token 不正确。
             */
            TOKEN_INCORRECT(4, "Token incorrect."),
    
            /**
             * 服务器异常或无法连接。
             */
            SERVER_INVALID(5, "Server invalid.");
    
        }
    
        /**
         * 网络状态变化。
         *
         * @param status 网络状态。
         */
        void onChanged(ConnectionStatus status);
    
    
    }
    

    您也可以直接获取当前的聊天状态。

    /**
     * 获取连接状态。
     *
     * @return 连接状态枚举。
     */
    public ConnectionStatusListener.ConnectionStatus getCurrentConnectionStatus()
    

    断开连接

    在断开与融云服务器的连接的时候,您可以设置该客户端是否接收远程推送。 如果设置了接收推送,断开连接之后,如果有人给该用户 ID 发送消息,融云的服务器会将该消息以远程推送的形式下发到客户端。

    我们针对断开连接之后是否接收远程推送,提供了以下接口。

    /**
     * 断开连接(默认断开后接收 Push 消息)。
     */
    public void disconnect()
    
    /**
     * 注销登录(不再接收 Push 消息)。
     */
    public void logout()
    

    RongIMClient 接口说明

    RongIMClient 类是 IMLib 的核心类,您可以通过查看 API 文档了解更多接口的详情。