本文主要以Android客户端为例,记录了有赞旗下 App 中使用自研 IM SDK 设计思路,由有赞移动开发组 IM SDK 团队共同讨论完成。
在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商,分销商,客户三方之间需要同步沟通,会同时存在多种沟通角色。此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK 项目便应运而生了,APP 通过接入此SDK,可以快速实现IM基本功能。
下图中简要描述了有赞客户端中IM系统的基本结构
此章节中主要描述了,IM SDK设计中一些重要流程。
IM SDK 所有数据收发流程,均通过Socket长连接完成,如何维护一个稳定Socket通道,是IM系统是否稳定的重要一环。
下面描述下Socket通道几个重要的流程
重连流程 重连被触发时,如果该次连接成功,退出重连。反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为6次),并对重连次数计数,如果超过就会退出重连,反之休眠预设的时间后再次进行重连操作。
重连触发条件分为三种:
网络状态判断
TCP API并没有提供一个可靠的方法判断当前长连接通道状态,isConnected()和isClosed()仅仅告诉你当前的Socket状态,不是是长连接断开是一回事。 isConnected()告诉你是否Socket与Romote host保持连接,isClosed()告诉你是否Socket被关闭。
假如你判断长连接通道是否被关闭,只能通过和流操作相关的以下方法:
所以SDK封装isConnected()方法的时候,是根据这几种情况综合判断当前的通道状态,而不是仅仅通过Socket.isConnected()或者Socket.isClosed()。
消息发送流程主要有两大类,一类是IM相关数据的请求,例如:历史消息列表,会话列表等,另一类是IM消息的发送,主要是文字消息。(富媒体消息发送,会将富媒体文件先上传服务器后,拿到文件URL, 通过文字消息,将此URL发给接收方,接收方下载后进行UI展示)。 此两类消息发送,均使用上图的流程进行发送,可通过发送回调感知请求的结果。
如图所示,消息发送流程,需要先封装消息请求,在通过发送队列发送至服务器,发送前,在将请求id和对应回调存入本地Map数据结构中。
if (requestCallBack != null) {
mCallBackMap.put(requestId, requestCallBack);
}
之后接收服务器推送消息(此消息带有发送请求时的请求id),在本地的Map数据找到请求id对应的回调,然后通过回调返回服务器推送过来的数据。
请求可以通过泛型指定返回值类型,SDK中会自行解析服务器数据返回的数据,直接返回给业务调用方model对象,方便使用。(目前支持json格式的数据解析)
private void IMResponseOnSuccess(String requestid, String response) {
if (mCallBackMap != null) {
IMCallBack callBack = mCallBackMap.get(requestid);
if (callBack == null) {
return;
}
if (callBack instanceof JsonResultCallback) {
final JsonResultCallback resultCallback = (JsonResultCallback) callBack;
if (resultCallback.mType == String.class) {
callBack.onResponse(response);
} else {
Object object = new Gson().fromJson(response, resultCallback.mType);
callBack.onResponse(object);
}
removeCallBack(requestid);
}
}
}
如下的示例中,展示了一个获取会话列表的请求,可以看出目前的请求封装,和一些第三方的的网络库类似,使用起来较为方便。
RequestApi requestApi = new RequestApi(IMConstant.REQ_TYPE_GET_CONVERSATION_LIST, EnumsManager.IMType.IM_TYPE_WSC.getRequestChannel());
requestApi.addRequestParams("limit", 100);
requestApi.addRequestParams("offset", 0);
IMEngine.getInstance().request(requestApi, new JsonResultCallback<List<ConversationEntity>>() {
@Override
public void onResponse(List<ConversationEntity> response) {
mSwipeRefreshLayout.setRefreshing(false);
mAdapter.mDataset.clear();
mAdapter.mDataset.addAll(response);
mAdapter.notifyDataSetChanged();
}
@Override
public void onError(int statusCode) {
//do something
}
});
可以看出,该请求直接返回了一个会话类型的List集合,业务方可直接使用。
消息的监听流程主要使用了一个全局监听的方式来进行,需要先注册监听器,监听器中有默认的回调。
public interface IMListener {
/**
* 连接成功
*/
void connectSuccess();
/**
* 连接失败
*/
void connectFailure(EnumsManager.DisconnectType type);
/**
* 鉴权成功
*/
void authorSuccess();
/**
* 鉴权失败
*/
void authorFailure();
/**
* 接收数据成功
*/
void receiveSuccess(int reqType, String msgId, String requestChannel, String message, int statusCode);
/**
* 接收数据失败
*/
void receiveError(int reqType, String msgId, String requestChannel, int statusCode);
}
该监听器中可以接收如下类型的消息:
业务如需使用此全局监听器,需要自行实现此接口,并在业务初始化时,注册此监听器即可。SDK中会根据注册的监听器,在读取到服务器推送消息后,直接通过监听器到回调进行分发。
private void distributeData(IMEntity imEntity) {
if (mIMListener != null && imEntity != null) {
// 省略部分逻辑代码
……
if (status == Response.SUCCESS) {
switch (responseModel.reqType) {
case IMConstant.REQ_TYPE_AUTH: // 鉴权成功
mIMListener.authorSuccess();
return;
case IMConstant.REQ_TYPE_OFFLINE: // 服务端踢客户端下线
mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER);
break;
case IMConstant.REQ_TYPE_HEARTBEAT: // 心跳成功
case IMConstant.REQ_TYPE_RECEIVER_MSG: // 收到回调消息
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);
}
}
}
部分接收消息,如心跳,多端登录时被踢下线通知等,sdk内部会自行处理,业务基本无感知。
随着公司规模的扩大与业务线的快速迭代,可能新的业务也需要 IM 这个功能,众所周知,IM UI 功能的嵌入会占据大量的开发与调试时间, 为了解决这个痛点,决定将 IM UI 部分抽成一个 Library,实现可定制与单独维护,做到真正的敏捷开发与快速迭代。
IM UIKit暴露相应的api接口,业务方注入相应的功能定制项,针对UI的点击回调通过EventBus总线post分发,减少了业务方与UIKit的耦合,底层业务方通过MVP模式对View与Model进行解耦。定制项一般通过如下几种方式:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="limit">
<!--每屏展示的条数-->
<item name="swiplimit">5</item>
......
</style>
......
......
<style name="itembox">
<item name="showvoice">true</item>
......
......
<item name="more" show="true">
<more>
<icon style="mipmap">im_plus_image</icon>
<itemname>测试</itemname>
<callback>false</callback>
</more>
......
......
<more>
<icon style="mipmap">ic_launcher</icon>
<itemname>测试</itemname>
<callback>true</callback>
</more>
</item>
......
......
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--im 聊天背景-->
<style name="imui_background">
<item name="android:background">@android:color/holo_red_dark</item>
</style>
......
......
<!--气泡背景-->
<style name="bubble_background">
<item name="android:background">@mipmap/bubble_right_green</item>
</style>
<!--背景和和字段颜色定制-->
<style name="bg_and_textcolor" parent="bubble_background">
<item name="android:textColor">@android:color/holo_red_dark</item>
</style>
......
......
</resources>
public class Entity {
public String action1;
public String action2;
public String aciton3;
......
}
除了文字消息之外,现在主流的IM系统中也支持各种富媒体发送,在有赞IM SDK UIKit中,目前也支持几种富媒体发送。 以下是发送流程图和两类常见富媒体消息简介。
AudioManager
中 requestAudioFocus
,abandonAudioFocus
相关方法,实现了录制和播放语音消息,如果有第三方播放音乐,会自动暂停,录制和播放语音消息结束后,声音会自动播放。参考业界主流的IM系统方案,用户聊天时,需要将已经发送和接收到的聊天信息保存到本地。而不是每次都拉取历史数据。以达到节约流量和无网络状态下也查看数据的效果。为此IM SDK持久化层的数据库中,也实现了简单存储加载机制,下面描述典型的数据加载场景。
欢迎关注我们的公众号
Original url: Access
Created at: 2019-09-26 16:58:59
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论