1, the introduction
This paper mainly takes Android client as an example to record the design idea of using self-developed IM in Youzan’s App and refining IM into component-based SDK. This work is completed by the IM SDK team of Youzan Mobile development group.
3. Design objectives
The design objectives of this IM componentized SDK are as follows:
- 1) IM main process is stable and available: message transmission has high reliability;
- 2) UI components are directly integrated into SDK and can be customized;
- 3) Rich media sending is integrated into SDK, and the required rich media types can be customized as required;
- 4) Realize the message transfer layer SDK, which is separated from the function of SDK with UI. Business callers can use the message transfer SDK to process messages and then process UI by themselves, or use the SDK with UI components to realize relatively complete IM functions in one step.
4. Overall structure
The following figure briefly describes the basic structure of the IM system in the like client:
As shown in the figure above, responsibilities for each layer are divided as follows:
- 1) Message channel layer: Long Socket connections are maintained as message channels, and message sending and receiving processes are mainly completed in this layer;
- 2) Persistence layer: messages are mainly stored in the database, and rich media files are stored in the file cache, so that when messages are displayed for the second time, they are loaded locally rather than obtained from the network layer;
- 3) Logic processing layer: complete all kinds of message-related logic processing, such as sorting, pre-processing of rich media files, etc.;
- 4) UI display layer: data is presented on UI.
5. Design points 1: Create and maintain long Socket connections
All data sending and receiving processes of IM SDK are completed through Socket long connection. How to maintain a stable Socket channel is an important part of whether THE IM system is stable.
The following describes several important processes of Socket channels.
As shown in figure on, when IM SDK initialization, business call connection request interface, will begin to connect the creation process, created after the success, will complete the authentication operation, when creating and authentication is completed, will open messaging thread, in order to maintain a long connection, there will be a heartbeat, in particular, the polling thread will open a heartbeat.
Heartbeat mechanism is a common concept in IM system design. The simple explanation is that the server sends a fixed message to the server at intervals, and the server replies a fixed message in time after receiving it. If the server does not receive the heartbeat message from the client within a certain period of time, the client is disconnected. Similarly, if the client does not receive the heartbeat return value from the server for a certain period of time, the server is disconnected.
After a long connection is successfully created, a polling thread is started and heartbeat messages are sent to the server periodically to maintain the long connection.
For a special article on IM heartbeat, see:
“Hand to hand teach you to use Netty to realize the heartbeat mechanism of network communication program, disconnection reconnection mechanism”
If the reconnection is triggered, exit the reconnection if the connection succeeds. Otherwise, if the reconnection fails, it will judge whether the current reconnection times exceed the expected value (set as 6 times here) and count the reconnection times. If the number of reconnection times exceeds the expected value, it will quit the reconnection; otherwise, it will start the reconnection operation again after sleeping for the preset time.
There are three triggering conditions for reconnection:
- A. The active connection fails (the Socket is actively connected. If the connection fails, the reconnection mechanism is triggered).
- B. The network is disconnected actively (the network is disconnected during operation after normal connection is established, and reconnection is triggered through system broadcast);
- C. The server does not respond and has no heartbeat response. (If the server has no heartbeat response within the preset period, the client considers that the server is disconnected and triggers reconnection.)
For an in-depth study of the reconnection mechanism, read the following two articles:
- “Correctly understand the IM long connection heartbeat and reconnection mechanism, and start to implement (complete IM source code)”
- “Hand to hand teach you to use Netty to realize the heartbeat mechanism of network communication program, disconnection reconnection mechanism”
The TCP API does not provide a reliable way to determine the current state of a long connection. IsConnected () and isClosed() only tell you the current state of the Socket. IsConnected () tells you whether the Socket remains connected to the Romote host, and isClosed() tells you whether the Socket isClosed.
If you determine whether a long connection channel is closed, you can do so only by using the following methods related to stream operations:
- A. Read () return -1;
- B. ReadLine () return null;
- C. ReadXXX () throw EOPException for any other XXX;
- D. write will throw IOException: Broken pipe(channel closed).
So when the SDK encapsulates the isConnected (method, the current channel state is determined based on these conditions, rather than just through socket.isConnected () or socket.isclosed ().
6. Design Point 2: message sending process
There are two main types of message sending processes:
1) Requests for IM-related data, such as historical message list, session list, etc.
2) The other is the sending of IM messages, mainly text messages.
(In rich media message sending, the system uploads the rich media file to the server, obtains the URL of the file, and sends the URL to the receiver through a text message. The receiver downloads the URL and displays the UI.)
Both types of messages are sent using the process shown in the figure above, and the result of the request can be sensed through the send callback.
As shown in the figure above, the message sending process needs to encapsulate the message request and store the request ID and corresponding callback into the local Map data structure before sending it to the server through the send queue.
if(requestCallBack ! = null) {
mCallBackMap.put(requestId, requestCallBack);
}
It then receives a server push message with the request ID at the time the request was sent, finds the callback corresponding to the request ID in the local Map data, and returns the data pushed by the server through the callback.
The request can specify the return value type through generics. The SDK will parse the data returned by the server data and return it directly to the business caller Model object for easy use. (Currently supports data parsing in JSON format)
private void IMResponseOnSuccess(String requestid, String response) {
if(mCallBackMap ! = null) {
IMCallBack callBack = mCallBackMap.get(requestid);
if(callBack == null) {
return;
}
if(callBack instanceofJsonResultCallback) {
finalJsonResultCallback resultCallback = (JsonResultCallback) callBack;
if(resultCallback.mType == String.class) {
callBack.onResponse(response);
} else{
Object object = newGson().fromJson(response, resultCallback.mType);
callBack.onResponse(object);
}
removeCallBack(requestid);
}
}
}
The following example shows a request to get a session list. It can be seen that the current request encapsulation is similar to some third-party network libraries, which are easier to use.
RequestApi requestApi = new RequestApi(IMConstant.REQ_TYPE_GET_CONVERSATION_LIST, Enums Manager.IMType.IM_TYPE_WSC.getRequestChannel());
requestApi.addRequestParams(“limit”, 100);
requestApi.addRequestParams(“offset”, 0);
IMEngine.getInstance().request(requestApi, newJsonResultCallback<List<ConversationEntity>>() {
@Override
publicvoidonResponse(List<ConversationEntity> response) {
mSwipeRefreshLayout.setRefreshing(false);
mAdapter.mDataset.clear();
mAdapter.mDataset.addAll(response);
mAdapter.notifyDataSetChanged();
}
@Override
publicvoidonError(intstatusCode) {
//do something
}
});
As you can see, the request directly returns a List collection of session types that the business can use directly.
7. Design Point 3: message receiving process
The message listening process mainly uses a global listening mode, which needs to register the jian listener, which has the default callback.
public interface IMListener {
/ * *
* Connection successful
* /
void connectSuccess();
/ * *
* Failed to connect
* /
void connectFailure(EnumsManager.DisconnectType type);
/ * *
* Authentication succeeds
* /
void authorSuccess();
/ * *
* Authentication failed
* /
void authorFailure();
/ * *
* Data was received successfully
* /
void receiveSuccess(int reqType, String msgId, String requestChannel, String message, int statusCode);
/ * *
* Failed to receive data
* /
void receiveError(int reqType, String msgId, String requestChannel, int statusCode);
}
The jian listener can receive the following types of messages:
- 1) Return result of Socket connection status;
- 2) the return result of the jian status, (the jian process needs to like the business);
- 3) Received IM messages or other types of return messages. Subsequent distribution processing can be performed based on the message type.
To use the global jian listener, you need to implement the interface and register the global jian listener during service initialization. According to the registered JIAN listener, the SDK will read the server push message and distribute it directly through the jian listener to the callback.
private void distributeData(IMEntity imEntity) {
if(mIMListener ! = null&& imEntity ! = null) {
// omit some logical code
…
if(status == Response.SUCCESS) {
switch(responseModel.reqType) {
Caseimconstant. REQ_TYPE_AUTH: // Authentication succeeded
mIMListener.authorSuccess();
return;
Caseimconstant. REQ_TYPE_OFFLINE: // The server kicks the client offline
mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER);
break;
Caseimconstant. REQ_TYPE_HEARTBEAT: // The heartbeat succeeds
Caseimconstant. REQ_TYPE_RECEIVER_MSG: // The callback message was received
handleMessageID(responseModel.body);
break;
default:
break;
}
mIMListener.receiveSuccess(responseModel.reqType, msgId, responseModel
.requestChannel, responseModel.body, 0);
} else{
mIMListener.receiveError(responseModel.reqType, msgId, responseModel
.requestChannel, status);
}
}
}
Some received messages, such as heartbeat and notification of being kicked offline when multiple logins are received, are processed by the SDK itself, and services are not aware of them.
Design point 4: Customizable UI
With the expansion of company scale and rapid iteration of business line, new business may also need IM function. As we all know, the embedding of IM UI function will occupy a lot of development and debugging time. In order to solve this pain point, we decided to extract IM UI part into a Library, which can be customized and maintained independently. Be truly agile and iterate quickly.
8.1 UIKit design
IM UIKit exposes the corresponding API interface, and the business side injects the corresponding function customization items. Click callback for UI is distributed through EventBus POST, reducing the coupling between the business side and UIKit. The bottom business side decouples the View and Model through MVP mode.
Customizing items can be done in the following ways.
1) XML(customized business information, resource information, display number, each business function switch, etc.) :
<? The XML version = “1.0” encoding = “utf-8”? >
<resources>
<stylename=”limit”>
<! — Number of items displayed on each screen –>
<itemname=”swiplimit”>5</item>
.
</style>
.
.
<stylename=”itembox”>
<itemname=”showvoice”>true</item>
.
.
<itemname=”more”show=”true”>
<more>
<iconstyle=”mipmap”>im_plus_image</icon>
< itemname > test < / itemname >
<callback>false</callback>
</more>
.
.
<more>
<iconstyle=”mipmap”>ic_launcher</icon>
< itemname > test < / itemname >
<callback>true</callback>
</more>
</item>
.
.
</style>
</resources>
2) Style(customize UI background, bubble color, font size, etc.) :
<? The XML version = “1.0” encoding = “utf-8”? >
<resources>
<! — IM chat background –>
<stylename=”imui_background”>
<itemname=”android:background”>@android:color/holo_red_dark</item>
</style>
.
.
<! — Bubble background –>
<stylename=”bubble_background”>
<itemname=”android:background”>@mipmap/bubble_right_green</item>
</style>
<! Background and field color customization –>
<stylename=”bg_and_textcolor”parent=”bubble_background”>
<itemname=”android:textColor”>@android:color/holo_red_dark</item>
</style>
.
.
</resources>
3) Model customization (pass in the preset custom Model template and fill in the corresponding parameters, and do the corresponding parsing in UIKit) :
public class Entity {
publicString action1;
publicString action2;
publicString aciton3;
.
}
8.2 Rich Media Types supported by UIKit
In addition to text messages, various rich media are also supported in the mainstream IM system. In the Uplike IM SDK UIKit, several rich media are also supported at present. Below is a flow chart and a brief introduction to two common types of rich media messages.
- 1) Voice message: in addition to the use of common recording and decoding playback technologies. In addition, requestAudioFocus and abandonAudioFocus in AudioManager were used to record and play voice messages. If a third party played music, it would be automatically suspended. After recording and playing voice messages, the voice would be automatically played.
- 2) Picture message: The thumbnail is set through qiliu server. After receiving the message, the recipient will download the thumbnail first. When the user clicks into the picture details page, the large picture will be downloaded.
9. Design Point 5: chat session data loading policy in UI
Referring to the mainstream IM system solution, users need to save the sent and received chat information to the local PC instead of pulling historical data each time. In order to save traffic and no network state also view data effect.
A simple storage loading mechanism is also implemented in the DATABASE of the IM SDK persistence layer. Typical data loading scenarios are described below.
1) Data request flow for the first time in AN IM session:
2) Process of IM drop-down to obtain historical data:
3) IM Single message sending Persistence scheme:
4) IM single data retransmission process:
10. Design deficiencies
In the current design scheme, there is no message receipt mechanism, that is, after receiving the message, the receiver will not return the notification of receiving the message to the server, and the server cannot determine whether the message is pushed successfully. As a result, the message arrival rate will be affected in the case of sudden network disconnection, network mode switching, or weak network environment.
A feasible design method is that the sender adds the sent and undelivered states. After receiving the message, the receiver returns a notification that the message has been received to the server, and the server pushes the status to the sender. If there is no receipt from the receiver, the server can try to push the status again. After receiving the receipt from the recipient, the sender updates the sending status to have been sent. If it has not been received, it will be displayed as not being delivered. To prevent receipt loss, the recipient maintains a local de-rescheduling queue when receiving messages.
Locally initiated requests do not use timers but rely on the server’s return or an upthrow notification after a Socket channel exception as a timeout judgment. Some scenarios may not be covered, so a fixed timeout processing mechanism needs to be added for the request. If no request is received at a fixed time, the request is considered timeout.
* Recommended learning: for the above two shortcomings, interested readers, you can study MobileIMSDK open source project source github.com/JackJiang20… , MobileIMSDK has implemented a complete message delivery guarantee mechanism (including ACK receipt, retransmission, de-duplication, timeout determination, etc.). (This article is simultaneously published at: www.52im.net/thread-3088…)