Custom Message Types
Important
This document applies to SDK versions 5.6.7 and later. If your SDK version is < 5.6.7, please use Custom Messages (Legacy).
Starting from SDK version 5.6.7, MessageContent.java encapsulates the encoding/decoding and serialization methods for the user (user information), mentionedInfo (@mention information), and extra (additional information) fields. When implementing custom messages, you can directly call the parent class's parsing methods without needing to parse them yourself. The new and legacy versions of custom messages are compatible and can interoperate. For customers already using legacy custom messages who need to add new custom message types, we recommend using the new version of custom messages.
In addition to using the built-in message types in the IMLib SDK, you can also create custom message types.
You need to choose the message base class to inherit from based on your business requirements:
- [MessageContent]: This is the base class for ordinary message content. For example, text messages and location messages in the SDK's built-in message types.
- [MediaMessageContent]: This is the base class for media-type messages. Media-type message content inherits from [MessageContent] and adds processing logic for multimedia files. When sending and receiving messages, the SDK will check whether the message type is a media-type message. If it is, the upload or download process for multimedia files will be triggered.
For more information about message entity classes and message content, see Message Introduction.
The type and structure of custom messages must be consistent across all clients; 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 is stored, displayed, or counted in the unread message count.
Example Code for Custom Ordinary Messages
Below is the complete example code for a custom ordinary message.
// 1. Custom message implementing the MessageTag annotation
@MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
public class MyTextContent extends MessageContent {
// <editor-fold desc="* 2. Internal variables, can have multiple">
private static final String TAG = "appTextContent";
private String content;
// </editor-fold>
// <editor-fold desc="* 3. Public constructors">
private MyTextContent() {}
// Quick method to construct a message object
public static MyTextContent obtain(String content) {
MyTextContent msg = new MyTextContent();
msg.content = content;
return msg;
}
// </editor-fold>
// <editor-fold desc="* 4. Binary Encode & decode methods">
/**
* Serializes the local message object into message data.
*
* @return The message data.
*/
@Override
public byte[] encode() {
// Here, we take the example of needing to carry "user information" or "@mention" information
JSONObject jsonObj = super.getBaseJsonObject();
try {
// 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;
}
/** Creates a MyTextContent(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);
// Here, we take the example of needing to parse "user information" or "@mention" information
super.parseBaseJsonObject(jsonObj);
// 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());
}
}
// </editor-fold>
// <editor-fold desc="* 5. Parcel serialization methods">
@Override
public void writeToParcel(Parcel dest, int i) {
// Serialize message properties, writing the class's data into the externally provided Parcel
// Here, we take the example of needing to serialize "user information" and "@mention information"
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel passed in during initialization.
*/
public MyTextContent(Parcel in) {
// Here, we take the example of needing to deserialize "user information" and "@mention information"
super.readFromBaseInfoParcel(in);
setContent(ParcelUtils.readFromParcel(in));
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
// </editor-fold>
// <editor-fold desc="* 6. get & set methods">
/**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
// </editor-fold>
}
Detailed Explanation of Example Code
The following explains the example code for custom ordinary messages. Create a subclass of MessageContent.
-
For custom message types, you must use the
@MessageTagannotation.The
@MessageTagannotation requires the following parameters:Parameter Type Description value String (Required) The unique identifier for the message type, e.g., app:txtcontent. To minimize the impact on message size, we recommend keeping it under 16 characters. Note: Do not start withRC:(reserved prefix for official messages), as this will conflict with RC's built-in messages.flag int (Required) The storage and counting properties of the message. For possible values, see Flag Explanation below. Note: Both client and server storage behaviors are affected by this property. messageHandler Class<? extends MessageHandler>(Optional) By default, the SDK's default messageHandleris used. For custom media message types, if the size afterencodeexceeds 128 KB, you need to use a custommessageHandler.The
flagparameter affects client and server storage behaviors, as explained below:
| Flag Value | Use Case |
|---|---|
MessageTag.NONE | Messages of this type are neither saved to the client's local message database nor counted as unread. Supports offline messages?. Typically used for command messages that don't require display, such as operational platform instructions to client devices. |
MessageTag.ISPERSISTED | Messages of this type are saved to the client's local message database but not counted as unread. Supports offline messages? and stored in server history. Commonly used for gray bar notifications that require UI display without incrementing unread counts. |
MessageTag.ISCOUNTED | Messages of this type are saved to the client's local message database and increment unread counts. Supports offline messages? and stored in server history. Examples include text and image messages. |
MessageTag.STATUS | Status messages are neither stored on client nor server, nor counted as unread. Used exclusively for real-time status updates (e.g., typing indicators). Recipients online will receive these messages; offline recipients will have them discarded by the server without push. Thus, offline recipients cannot receive status messages later, nor will they be re-delivered after app reinstall. |
-
Add internal variables as needed (multiple allowed). The
contentfield in the sample code serves as an internal variable for storing message content.private String content; -
Implement public constructors for external class invocation.
// Quick message object construction method
public static MyTextContent obtain(String content) {
MyTextContent msg = new MyTextContent();
msg.content = content;
return msg;
} -
Implement binary encoding/decoding methods for converting between binary data and message objects (two methods: Encode and Decode).
tipThe parent class
MessageContent.javaencapsulates encoding/decoding and serialization methods foruser(user info),mentionedInfo(mention info), andextra(additional info) fields. For custom messages needing these fields, reference the sample code comments to invoke parent class methods.-
Override the parent class's
encodemethod to convert message objects tobyte[].Process: Message object => JSONObject => JSON String => byte[]
/**
* Serializes local message object into message data.
*
* @return Message data.
*/
@Override
public byte[] encode() {
// Invoke parent method to convert base fields to JSONObject
JSONObject jsonObj = super.getBaseJsonObject();
try {
// Serialize all custom message variables into JSON object
jsonObj.put("content", this.content);
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}
try {
// Convert jsonObj to binary
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 custom message content.Decoding process is the inverse of encoding: byte[] => JSON String => JSONObject => Message object
/** Constructor MyMyTextContent(byte[] data) for parsing message content from 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);
// Must invoke parent's parseBaseJsonObjec() to parse base fields
super.parseBaseJsonObject(jsonObj);
// Parse and assign all custom variables from JSON
if (jsonObj.has("content")) {
content = jsonObj.optString("content");
}
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}
}
-
-
Implement Parcel serialization methods (four methods total) for cross-process message object transmission.
Use SDK's ParcelUtils to implement MyTextContent serialization/deserialization.
Important
- The parent class
MessageContent.javaencapsulates serialization methods foruser,mentionedInfo, andextrafields. Reference sample code comments to invoke parent methods if needed. - Parcel read/write operations must maintain strict order and quantity matching.
/**
* Writes class data to external Parcel.
*
* @param dest Parcel receiving object data.
* @param flags Additional flags for writing (0 or PARCELABLE_WRITE_RETURN_VALUE).
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// Write parent variables
super.writeToBaseInfoParcel(dest);
// Write internal variables
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in Initialization Parcel.
*/
public MyTextContent(Parcel in) {
// Read parent variables
super.readFromBaseInfoParcel(in);
// Read internal variables
setContent(ParcelUtils.readFromParcel(in));
}
/**
* Describes special object types in Parcelable's object representation.
*
* @return Bitmask indicating special object type set.
*/
public int describeContents() {
return 0;
}
/** Interface for constructing Parcelable class instances from Parcel. */
public static final Creator<MyTextContent> CREATOR =
new Creator<MyTextContent>() {
@Override
public TextMessage createFromParcel(Parcel source) {
return new MyTextContent(source);
}
@Override
public TextMessage[] newArray(int size) {
return new MyTextContent[size];
}
}; - The parent class
-
Implement Getter and Setter methods for custom internal variables.
/**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
Custom Message Example Code: Media Message
Unless you're not using the SDK's built-in upload logic, the JSON's localPath property must have a value.
// 1. Custom message implements MessageTag annotation
@MessageTag(value = "app:mediacontent", flag = MessageTag.ISCOUNTED)
public class MyMediaMessageContent extends MediaMessageContent {
// <editor-fold desc="* 2. Internal variables (multiple allowed). Media messages can directly use MediaMessageContent's localPath">
// </editor-fold>
// <editor-fold desc="* 3. Public constructors">
public MyMediaMessageContent(Uri localUri) {
setLocalPath(localUri);
}
/**
* Creates a MyMediaMessageContent object.
*
* @param localUri Media file URI.
* @return MyMediaMessageContent object instance.
*/
public static MyMediaMessageContent obtain(Uri localUri) {
return new MyMediaMessageContent(localUri);
}
// </editor-fold>
// <editor-fold desc="* 4. Binary Encode & decode methods">
@Override
public byte[] encode() {
//Example showing how to carry "user info" or "@ mentions"
JSONObject jsonObj = super.getBaseJsonObject();
try {
if (getLocalUri() != null) {
/** Unless not using SDK's built-in upload logic, JSON's `localPath` property must have a value. */
jsonObj.put("localPath", getLocalUri().toString());
}
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
return jsonObj.toString().getBytes();
}
public MyMediaMessageContent(byte[] data) {
String jsonStr = new String(data);
try {
JSONObject jsonObj = new JSONObject(jsonStr);
//Example showing how to carry "user info" or "@ mentions"
super.parseBaseJsonObject(jsonObj);
if (jsonObj.has("localPath")) {
setLocalPath(Uri.parse(jsonObj.optString("localPath")));
}
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
}
// </editor-fold>
// <editor-fold desc="* 5. Parcel serialization methods">
/**
* Writes the class data to the provided Parcel.
*
* @param dest The Parcel where 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) {
// Serializes message attributes into the Parcel (example showing "user info" and "@ mentions")
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, getLocalPath());
}
/**
* Constructor.
*
* @param in The input Parcel for initialization.
*/
public MyMediaMessageContent(Parcel in) {
// Example showing serialization of "user info" and "@ mentions"
super.readFromBaseInfoParcel(in);
setLocalPath(ParcelUtils.readFromParcel(in, Uri.class));
}
/** Reader interface for constructing Parcelable class instances from Parcel. */
public static final Creator<MyMediaMessageContent> CREATOR =
new Creator<MyMediaMessageContent>() {
@Override
public MyMediaMessageContent createFromParcel(Parcel source) {
return new MyMediaMessageContent(source);
}
@Override
public MyMediaMessageContent[] newArray(int size) {
return new MyMediaMessageContent[size];
}
};
/**
* Describes special object types contained in this Parcelable instance.
*
* @return A bitmask indicating special object types.
*/
@Override
public int describeContents() {
return 0;
}
// </editor-fold>
// <editor-fold desc="* 6. Getter & setter methods">
/**
* 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);
}
// </editor-fold>
}
Registering Custom Messages
You need to call [registerMessageType] to register custom message types before establishing an IM connection, so the SDK can recognize these message types. Otherwise, messages will be treated as UnknownMessage.
Custom messages must be registered before connection. Recommended to call during the application lifecycle.
Example for registering custom messages MyTextContent.class and MyMessage.class:
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. Please select the appropriate core class and method based on your SDK version, business needs, and message type:
-
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 push content (pushContent) when sending the message. This content will be displayed in the notification bar when the recipient receives the push.
-
You can directly specify push content through the
pushContentparameter when sending messages. -
Alternatively, you can configure personalized push settings by setting
pushContentand other fields in [Message]'sMessagePushConfig. TheMessagePushConfigsettings take precedence.
For specific methods and configurations for sending messages, please refer to the following documentation:
-
App with IMLib SDK only: [Send Messages][imlib-发送消息] (one-to-one chat, group chat, chatroom), [Send and Receive Messages] (ultra group)
-
App with IMKit SDK: [Send Messages][imkit-发送消息] (one-to-one chat, group chat, chatroom)
- If RC server cannot obtain the custom message's
pushContent, message push cannot be triggered. For example, the recipient will not receive push notifications when offline. - If the custom message type is a status message, push notifications are not supported and no additional push content needs to be specified.