Custom Message Types
Starting from SDK version 5.6.7, the MessageContent.java
class 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 these fields yourself. Custom messages from both old and new versions are compatible and can interoperate. For customers already using the legacy custom message system, if you need to add new custom message types, it is recommended to use the new version of custom messages.
The client SDK uses the [Message] object to represent sent and received messages. The [Message] class encapsulates the [MessageContent] object, which represents the specific content of the message.
The concept of message types is implemented through subclasses of [MessageContent]. The SDK provides default implementations for basic message types such as text, image, voice, video, and file (see [Built-in Message Types]). If the built-in message types do not meet your requirements, you can define custom message types.
To implement a custom message, you must inherit from one of the following abstract classes:
- [MessageContent]: Represents regular message content. For example, text messages and location messages in the SDK's built-in message types.
- [MediaMessageContent]: Represents media-type messages. Media-type message content inherits from [MessageContent] and adds logic for handling multimedia files. When sending and receiving messages, the SDK checks if the message type is a media type. If it is, the SDK triggers the upload or download process for the multimedia file.
The type and structure of custom messages must be consistent across different platforms to avoid interoperability issues.
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 Messages: Regular Message
Below is the complete example code for a custom regular message.
// 1. Custom message implementation with 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 constructor">
private MyTextContent() {}
// Quick method to build 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() {
// Example of carrying "user info" or "@ mention" info
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;
}
/** Create 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);
// Example of carrying "user info" or "@ mention" info
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 attributes, writing the class data into the provided Parcel
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel passed in for initialization.
*/
public MyTextContent(Parcel in) {
// Example of serializing "user info", "@ mention" info, etc.
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">
/**
* Set 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 a custom regular message. Create a subclass of MessageContent
.
-
Custom message types must use the
@MessageTag
annotation.The
@MessageTag
annotation 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, it is recommended to keep it within 16 characters. Note: Do not start withRC:
(reserved prefix for RC), as it will conflict with RC's built-in messages.flag int (Required) The storage and counting properties of the message. See Flag Description below for details. Note: Both client and server storage behaviors are affected by this property. messageHandler Class<? extends MessageHandler>
(Optional) By default, the SDK's default messageHandler
is used. For custom media message types, if the size afterencode
exceeds 128 KB, you need to use a custommessageHandler
.flag
Parameter affects client and server storage behaviors. The details are as follows:flag Value Applicable Scenarios MessageTag.NONE
Such messages are not saved to the client's local message database and do not increment the unread count. Supports offline messages?. Typically used for command messages that do not need to be displayed. For example, a command message from an operations platform to a terminal, notifying the terminal to perform an action. MessageTag.ISPERSISTED
Such messages are saved to the client's local message database but do not increment the unread count. Supports offline messages? and are stored in the server's historical messages. Typically used for gray bar messages that need to be displayed in the UI but do not increment the unread count. MessageTag.ISCOUNTED
Such messages are saved to the client's local message database and increment the unread count. Supports offline messages? and are stored in the server's historical messages. Examples include text and image messages. MessageTag.STATUS
Status messages are not stored on either the client or server and do not increment the unread count. They are only used to convey instant status, such as typing status. The recipient can receive the message if online; if offline, the server discards the message and does not push it. Therefore, if the recipient is offline, they cannot receive the status message again; it will not be resent after reinstallation. -
Add internal variables as needed. The
content
field in the example code is an internal variable of the custom message, used to store the message content.private String content;
-
Implement the public constructor for other classes to call.
// Quick method to build a message object
public static MyTextContent obtain(String content) {
MyTextContent msg = new MyTextContent();
msg.content = content;
return msg;
} -
Implement binary encoding and decoding methods for converting between binary data and message objects. There are two methods (Encode and Decode).
tipThe parent class
MessageContent.java
encapsulates the encoding, decoding, and serialization methods for theuser
(user information),mentionedInfo
(mention information), andextra
(additional information) fields. If your custom message needs to carry this information, you can refer to the example code comments to call the parent class methods for parsing.-
Override the parent class's
encode
method to convert the message object intobyte[]
.Process: Message object => JSONObject => JSON String => byte[]
/**
* Serializes the local message object into message data.
*
* @return The message data.
*/
@Override
public byte[] encode() {
// Call the parent class method to convert basic fields into JSONObject
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 {
// 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 the custom message content.The parsing process (Decode) is the reverse of the encoding process: byte[] => JSON String => JSONObject => Message object
/** Create 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);
// Call the parent class's parseBaseJsonObjec() method to parse basic fields
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());
}
}
-
-
Implement Parcel serialization methods. This set of methods is used for cross-process transmission of message objects. There are four methods in total.
Use the SDK's ParcelUtils utility class to implement the serialization and deserialization of MyTextContent.
Important
- The parent class
MessageContent.java
encapsulates the encoding, decoding, and serialization methods for theuser
(user information),mentionedInfo
(mention information), andextra
(additional information) fields. If your custom message needs to carry this information, you can refer to the example code comments to call the parent class methods for serialization. - The number and order of Parcel reads and writes in serialization must correspond exactly.
/**
* Writes the class data into the provided Parcel.
*
* @param dest The Parcel the object is being written into.
* @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) {
// Write parent class variables
super.writeToBaseInfoParcel(dest);
// Write internal variables
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel passed in for initialization.
*/
public MyTextContent(Parcel in) {
// Read parent class variables
super.readFromBaseInfoParcel(in);
// Read internal variables
setContent(ParcelUtils.readFromParcel(in));
}
/**
* Describes the special objects contained in this Parcelable instance's marshaled representation.
*
* @return A bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
public int describeContents() {
return 0;
}
/** Interface for reading, to construct an instance of a Parcelable class from a 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.
/**
* Set 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;
}
Example Code for Custom Messages: Media Message
Unless you do not use the SDK's built-in upload logic, the localPath
property in the JSON must have a value.
// 1. Custom message implementation with MessageTag annotation
@MessageTag(value = "app:mediacontent", flag = MessageTag.ISCOUNTED)
public class MyMediaMessageContent extends MediaMessageContent {
// <editor-fold desc="* 2. Internal variables, can have multiple, media messages can directly use MediaMessageContent's localPath ">
// </editor-fold>
// <editor-fold desc="* 3. Public constructor">
public MyMediaMessageContent(Uri localUri) {
setLocalPath(localUri);
}
/**
* Generates a MyMediaMessageContent object.
*
* @param localUri The media file address.
* @return An instance of MyMediaMessageContent.
*/
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 of carrying "user info" or "@ mention" info
JSONObject jsonObj = super.getBaseJsonObject();
try {
if (getLocalUri() != null) {
/** Unless you do not use the SDK's built-in upload logic, the `localPath` property in the JSON 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 of carrying "user info" or "@ mention" info
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 into the provided Parcel.
*
* @param dest The Parcel the object is being written into.
* @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) {
// Serialize message attributes, writing the class data into the provided Parcel
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, getLocalPath());
}
/**
* Constructor.
*
* @param in The Parcel passed in for initialization.
*/
public MyMediaMessageContent(Parcel in) {
// Example of serializing "user info", "@