文档导航
Android
SDK 版本:  5.X
公告:融云新文档中心已上线,欢迎到新文档中心阅读 Android IMLibAndroid IMKit 的文档。

自定义消息类型(旧版)

更新时间:2024-02-06 PDF

自定义消息类型(旧版)

重要

本文适用于 SDK < 5.6.7 版本。如果您的 SDK 版本 ≧ 5.6.7,推荐使用新版自定义消息

客户端 SDK 使用 Message 对象表示收发的消息。Message 类中封装了 MessageContent 对象,代表消息的具体内容。

消息类型的概念是通过 MessageContent 的子类实现的,默认实现了文本、图片、语音、视频、文件等基本消息类型(参见内置消息类型)。如果内置消息类型满足不了您的需求,您可以自定义消息类型。

实现自定义消息必须继承自以下抽象类中的一个:

  • MessageContent:即普通类型消息内容。例如在 SDK 内置消息类型中的文本消息和位置消息。
  • MediaMessageContent:即媒体类型消息。媒体类型消息内容继承自 MessageContent,并在其基础上增加了对多媒体文件的处理逻辑。在发送和接收消息时,SDK 会判断消息类型是否为多媒体类型消息,如果是多媒体类型,则会触发上传或下载多媒体文件流程。
提示

自定义消息的类型、消息结构需要确保多端一致,否则将出现无法互通的问题。

创建自定义消息

SDK 不负责定义和解析自定义消息的具体内容,您需要自行实现。

自定义消息类型的 @MessageTagMessageTag) 决定该消息类型的唯一标识(objectname),以及是否存储、是否展示、是否计入消息未读数等属性。

  1. 创建一个 MessageContent 的子类。如果自定义媒体消息,需要继承 MediaMessageContent。以下示例中创建了一个 MyTextContent 类:

    @MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
    public class MyTextContent extends MessageContent {
    
        private static final String TAG = "MyTextContent";
        // 自定义消息变量,可以有多个
        private String content;
    
        private MyTextContent() {}
    
        /**
        * 设置文字消息的内容。
        *
        * @param content 文字消息的内容。
        */
        public void setContent(String content) {
            this.content = content;
        }
    
    }
                  
    已复制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    继承自 MessageContentMediaMessageContent 的子类都必须使用 @MessageTag 添加消息注解。以上是一个非媒体消息的示例,其中的 MessageTag 指定了消息类型的唯一标识为 app:txtcontent、需要在客户端存入数据库、且需要计入未读消息数。

    MessageTag 字段说明与详细用法参见下文如何添加消息注解

  2. 重写父类的 encode 方法,将 MyTextContent 的属性写入 JSON,转为 JSON 字符串,最后编码为字节序列(Bytes 数组)。

        /**
         * 将本地消息对象序列化为消息数据。
         *
         * @return 消息数据。
         */
        @Override
        public byte[] encode() {
            JSONObject jsonObj = new JSONObject();
            try {
                // 消息携带用户信息时, 自定义消息需添加下面代码
                if (getJSONUserInfo() != null) {
                    jsonObj.putOpt("user", getJSONUserInfo());
                }
                // 用于群组聊天, 消息携带 @ 人信息时, 自定义消息需添加下面代码
                if (getJsonMentionInfo() != null) {
                    jsonObj.putOpt("mentionedInfo", getJsonMentionInfo());
                }
                //  将所有自定义消息的内容,都序列化至 json 对象中
                jsonObj.put("content", this.content);
            } catch (JSONException e) {
                Log.e(TAG, "JSONException " + e.getMessage());
            }
    
            try {
                return jsonObj.toString().getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, "UnsupportedEncodingException ", e);
            }
            return null;
        }
                  
    已复制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

  3. 重写父类的 MessageContent(byte[] data) 构造方法,以实现对自定义消息内容的解析。先由字节序列(Bytes)解码成 JSON 字符串,再构造 JSON,并将内容取出赋给 MyTextContent 的属性。

    /** 创建 MyMyTextContent(byte[] data) 带有 byte[] 的构造方法用于解析消息内容. */
    public MyTextContent(byte[] data) {
        if (data == null) {
            return;
        }
        String jsonStr = null;
        try {
            jsonStr = new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "UnsupportedEncodingException ", e);
        }
        if (jsonStr == null) {
            Log.e(TAG, "jsonStr is null ");
            return;
        }
    
        try {
            JSONObject jsonObj = new JSONObject(jsonStr);
            // 消息携带用户信息时, 自定义消息需添加下面代码
            if (jsonObj.has("user")) {
                setUserInfo(parseJsonToUserInfo(jsonObj.getJSONObject("user")));
            }
            // 用于群组聊天, 消息携带 @ 人信息时, 自定义消息需添加下面代码
            if (jsonObj.has("mentionedInfo")) {
                setMentionedInfo(parseJsonToMentionInfo(jsonObj.getJSONObject("mentionedInfo")));
            }
            // 将所有自定义变量从收到的 json 解析并赋值
            if (jsonObj.has("content")) {
                content = jsonObj.optString("content");
            }
        } catch (JSONException e) {
            Log.e(TAG, "JSONException " + e.getMessage());
        }
    }
                  
    已复制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

  4. 使用 ParcelUtils 工具类实现 MyTextContent 的序列化与反序列化。

    提示

    序列化中的 Parcel 的读写个数和顺序一定要一一对应。

       /**
         * 描述了包含在 Parcelable 对象排列信息中的特殊对象的类型。
         *
         * @return 一个标志位,表明 Parcelable 对象特殊对象类型集合的排列。
         */
        public int describeContents() {
            return 0;
        }
    
        /**
         * 将类的数据写入外部提供的 Parcel 中。
         *
         * @param dest 对象被写入的 Parcel。
         * @param flags 对象如何被写入的附加标志,可能是 0 或 PARCELABLE_WRITE_RETURN_VALUE。
         */
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            ParcelUtils.writeToParcel(dest, getExtra());
            ParcelUtils.writeToParcel(dest, content);
        }
    
        /**
         * 构造函数。
         *
         * @param in 初始化传入的 Parcel。
         */
        public MyTextContent(Parcel in) {
            setExtra(ParcelUtils.readFromParcel(in));
            setContent(ParcelUtils.readFromParcel(in));
        }
                  
    已复制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

您已成功创建自定义消息类型。后续步骤如下:

  1. 完善消息注解 @MessageTag。如果需要实现对消息内容的自定义处理,您需要创建自定义的 messageHandler
  2. 注册自定义消息类型,SDK 才能识别并收发该类消息。

如何添加消息注解

SDK 内置消息类型默认已带有 MessageTag。如果创建自定义消息类型,必须使用 @MessageTag 添加消息注解。

@MessageTag 中需要指定以下参数:

参数 类型 描述
value String (必要参数)消息类型唯一标识,例如 "app:txtcontent。为尽量减少对消息体积的影响,建议控制在 16 字符以内。注意,请不要以 RC: 开头(RC: 为官方保留前缀),否则会和融云内置消息冲突。
flag int (必要参数)消息的存储与计数属性。传入值详见下方 Flag 说明注意,客户端与服务端的存储行为均会受该属性的影响。
messageHandler Class<? extends MessageHandler> (可选参数)默认使用 SDK 默认的 messageHandler。在自定义媒体消息类型的情况下,如果 encode 后的大小超过 128 KB,您需要使用自定义的 messageHandler
  • flag 参数说明

    存储计数属性 客户端是否存储 服务端是否存储 适用场景
    MessageTag.NONE 客户端不存储、不计入未读消息数 支持离线消息?机制 不需要展示的消息。例如,运营平台向终端发送的指令消息。
    MessageTag.ISCOUNTED 客户端存储、计入未读消息数 支持离线消息?机制,且存入服务端历史消息 ---
    MessageTag.ISPERSISTED 客户端存储,不计入未读消息数 支持离线消息?机制,且存入服务端历史消息 ---
    MessageTag.STATUS 客户端不存储,不计入未读消息数 服务端不存储 用于传递即时状态的消息,无法支持消息推送。
    提示
    • MessageTag.NONE 一般用于需要确保收到,但不需要展示的消息,例如运营平台向终端发送的指令信息。如果消息接收方不在线,再次上线时可通过离线消息收到。
    • MessageTag.STATUS 用于状态消息。状态消息表示的是即时的状态,例如输入状态。因为状态消息在客户端与服务端均不会存储,如果接收方不在线,则无法再收到该状态消息。
  • 代码示例

    @MessageTag(value = "appx:MyTextContent", flag = MessageTag.ISCOUNTED, )
    class MyTextContent extend MessageContent {
    
    }
                  
    已复制
    1
    2
    3
    4

自定义媒体消息类型的处理方式

MessageTag 中定义了 messageHandler,支持自定义处理消息内容,例如文件的压缩等操作。

  • 如果不指定 messageHandler,SDK 会默认使用 DefaultMessageHandler。一般情况下,您不需要指定 messageHandler
  • 如果希望自定义处理消息内容,您需要创建 messageHandler 并继承 MessageHandler。在自定义媒体消息类型的情况下,如果 encode 后的大小超过 128 KB,您需要使用自定义的 messageHandler

以下示例中自定义的 MyMediaHandler 继承了 MessageHandler。其中 MyMediaMessageContent 为自定义的媒体消息内容。

public class MyMediaHandler extends MessageHandler<MyMediaMessageContent> {

    public MyMediaHandler(Context context) {
        super(context);
    }

    /**
     * 解码 MessageContent 到 Message 中。
     *
     * @param message 用于存放 MessageContent 的消息实体。
     * @param content 将要被解码的 MessageContent。
     */
    @Override
    public void decodeMessage(Message message, MyMediaMessageContent model) {

    }

    /**
     * 对 Message 编码。
     *
     * @param message 将要被编码的 Message 实体。
     */
    @Override
    public void encodeMessage(Message message) {

    }
}
              
已复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

注册自定义消息

您需要在建立 IM 连接之前调用 registerMessageType 注册自定义消息类型,SDK 才能识别该类消息。否则消息将无法识别,SDK 会按照 UnknownMessage 处理。

提示

必须在连接之前注册自定义消息。建议在应用生命周期内调用。

以注册自定义消息 MyTextContent.classMyMessage.class 为例:

ArrayList<Class<? extends MessageContent>> myMessages = new ArrayList<>();
myMessages.add(MyTextContent.class);
myMessages.add(MyMessage.class);
RongIMClient.registerMessageType(myMessages);
              
已复制
1
2
3
4

参数 类型 说明
messageContentClassList List<Class<? extends MessageContent>> 自定义消息的类列表。

发送自定义消息

自定义消息类型可直接使用发送内置消息类型的方法。请注意根据当前使用的 SDK、业务、消息类型选择合适的核心类与方法:

  • 如果自定义消息类型继承 MessageContent,请使用发送普通消息的接口发送。
  • 如果自定义消息类型继承 MediaMessageContent,请使用发送媒体消息的接口发送。

如果自定义消息类型需要支持推送,必须在发送自定义消息时额外指定推送内容(pushContent)。推送内容在接收方收到推送时显示在通知栏中。

  • 在发送消息时,可直接通过 pushContent 参数指定推送内容。
  • 您也可以通过设置 MessageMessagePushConfig 中的 pushContent 及其他字段,对消息的推送进行个性化配置。优先使用 MessagePushConfig 中的配置。

发送消息的具体方法与配置方式,请参考以下文档:

提示
  • 如果融云服务端无法获取自定义消息的 pushContent,则无法触发消息推送。例如,在接收方在离线等情况无法收到消息推送通知。
  • 如果自定义的消息类型为状态消息(见如何添加消息注解),则无法支持推送,不需要额外指定推送内容。

代码示例

示例:自定义普通消息类型

@MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
public class MyTextContent extends MessageContent {

    private static final String TAG = "MyTextContent";
    // 自定义消息变量,可以有多个
    private String content;

    private MyTextContent() {}

    /**
    * 设置文字消息的内容。
    *
    * @param content 文字消息的内容。
    */
    public void setContent(String content) {
        this.content = content;
    }

    /**
        * 构造函数。
        *
        * @param in 初始化传入的 Parcel。
        */
    public MyTextContent(Parcel in) {
        setExtra(ParcelUtils.readFromParcel(in));
        setContent(ParcelUtils.readFromParcel(in));
    }

    // 快速构建消息对象方法
    public static MyTextContent obtain(String content) {
        MyTextContent msg = new MyTextContent();
        msg.content = content;
        return msg;
    }

    /** 创建 MyTextContent(byte[] data) 带有 byte[] 的构造方法用于解析消息内容. */
    public MyTextContent(byte[] data) {
        if (data == null) {
            return;
        }
        String jsonStr = null;
        try {
            jsonStr = new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "UnsupportedEncodingException ", e);
        }
        if (jsonStr == null) {
            Log.e(TAG, "jsonStr is null ");
            return;
        }

        try {
            JSONObject jsonObj = new JSONObject(jsonStr);
            // 消息携带用户信息时, 自定义消息需添加下面代码
            if (jsonObj.has("user")) {
                setUserInfo(parseJsonToUserInfo(jsonObj.getJSONObject("user")));
            }
            // 用于群组聊天, 消息携带 @ 人信息时, 自定义消息需添加下面代码
            if (jsonObj.has("mentionedInfo")) {
                setMentionedInfo(parseJsonToMentionInfo(jsonObj.getJSONObject("mentionedInfo")));
            }
            // 将所有自定义变量从收到的 json 解析并赋值
            if (jsonObj.has("content")) {
                content = jsonObj.optString("content");
            }
        } catch (JSONException e) {
            Log.e(TAG, "JSONException " + e.getMessage());
        }
    }

    public String getContent() {
        return content;
    }

    /**
     * 将本地消息对象序列化为消息数据。
     *
     * @return 消息数据。
     */
    @Override
    public byte[] encode() {
        JSONObject jsonObj = new JSONObject();
        try {
            // 消息携带用户信息时, 自定义消息需添加下面代码
            if (getJSONUserInfo() != null) {
                jsonObj.putOpt("user", getJSONUserInfo());
            }
            // 用于群组聊天, 消息携带 @ 人信息时, 自定义消息需添加下面代码
            if (getJsonMentionInfo() != null) {
                jsonObj.putOpt("mentionedInfo", getJsonMentionInfo());
            }
            //  将所有自定义消息的内容,都序列化至 json 对象中
            jsonObj.put("content", this.content);
        } catch (JSONException e) {
            Log.e(TAG, "JSONException " + e.getMessage());
        }

        try {
            return jsonObj.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "UnsupportedEncodingException ", e);
        }
        return null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int i) {
        // 对消息属性进行序列化,将类的数据写入外部提供的 Parcel 中
        ParcelUtils.writeToParcel(dest, getExtra());
        ParcelUtils.writeToParcel(dest, content);
    }

    public static final Creator<MyTextContent> CREATOR =
            new Creator<MyTextContent>() {
                public MyTextContent createFromParcel(Parcel source) {
                    return new MyTextContent(source);
                }

                public MyTextContent[] newArray(int size) {
                    return new MyTextContent[size];
                }
            };
}
              
已复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

示例:自定义媒体消息类型

提示

除非不使用 SDK 内置上传逻辑,否则 JSON 的 localPath 属性必须有值。

@MessageTag(value = "app:mediacontent", flag = MessageTag.ISCOUNTED)
public class MyMediaMessageContent extends MediaMessageContent {

    /** 读取接口,目的是要从 Parcel 中构造一个实现了 Parcelable 的类的实例处理。 */
    public static final Creator<MyMedia> CREATOR =
            new Creator<MyMediaMessage>() {

                @Override
                public MyMediaMessageContent createFromParcel(Parcel source) {
                    return new MyMediaMessageContent(source);
                }

                @Override
                public MyMediaMessageContent[] newArray(int size) {
                    return new MyMediaMessageContent[size];
                }
            };


    public MyMediaMessageContent(byte[] data) {
        String jsonStr = new String(data);

        try {
            JSONObject jsonObj = new JSONObject(jsonStr);

            if (jsonObj.has("localPath")) {
                setLocalPath(Uri.parse(jsonObj.optString("localPath")));
            }

        } catch (JSONException e) {
            Log.e("JSONException", e.getMessage());
        }
    }


    /**
     * 构造函数。
     *
     * @param in 初始化传入的 Parcel。
     */
    public MyMediaMessageContent(Parcel in) {
        setLocalPath(ParcelUtils.readFromParcel(in, Uri.class));
    }


    public MyMediaMessageContent(Uri localUri) {
        setLocalPath(localUri);
    }

    /**
     * 生成 MyMediaMessageContent 对象。
     *
     * @param localUri 媒体文件地址。
     * @return MyMediaMessageContent 对象实例。
     */
    public static MyMediaMessageContent obtain(Uri localUri) {
        return new MyMediaMessageContent(localUri);
    }

    @Override
    public byte[] encode() {
        JSONObject jsonObj = new JSONObject();

        try {

            if (getLocalUri() != null) {
                /** 除非不使用 SDK 内置上传逻辑,否则 JSON 的 `localPath` 属性必须有值。 */
                jsonObj.put("localPath", getLocalUri().toString());
            }
        } catch (JSONException e) {
            RLog.e("JSONException", e.getMessage());
        }
        return jsonObj.toString().getBytes();
    }

    /**
     * 获取本地图片地址(file:///)。
     *
     * @return 本地图片地址(file:///)。
     */
    public Uri getLocalUri() {
        return getLocalPath();
    }

    /**
     * 设置本地图片地址(file:///)。
     *
     * @param localUri 本地图片地址(file:///).
     */
    public void setLocalUri(Uri localUri) {
        setLocalPath(localUri);
    }

    /**
     * 描述了包含在 Parcelable 对象排列信息中的特殊对象的类型。
     *
     * @return 一个标志位,表明Parcelable对象特殊对象类型集合的排列。
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 将类的数据写入外部提供的 Parcel 中。
     *
     * @param dest 对象被写入的 Parcel。
     * @param flags 对象如何被写入的附加标志,可能是 0 或 PARCELABLE_WRITE_RETURN_VALUE。
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        ParcelUtils.writeToParcel(dest, getLocalPath());
    }
}
              
已复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

文档反馈
意见反馈

您的改进建议

意见反馈

问题类型

联系我们

提交工单

技术支持|集成使用|产品方案


商务咨询

7 x 24 小时

为您解答方案与报价问题

131 6185 6839

文档反馈