默认
打赏 发表评论 3
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践
微信扫一扫关注!

本文由有赞技术团队原创分享,原题“有赞 APP IM SDK 组件架构设计”,即时通讯网收录时有修订和改动,感谢原作者的无私分享。


1、引言


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

tech.jpg

在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商、分销商、客户三方之间需要同步沟通,会同时存在多种沟通角色。

此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK项目便应运而生了,APP 通过接入此给件化SDK,可以快速实现IM基本功能。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:
52im_qr_即时通讯技术圈_400px.png
▲ 本文在公众号上的链接是:点此打开

2、相关文章



3、设计目标


本次IM组件化SDK的设计目标有以下几点:

  • 1)IM 主流程稳定可用:消息传输具有高可靠性;
  • 2)UI 组件直接集成进入SDK,并支持可定制化;
  • 3)富媒体发送集成进入SDK,并可按需定制需要的富媒体类型;
  • 4)实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。

4、整体结构


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

如上图所示,各分层的职责分工如下:

  • 1)消息通道层:维护Socket长连接作为消息通道,消息收发流程主要在这一层中完成;
  • 2)持久化层:主要将消息存入数据库中,富媒体文件存入文件缓存中,方便第二次展示消息时候,从本地加载,而不是网络层获取;
  • 3)逻辑处理层:完成各种消息相关的逻辑处理,如排序,富媒体文件的预处理等;
  • 4)UI显示层:将数据在UI上进行呈现。

5、设计要点1:Socket长连接的创建与维护


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

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

1)创建流程(连接) :

2.jpg

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

2)心跳机制 :

心跳机制,是IM系统设计中的常见概念,简单的解释就是每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。

3.jpg

当长连接创建成功后,会开启一个轮询线程,每隔一段时间发送心跳消息给服务器端,以维持长连接。

有关IM心跳方面的专项文章,请见:


3)重连流程 :

4.jpg

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

重连触发条件分为三种:

  • a. 主动连接不成功(主动连接Socket,如果连接失败,会触发重连机制);
  • b. 网络被主动断开(正常建立连接,操作过程中,网络被断开,通过系统广播触发重连);
  • c. 服务器没响应,心跳没回值(服务端心跳预设时间内没回值,客户端认为服务端已经断开,触发重连)。

有关重连机制的深入学习,可以阅读以下两篇:


4)网络状态判断:

TCP API并没有提供一个可靠的方法判断当前长连接通道状态,isConnected()和isClosed()仅仅告诉你当前的Socket状态,不是是长连接断开是一回事。 isConnected()告诉你是否Socket与Romote host保持连接,isClosed()告诉你是否Socket被关闭。

假如你判断长连接通道是否被关闭,只能通过和流操作相关的以下方法:

  • a. read() return -1;
  • b. readLine() return null;
  • c. readXXX() throw EOPException for any other XXX;
  • d. write 将抛出IOException: Broken pipe(通道被关闭)。

所以SDK封装isConnected(方法的时候,是根据这几种情况综合判断当前的通道状态,而不是仅仅通过Socket.isConnected()或者Socket.isClosed()。

6、设计要点2:消息发送流程


5.jpg

消息发送流程主要有两大类:

  • 1)一类是IM相关数据的请求,例如:历史消息列表,会话列表等;
  • 2)另一类是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集合,业务方可直接使用。

7、设计要点3:消息接收流程


6.jpg

消息的监听流程主要使用了一个全局监听的方式来进行,需要先注册监听器,监听器中有默认的回调。
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);
}

该监听器中可以接收如下类型的消息:

  • 1)Socket连接状态的返回结果;
  • 2)鉴权状态的返回结果,(鉴权流程因有赞业务需要);
  • 3)接收的IM消息,或者其他类型的返回消息。可根据消息类型进行后续的分发处理。

业务如需使用此全局监听器,需要自行实现此接口,并在业务初始化时,注册此监听器即可。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内部会自行处理,业务基本无感知。

8、设计要点4:可定制化的UI


随着公司规模的扩大与业务线的快速迭代,可能新的业务也需要 IM 这个功能,众所周知,IM UI 功能的嵌入会占据大量的开发与调试时间, 为了解决这个痛点,决定将 IM UI 部分抽成一个 Library,实现可定制与单独维护,做到真正的敏捷开发与快速迭代。

8.1UIKit设计


7.jpg

IM UIKit暴露相应的api接口,业务方注入相应的功能定制项,针对UI的点击回调通过EventBus总线post分发,减少了业务方与UIKit的耦合,底层业务方通过MVP模式对View与Model进行解耦。

定制项一般通过如下几种方式。

1)XML(定制业务信息,资源信息,显示条数,各个业务功能开关等):
    <?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>

2)Style(定制UI背景,气泡颜色,字体大小等):
    <?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>

3)Model定制(传入预设的定制Model模板填入相应参数,UIKit里面做相应解析):
    public class Entity {
        public String action1;
        public String action2;
        public String aciton3;
        ......
    }

8.2UIKit 支持的富媒体类型


除了文字消息之外,现在主流的IM系统中也支持各种富媒体发送,在有赞IM SDK UIKit中,目前也支持几种富媒体发送。 以下是发送流程图和两类常见富媒体消息简介。

1)语音消息:除了使用常见的录制和解码播放的技术之外。还利用了 AudioManager 中 requestAudioFocus,abandonAudioFocus 相关方法,实现了录制和播放语音消息,如果有第三方播放音乐,会自动暂停,录制和播放语音消息结束后,声音会自动播放。

2)图片消息:通过七牛服务器设置了缩略图,接收方收到消息后,会先下载缩略图,当用户再点击进入图片详情页时,会下载大图,Andorid客户端使用Picasso加载库加载图片,并做本地缓存。

9、设计要点5:UI 中聊天会话数据加载策略


参考业界主流的IM系统方案,用户聊天时,需要将已经发送和接收到的聊天信息保存到本地,而不是每次都拉取历史数据。以达到节约流量和无网络状态下也查看数据的效果。

为此IM SDK持久化层的数据库中,也实现了简单存储加载机制,下面描述典型的数据加载场景。

1)IM会话首次请求数据流程:
8.jpg

2)IM下拉获取历史数据流程:
9.jpg

3)IM单条消息发送持久化方案:
10.jpg

4)IM单条数据重发流程:
11.jpg

10、设计不足之处


1)消息回执:

当前的设计方案中,没有消息回执的机制,也就是说接受方收到消息后,不会返回服务器收到消息的通知,服务器无法判断消息是否推送成功,这样在突然断网,网络模式切换,或者弱网环境下,会影响消息的到达率。

一种可行的设计方式是,发送方增加已送到和未送达的状态,接收方收到消息后,给服务器返回已收到消息的通知,服务器再推送给发送方该状态,如果没有收到接收方回执,服务器可尝试重新推送。发送方接受到接收方的收到回执后,更新发送状态已发送,如果未收到,则显示未送达。为了防止接收方回执丢失,接收方接收消息时候,可维护本地去重队列。

2)本地请求超时的判断:

本地发起的请求,没有用定时器,完全依赖服务器返回或者出现Socket通道异常后上抛的通知作为超时判断,部分场景可能覆盖不到,需要对请求增加固定的超时处理机制,固定时候未收到请求,即认为超时。

* 推荐学习:针对以上两点不足,感兴趣的读者,可以研究一下MobileIMSDK开源工程源码,MobileIMSDK已经实现了完整的消息送达保证机制(包括:ACK回执、重传、去重、超时判定等等)。

原文链接:developers.youzanyun.com/article/1557135455431

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:IM系统开发中,在线消息推送失败后一般怎么处理?下一篇:Soul 千万级 IM搭建分享-网络层搭建

本帖已收录至以下技术专辑

推荐方案
评论 3
有群友说,文章里的链接放的太多了,所以就去掉了附录部分,感兴趣的可以看看,这是附录被删除的内容:

[1] IM热点问题技术文章:
新手入门一篇就够:从零开发移动端IM
移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
从客户端的角度来谈谈移动端IM的消息可靠性和送达机制
现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障
腾讯技术分享:社交网络图片的带宽压缩技术演进之路
小白必读:闲话HTTP短连接中的Session和Token
IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理
移动端IM中大规模群消息的推送如何保证效率、实时性?
移动端IM开发需要面对的技术问题
开发IM是自己设计协议用字节流好还是字符流好?
请问有人知道语音留言聊天的主流实现方式吗?
IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
IM消息送达保证机制实现(二):保证离线消息的可靠投递
如何保证IM实时消息的“时序性”与“一致性”?
一个低成本确保IM消息时序的方法探讨
IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?
IM群聊消息如此复杂,如何保证不丢不重?
谈谈移动端 IM 开发中登录请求的优化
移动端IM登录时拉取数据如何作到省流量?
浅谈移动端IM的多点登录和消息漫游原理
完全自已开发的IM该如何设计“失败重试”机制?
通俗易懂:基于集群的移动端IM接入层负载均衡方案分享
微信对网络影响的技术试验及分析(论文全文)
即时通讯系统的原理、技术和应用(技术论文)
开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀
QQ音乐团队分享:Android中的图片压缩技术详解(上篇)
QQ音乐团队分享:Android中的图片压缩技术详解(下篇)
腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率
腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)
腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)
如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)
字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8
全面掌握移动端主流图片格式的特点、性能、调优等
子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践
IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)
融云技术分享:解密融云IM产品的聊天消息ID生成策略
IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!
适合新手:从零开发一个IM服务端(基于Netty,有完整源码)
拿起键盘就是干:跟我一起徒手开发一套分布式IM系统
适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)
IM里“附近的人”功能实现原理是什么?如何高效率地实现它?
IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路
IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质
IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现
IM的扫码登录功能如何实现?一文搞懂主流应用的扫码登录技术原理
IM要做手机扫码登录?先看看微信的扫码登录功能技术原理
IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)
IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)
IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略
IM消息ID技术专题(四):深度解密美团的分布式ID生成算法
IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现
IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总
IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的
IM开发快速入门(一):什么是IM系统?
IM开发干货分享:如何优雅的实现大量离线消息的可靠投递
IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践
>> 更多同类文章 ……

[2] 有关IM架构设计的文章:
浅谈IM系统的架构设计
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
一套海量在线用户的移动端IM架构设计实践分享(含详细图文)
一套原创分布式即时通讯(IM)系统理论架构方案
从零到卓越:京东客服即时通讯系统的技术架构演进历程
蘑菇街即时通讯/IM服务器开发之架构选择
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT
微信后台基于时间序的海量数据冷热分级架构设计实践
微信技术总监谈架构:微信之道——大道至简(演讲全文)
如何解读《微信技术总监谈架构:微信之道——大道至简》
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
17年的实践:腾讯海量产品的技术方法论
移动端IM中大规模群消息的推送如何保证效率、实时性?
现代IM系统中聊天消息的同步和存储方案探讨
IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?
IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议
IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token
WhatsApp技术实践分享:32人工程团队创造的技术神话
微信朋友圈千亿访问量背后的技术挑战和实践总结
王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等
IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?
腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面
以微博类应用场景为例,总结海量社交系统的架构设计步骤
快速理解高性能HTTP服务端的负载均衡技术原理
子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践
知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)
新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践
一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史
阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路
社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等
社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进
社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节
社交软件红包技术解密(四):微信红包系统是如何应对高并发的
社交软件红包技术解密(五):微信红包系统是如何实现高可用性的
社交软件红包技术解密(六):微信红包系统的存储层架构演进实践
社交软件红包技术解密(七):支付宝红包的海量高并发技术实践
社交软件红包技术解密(八):全面解密微博红包技术方案
社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等
社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践
即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?
即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途
多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了
从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路
从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结
从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践
IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!
瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)
阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处
微信后台基于时间序的新一代海量数据存储架构的设计实践
IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!
>> 更多同类文章 ……

感谢楼主的分享,非常有用!
引用:卡瑞吉 发表于 2020-10-09 11:36
感谢楼主的分享,非常有用!

不客气
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部