首页 存档 技术 查看内容

有赞AppIMSDK组件架构设计 背景 设计目标 整体结构 设计要点 设计不足之处 未来发展方 ...

2018-3-30 13:00 |来自: 互联网 362 0

摘要: 本文选自《开发者头条》4 月 18 日用户分享,作者 Wei Jianfeng ,感谢 崔玉松 分享。 头条不止是阅读,还有分享,更有IO币。可以兑换机械键盘、技术图书喔。 欢迎分享:http://toutiao.io/contribute 本文主要以 ...

本文选自《开发者头条》4 月 18 日用户分享,作者 Wei Jianfeng ,感谢 崔玉松 分享。

头条不止是阅读,还有分享,更有IO币。可以兑换机械键盘、技术图书喔。


欢迎分享http://toutiao.io/contribute

本文主要以Android客户端为例,记录了有赞旗下 App 中使用自研 IM SDK 设计思路,由有赞移动开发组 IM SDK 团队共同讨论完成。


背景

在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商,分销商,客户三方之间需要同步沟通,会同时存在多种沟通角色。此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK 项目便应运而生了,APP 通过接入此SDK,可以快速实现IM基本功能。


设计目标

  • IM 主流程稳定可用:消息传输具有高可靠性。

  • UI 组件直接集成进入SDK,并支持可定制化。

  • 富媒体发送集成进入SDK,并可按需定制需要的富媒体类型。

  • 实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。


整体结构

下图中简要描述了有赞客户端中IM系统的基本结构

  • 消息通道层:维护Socket长连接作为消息通道,消息收发流程主要在这一层中完成。

  • 持久化层:主要将消息存入数据库中,富媒体文件存入文件缓存中,方便第二次展示消息时候,从本地加载,而不是网络层获取。

  • 逻辑处理层:完成各种消息相关的逻辑处理,如排序,富媒体文件的预处理等。

  • UI显示层:将数据在UI上进行呈现。


设计要点

此章节中主要描述了,IM SDK设计中一些重要流程。


Socket长连接的创建与维护

IM SDK 所有数据收发流程,均通过Socket长连接完成,如何维护一个稳定Socket通道,是IM系统是否稳定的重要一环。

下面描述下Socket通道几个重要的流程

  • 创建流程(连接)如图所示,当IM SDK初始化后,业务调用连接请求接口,会开始连接的创建过程,创建成功后,会完成鉴权操作,当创建和鉴权都完成后,会开启消息收发线程,为了维持长连接,会有心跳机制,特别的,会开启一个心跳轮询线程。

  • 心跳
    心跳机制,是IM系统设计中的常见概念,简单的解释就是每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。
    当长连接创建成功后,会开启一个轮询线程,每隔一段时间发送心跳消息给服务器端,以维持长连接。

  • 重连流程
    重连被触发时,如果该次连接成功,退出重连。反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为6次),并对重连次数计数,如果超过就会退出重连,反之休眠预设的时间后再次进行重连操作。
    重连触发条件分为三种:

    • 主动连接不成功(主动连接Socket,如果连接失败,会触发重连机制)

    • 网络被主动断开(正常建立连接,操作过程中,网络被断开,通过系统广播触发重连)

    • 服务器没响应,心跳没回值(服务端心跳预设时间内没回值,客户端认为服务端已经断开,触发重连)

  • 网络状态判断
    TCP API并没有提供一个可靠的方法判断当前长连接通道状态,isConnected()和isClosed()仅仅告诉你当前的Socket状态,不是是长连接断开是一回事。 isConnected()告诉你是否Socket与Romote host保持连接,isClosed()告诉你是否Socket被关闭。
    假如你判断长连接通道是否被关闭,只能通过和流操作相关的以下方法:

  1. read() return -1

  2. readLine() return null

  3. readXXX() throw EOPException for any other XXX

  4. write 将抛出IOException: Broken pipe(通道被关闭)

所以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
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部