Custom Message Types (Legacy)
Important
This document applies to SDK versions < 5.6.7. If your SDK version ≧ 5.6.7, we recommend using the new Custom Messages.
In addition to using IMLib SDK's built-in messages, you can also create custom message types. You need to choose the appropriate message base class to inherit from based on your business requirements:
- MessageContent: Regular message content. For example, text messages and location messages among the SDK's built-in message types.
- MediaMessageContent: Media-type messages. Media message content inherits from MessageContent and adds processing logic for multimedia files. When sending and receiving messages, the SDK checks whether the message type is a media message. If it is, the upload or download process for multimedia files will be triggered.
The message type and structure of custom messages must be consistent across all platforms; otherwise, interoperability issues may occur.
Creating Custom Messages
The SDK does not handle the definition and parsing of custom message content; you need to implement this yourself.
The @MessageTag (MessageTag) of a custom message type determines its unique identifier (objectname) and properties such as whether it should be stored, displayed, or counted in unread messages.
-
Create a subclass of MessageContent. For custom media messages, inherit from MediaMessageContent. The following example creates a
MyTextContentclass:@MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
public class MyTextContent extends MessageContent {
private static final String TAG = "MyTextContent";
// Custom message variables; there can be multiple
private String content;
private MyTextContent() {}
/**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}
}Subclasses inheriting from MessageContent or MediaMessageContent must use
@MessageTagto add message annotations. The above is an example of a non-media message, whereMessageTagspecifies the unique identifier of the message type asapp:txtcontent, indicates that the message should be stored in the client database, and should be counted in unread messages.For details on
MessageTagfields and usage, refer to How to Add Message Annotations below. -
Override the parent class's
encodemethod to write the properties ofMyTextContentinto JSON, convert it to a JSON string, and finally encode it into a byte sequence (Bytes array)./**
* Serializes the local message object into message data.
*
* @return The message data.
*/
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
// When the message carries user information, custom messages need to add the following code
if (getJSONUserInfo() != null) {
jsonObj.putOpt("user", getJSONUserInfo());
}
// For group chats, when the message carries @mention information, custom messages need to add the following code
if (getJsonMentionInfo() != null) {
jsonObj.putOpt("mentionedInfo", getJsonMentionInfo());
}
// Serialize all custom message content into the JSON object
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 the parent class's
MessageContent(byte[] data)constructor to parse the custom message content. First, decode the byte sequence (Bytes) into a JSON string, then construct the JSON, and extract the content to assign to the properties ofMyTextContent./** Creates the MyMyTextContent(byte[] data) constructor with byte[] for parsing message content. */
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);
// When the message carries user information, custom messages need to add the following code
if (jsonObj.has("user")) {
setUserInfo(parseJsonToUserInfo(jsonObj.getJSONObject("user")));
}
// For group chats, when the message carries @mention information, custom messages need to add the following code
if (jsonObj.has("mentionedInfo")) {
setMentionedInfo(parseJsonToMentionInfo(jsonObj.getJSONObject("mentionedInfo")));
}
// Parse and assign all custom variables from the received JSON
if (jsonObj.has("content")) {
content = jsonObj.optString("content");
}
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}
} -
Use the
ParcelUtilsutility class to implement serialization and deserialization forMyTextContent.tipThe number and order of reads and writes in the Parcel during serialization must correspond exactly.
/**
* Describes the special object types contained in the Parcelable object's marshaled representation.
*
* @return A bitmask indicating the set of special object types marshaled by the Parcelable.
*/
public int describeContents() {
return 0;
}
/**
* Flatten this object into a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or PARCELABLE_WRITE_RETURN_VALUE.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
ParcelUtils.writeToParcel(dest, getExtra());
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel used to initialize.
*/
public MyTextContent(Parcel in) {
setExtra(ParcelUtils.readFromParcel(in));
setContent(ParcelUtils.readFromParcel(in));
}
You have now successfully created a custom message type. The next steps are:
- Refine the message annotation
@MessageTag. If you need to implement custom processing for message content, you need to create a custommessageHandler. - Register the custom message type so the SDK can recognize and handle this type of message.
How to Add Message Annotations
The SDK's built-in message types already come with MessageTag by default. If you create a custom message type, you must use @MessageTag to add message annotations.
The @MessageTag requires the following parameters:
| Parameter | Type | Description |
|---|---|---|
| value | String | (Required) Unique identifier for the message type, e.g., "app:txtcontent". To minimize impact on message size, it is recommended to keep it under 16 characters. Note: Do not prefix with RC: (reserved for official use) to avoid conflicts with RC's built-in messages. |
| flag | int | (Required) Storage and counting attributes of the message. See Flag Description below for valid values. Note: Both client and server storage behaviors are affected by this attribute. |
| messageHandler | Class<? extends MessageHandler> | (Optional) By default, the SDK uses its default messageHandler. For custom media message types, if the size after encode exceeds 128 KB, you need to use a custom messageHandler. |
-
flagParameter Description:Storage & Counting Attribute Client Storage Server Storage Use Case MessageTag.NONE Not stored on client, not counted in unread messages Supports offline messages? mechanism Messages that do not need to be displayed, e.g., command messages from operations platforms to terminals. MessageTag.ISCOUNTED Stored on client, counted in unread messages Supports offline messages? mechanism and stored in server history --- MessageTag.ISPERSISTED Stored on client, not counted in unread messages Supports offline messages? mechanism and stored in server history --- MessageTag.STATUS Not stored on client, not counted in unread messages Not stored on server Used for real-time status messages (e.g., typing indicators), which cannot support push notifications. tipMessageTag.NONEis typically used for messages that need to be received but not displayed, such as command messages from operations platforms to terminals. If the recipient is offline, they will receive the message via the offline mechanism when they come back online.MessageTag.STATUSis used for status messages. Status messages represent real-time states (e.g., typing indicators). Since status messages are not stored on either the client or server, they cannot be received if the recipient is offline.
-
Code Example
@MessageTag(value = "appx:MyTextContent", flag = MessageTag.ISCOUNTED, )
class MyTextContent extend MessageContent {
}
Custom Media Message Handling
The MessageTag defines messageHandler, which supports custom processing of message content, such as file compression.
-
If
messageHandleris not specified, the SDK will useDefaultMessageHandlerby default. In most cases, you do not need to specifymessageHandler. -
If you want to customize message content processing, you need to create a
messageHandlerthat inherits from MessageHandler. For custom media message types, if the size afterencodeexceeds 128 KB, you must use a custommessageHandler.
The following example shows a custom MyMediaHandler that inherits from MessageHandler. Here, MyMediaMessageContent is the custom media message content.
public class MyMediaHandler extends MessageHandler<MyMediaMessageContent> {
public MyMediaHandler(Context context) {
super(context);
}
/**
* Decodes MessageContent into a Message.
*
* @param message The message entity to store the MessageContent.
* @param content The MessageContent to be decoded.
*/
@Override
public void decodeMessage(Message message, MyMediaMessageContent model) {
}
/**
* Encodes a Message.
*
* @param message The Message entity to be encoded.
*/
@Override
public void encodeMessage(Message message) {
}
}
## Register Custom Messages
You must call [registerMessageType] to register custom message types before establishing an IM connection. Otherwise, the SDK will not recognize these messages and will treat them as `UnknownMessage`.
Example of registering custom messages `MyTextContent.class` and `MyMessage.class`:
```java
ArrayList<Class<? extends MessageContent>> myMessages = new ArrayList<>();
myMessages.add(MyTextContent.class);
myMessages.add(MyMessage.class);
RongIMClient.registerMessageType(myMessages);
| Parameter | Type | Description |
|:------------------------|:-----------------------------------------|:------------------|
| messageContentClassList | List\<Class\<? extends MessageContent\>> | List of custom message classes. |
## Sending Custom Messages
Custom message types can be sent using the same methods as built-in message types.
- If the custom message type inherits `MessageContent`, use the interface for sending regular messages.
- If the custom message type inherits `MediaMessageContent`, use the interface for sending media messages.
If the custom message type needs to support push notifications, you must specify the push content (`pushContent`) when sending the message. The push content is displayed in the notification bar when the recipient receives the push.
- You can directly specify the push content via the `pushContent` parameter when sending the message.
- Alternatively, you can configure the push notification settings (including `pushContent`) in the `MessagePushConfig` of the [Message] object. The `MessagePushConfig` settings take precedence.
For specific methods and configurations for sending messages, refer to the following documentation:
- **Apps with only IMLib SDK**: [Sending Messages][imlib-发送消息] (one-to-one chat, group chat, chatroom), [Sending and Receiving Messages] (ultra group)
- **Apps with IMKit SDK**: [Sending Messages][imkit-发送消息] (one-to-one chat, group chat, chatroom)
:::tip
- If the RC server cannot obtain the `pushContent` of the custom message, push notifications will not be triggered. For example, the recipient will not receive push notifications if they are offline.
- If the custom message type is a **status message** (see [How to Add Message Annotations](#messagetag)), push notifications are not supported, and no push content needs to be specified.
:::
## Code Examples
### Example: Custom Regular Message Type
```java
@MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
public class MyTextContent extends MessageContent {
private static final String TAG = "MyTextContent";
// Custom message variables (can have multiple)
private String content;
private MyTextContent() {}
/**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}
/**
* Constructor.
*
* @param in The Parcel used for initialization.
*/
public MyTextContent(Parcel in) {
setExtra(ParcelUtils.readFromParcel(in));
setContent(ParcelUtils.readFromParcel(in));
}
// Quick message object creation method
public static MyTextContent obtain(String content) {
MyTextContent msg = new MyTextContent();
msg.content = content;
return msg;
}
/** Creates MyTextContent(byte[] data) with a byte[] constructor for parsing message content. */
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 the message carries user info, add the following code for custom messages
if (jsonObj.has("user")) {
setUserInfo(parseJsonToUserInfo(jsonObj.getJSONObject("user")));
}
// For group chats where the message carries @ mentions, add the following code for custom messages
if (jsonObj.has("mentionedInfo")) {
setMentionedInfo(parseJsonToMentionInfo(jsonObj.getJSONObject("mentionedInfo")));
}
// Parse and assign all custom variables from the received JSON
if (jsonObj.has("content")) {
content = jsonObj.optString("content");
}
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}
}
public String getContent() {
return content;
}
/**
* Serializes the local message object into message data.
*
* @return The message data.
*/
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
// If the message carries user info, add the following code for custom messages
if (getJSONUserInfo() != null) {
jsonObj.putOpt("user", getJSONUserInfo());
}
// For group chats where the message carries @ mentions, add the following code for custom messages
if (getJsonMentionInfo() != null) {
jsonObj.putOpt("mentionedInfo", getJsonMentionInfo());
}
// Serialize all custom message content into the JSON object
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) {
// Serialize message attributes, writing the class data to the provided 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];
}
};
}
Example: Custom Media Message Type
Unless you are not using the SDK's built-in upload logic, the localPath property in JSON must have a value.
@MessageTag(value = "app:mediacontent", flag = MessageTag.ISCOUNTED)
public class MyMediaMessageContent extends MediaMessageContent {
/** Reader interface for constructing a Parcelable class instance from a Parcel. */
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());
}
}
/**
* Constructor.
*
* @param in The input Parcel for initialization.
*/
public MyMediaMessageContent(Parcel in) {
setLocalPath(ParcelUtils.readFromParcel(in, Uri.class));
}
public MyMediaMessageContent(Uri localUri) {
setLocalPath(localUri);
}
/**
* Creates a MyMediaMessageContent object.
*
* @param localUri The media file URI.
* @return MyMediaMessageContent object instance.
*/
public static MyMediaMessageContent obtain(Uri localUri) {
return new MyMediaMessageContent(localUri);
}
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
if (getLocalUri() != null) {
/** Unless you are not using the SDK's built-in upload logic, the `localPath` property in JSON must have a value. */
jsonObj.put("localPath", getLocalUri().toString());
}
} catch (JSONException e) {
RLog.e("JSONException", e.getMessage());
}
return jsonObj.toString().getBytes();
}
/**
* Gets the local image URI (file:///).
*
* @return The local image URI (file:///).
*/
public Uri getLocalUri() {
return getLocalPath();
}
/**
* Sets the local image URI (file:///).
*
* @param localUri The local image URI (file:///).
*/
public void setLocalUri(Uri localUri) {
setLocalPath(localUri);
}
/**
* Describes the special object types contained in this Parcelable's marshalled representation.
*
* @return A bitmask indicating the set of special object types.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object into a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written (0 or PARCELABLE_WRITE_RETURN_VALUE).
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
ParcelUtils.writeToParcel(dest, getLocalPath());
}
}