IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践
本文由有赞技术团队原创分享,原题“有赞 APP IM SDK 组件架构设计”,即时通讯网收录时有修订和改动,感谢原作者的无私分享。
1、引言
本文主要以Android客户端为例,记录了有赞旗下 App 中使用自研 IM,并将IM提炼成组件化SDK的设计思路。此项工作由有赞移动开发组 IM SDK 团队共同讨论完成。

在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商、分销商、客户三方之间需要同步沟通,会同时存在多种沟通角色。
此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK项目便应运而生了,APP 通过接入此给件化SDK,可以快速实现IM基本功能。
学习交流:
- 即时通讯/推送技术开发交流5群:215477170[推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/ANp1kuj65Ww5RpABl2M9RQ,原文链接是:http://www.52im.net/thread-3088-1-1.html
2、相关文章
《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》
《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》(* 推荐)
《从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践》
《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》
《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》
3、设计目标
本次IM组件化SDK的设计目标有以下几点:
- 1)IM 主流程稳定可用:消息传输具有高可靠性;
- 2)UI 组件直接集成进入SDK,并支持可定制化;
- 3)富媒体发送集成进入SDK,并可按需定制需要的富媒体类型;
- 4)实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。
4、整体结构
下图中简要描述了有赞客户端中IM系统的基本结构 :

如上图所示,各分层的职责分工如下:
- 1)消息通道层:维护Socket长连接作为消息通道,消息收发流程主要在这一层中完成;
- 2)持久化层:主要将消息存入数据库中,富媒体文件存入文件缓存中,方便第二次展示消息时候,从本地加载,而不是网络层获取;
- 3)逻辑处理层:完成各种消息相关的逻辑处理,如排序,富媒体文件的预处理等;
- 4)UI显示层:将数据在UI上进行呈现。
5、设计要点1:Socket长连接的创建与维护
IM SDK 所有数据收发流程,均通过Socket长连接完成,如何维护一个稳定Socket通道,是IM系统是否稳定的重要一环。
下面描述下Socket通道几个重要的流程。
1)创建流程(连接) :

如图上所示,当IM SDK初始化后,业务调用连接请求接口,会开始连接的创建过程,创建成功后,会完成鉴权操作,当创建和鉴权都完成后,会开启消息收发线程,为了维持长连接,会有心跳机制,特别的,会开启一个心跳轮询线程。
2)心跳机制 :
心跳机制,是IM系统设计中的常见概念,简单的解释就是每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。

当长连接创建成功后,会开启一个轮询线程,每隔一段时间发送心跳消息给服务器端,以维持长连接。
有关IM心跳方面的专项文章,请见:
《手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制》
《移动端IM实践:WhatsApp、Line、微信的心跳策略分析》
《一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等》
《正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)》
3)重连流程 :

重连被触发时,如果该次连接成功,退出重连。反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为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:消息发送流程
消息发送流程主要有两大类:
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 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);
}
}
}
如下的示例中,展示了一个获取会话列表的请求,可以看出目前的请求封装,和一些第三方的的网络库类似,使用起来较为方便。
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
}
});
可以看出,该请求直接返回了一个会话类型的List集合,业务方可直接使用。
7、设计要点3:消息接收流程
消息的监听流程主要使用了一个全局监听的方式来进行,需要先注册监听器,监听器中有默认的回调。
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) {
caseIMConstant.REQ_TYPE_AUTH: // 鉴权成功
mIMListener.authorSuccess();
return;
caseIMConstant.REQ_TYPE_OFFLINE: // 服务端踢客户端下线
mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER);
break;
caseIMConstant.REQ_TYPE_HEARTBEAT: // 心跳成功
caseIMConstant.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.1 UIKit设计

IM UIKit暴露相应的api接口,业务方注入相应的功能定制项,针对UI的点击回调通过EventBus总线post分发,减少了业务方与UIKit的耦合,底层业务方通过MVP模式对View与Model进行解耦。
定制项一般通过如下几种方式。
1)XML(定制业务信息,资源信息,显示条数,各个业务功能开关等):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<stylename="limit">
<!--每屏展示的条数-->
<itemname="swiplimit">5</item>
......
</style>
......
......
<stylename="itembox">
<itemname="showvoice">true</item>
......
......
<itemname="more"show="true">
<more>
<iconstyle="mipmap">im_plus_image</icon>
<itemname>测试</itemname>
<callback>false</callback>
</more>
......
......
<more>
<iconstyle="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 聊天背景-->
<stylename="imui_background">
<itemname="android:background">@android:color/holo_red_dark</item>
</style>
......
......
<!--气泡背景-->
<stylename="bubble_background">
<itemname="android:background">@mipmap/bubble_right_green</item>
</style>
<!--背景和和字段颜色定制-->
<stylename="bg_and_textcolor"parent="bubble_background">
<itemname="android:textColor">@android:color/holo_red_dark</item>
</style>
......
......
</resources>
3)Model定制(传入预设的定制Model模板填入相应参数,UIKit里面做相应解析):
public class Entity {
publicString action1;
publicString action2;
publicString aciton3;
......
}
8.2 UIKit 支持的富媒体类型
除了文字消息之外,现在主流的IM系统中也支持各种富媒体发送,在有赞IM SDK UIKit中,目前也支持几种富媒体发送。 以下是发送流程图和两类常见富媒体消息简介。
- 1)语音消息:除了使用常见的录制和解码播放的技术之外。还利用了 AudioManager 中 requestAudioFocus,abandonAudioFocus 相关方法,实现了录制和播放语音消息,如果有第三方播放音乐,会自动暂停,录制和播放语音消息结束后,声音会自动播放。
- 2)图片消息:通过七牛服务器设置了缩略图,接收方收到消息后,会先下载缩略图,当用户再点击进入图片详情页时,会下载大图,Andorid客户端使用Picasso加载库加载图片,并做本地缓存。
9、设计要点5:UI 中聊天会话数据加载策略
参考业界主流的IM系统方案,用户聊天时,需要将已经发送和接收到的聊天信息保存到本地,而不是每次都拉取历史数据。以达到节约流量和无网络状态下也查看数据的效果。
为此IM SDK持久化层的数据库中,也实现了简单存储加载机制,下面描述典型的数据加载场景。
1)IM会话首次请求数据流程:

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

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

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

10、设计不足之处
1)消息回执:
当前的设计方案中,没有消息回执的机制,也就是说接受方收到消息后,不会返回服务器收到消息的通知,服务器无法判断消息是否推送成功,这样在突然断网,网络模式切换,或者弱网环境下,会影响消息的到达率。
一种可行的设计方式是,发送方增加已送到和未送达的状态,接收方收到消息后,给服务器返回已收到消息的通知,服务器再推送给发送方该状态,如果没有收到接收方回执,服务器可尝试重新推送。发送方接受到接收方的收到回执后,更新发送状态已发送,如果未收到,则显示未送达。为了防止接收方回执丢失,接收方接收消息时候,可维护本地去重队列。
2)本地请求超时的判断:
本地发起的请求,没有用定时器,完全依赖服务器返回或者出现Socket通道异常后上抛的通知作为超时判断,部分场景可能覆盖不到,需要对请求增加固定的超时处理机制,固定时候未收到请求,即认为超时。
* 推荐学习:针对以上两点不足,感兴趣的读者,可以研究一下MobileIMSDK开源工程源码https://github.com/JackJiang2011/MobileIMSDK,MobileIMSDK已经实现了完整的消息送达保证机制(包括:ACK回执、重传、去重、超时判定等等)。
(本文同步发布于:http://www.52im.net/thread-3088-1-1.html)
IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践的更多相关文章
- 搭建rtmp直播流服务之3:java开发ffmpeg实现rtsp转rtmp并实现ffmpeg命令的接口化管理架构设计及代码实现
上一篇文章简单介绍了java如何调用ffmpeg的命令:http://blog.csdn.net/eguid_1/article/details/51777716 上上一篇介绍了nginx-rtmp服 ...
- 【Bugly安卓开发干货分享】Android APP 快速 Pad 化实现
项目背景 采用最新版本手机 APP(之后称为 MyApp)代码,实现其 Pad 化,为平板和大屏手机用户提供更好的体验.为实现 MyApp 的 Pad 化工作,需要我们首先来了解一下 MyApp 项目 ...
- 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)(转)
1.写在前面 1.1.引言 如果在没有太多经验可借鉴的情况下,要设计一套完整可用的移动端IM架构,难度是相当大的.原因在于,IM系统(尤其是移动端IM系统)是多种技术和领域知识的横向应用综合体:网络编 ...
- 网易实战分享|云信IM SDK接口设计实践
引语 IM (Instant Messaging)是网络上最流行的通信方式,与日常生活息息相关.IM软件也层出不穷,例如:微信.QQ.易信等.通过多年深耕和技术沉淀,云信产出了一套成熟稳定的IM SD ...
- 【腾讯Bugly干货分享】打造“微信小程序”组件化开发框架
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2nQzsuqq7Avgs8wsRizUhw 作者:Gc ...
- IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路
1.引言 在即时通讯网经常能看到各种高大上的高并发.分布式.高性能架构设计方面的文章,平时大家参加的众多开发者大会,主题也都是各种高大上的话题——什么5G啦.AI人工智能啦.什么阿里双11分分钟多少万 ...
- iOS开发之组件化架构漫谈
前段时间公司项目打算重构,准确来说应该是按之前的产品逻辑重写一个项目.在重构项目之前涉及到架构选型的问题,我和组里小伙伴一起研究了一下组件化架构,打算将项目重构为组件化架构.当然不是直接拿来照搬,还是 ...
- 升讯威微信营销系统开发实践:(1)功能概要与架构设计( 完整开源于 Github)
GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...
- Vue.js 桌面端自定义滚动条组件|vue美化滚动条VScroll
基于vue.js开发的小巧PC端自定义滚动条组件VScroll. 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue ...
- Android 开发:由模块化到组件化(一)
在Android SDK一文中,我们谈到模块化和组件化,现在我们来聊聊组件化开发背后的哪些事.最早是在广告SDK中应用组件化,但是同样适用于普通应用开发 以下高能,请做好心理准备,看不懂请发私信来交流 ...
随机推荐
- 用 KubeKey 快速离线部署 K8s 与 KubeSphere
作者:尹珉,KubeSphere Ambassador,KubeSphere 社区用户委员会杭州站站长 一.KubeKey 介绍 KubeKey(以下简称 KK) 是一个用于部署 Kubernetes ...
- RecyclerView刷新方式
RecyclerView刷新方式 刷新全部item notifyDataSetChanged() student.setValue(new Student("二狗")); stud ...
- C语言(从入门到入土)
1.C语言整体上需要记住的 总体上必须清楚的: 1)程序结构是三种: 顺序结构 , 循环结构 (三个循环结构), 选择结构 (if 和 switch) 2)读程序都要从main()入口, 然后从最上面 ...
- 一份阅读量30万+免费且全面的C#/.NET面试宝典
前言 C#/.NET/.NET Core相关技术常见面试题汇总,不仅仅为了面试而学习,更多的是查漏补缺.扩充知识面和大家共同学习进步.该知识库主要由自己平时学习实践总结.网上优秀文章资料收集(这一部分 ...
- SaaS平台的组织数据模型设计
大家好,我是汤师爷~ 想要深入理解零售企业的组织架构并不容易.大多数人并没有实际经营过零售企业,更不曾参与设计其组织架构. 在调研商家的过程中,我们通常只能了解他们组织架构的现状,却难以直接与企业高层 ...
- pdf.js使用
百度上很多例子,都是构建之前的! 我们使用pdf.js,最终只需要构建后的内容,大家可以通过这里进行下载: https://pan.baidu.com/s/14J-m-jeHdvn46cPhPXk54 ...
- Flink 中的事件时间触发器和处理时间触发器
EventTimeTrigger EventTimeTrigger 的触发完全依赖 watermark,换言之,如果 stream 中没有 watermark,就不会触发 EventTimeTrigg ...
- 【一步步开发AI运动小程序】十八、如何识别用户上传图片中的人体、运动、动作、姿态?
[云智AI运动识别小程序插件],可以为您的小程序,赋于人体检测识别.运动检测识别.姿态识别检测AI能力.本地原生识别引擎,内置10余个运动,无需依赖任何后台或第三方服务,有着识别速度快.体验佳.扩展性 ...
- 软件逆向之IDA Pro
IDA Pro作为一款强大的逆向分析工具,对于软件开发和安全领域的专业人士来说是必不可少的. 1. 什么是逆向分析 逆向分析是指通过分析已有的软件或程序,推测出其内部运行机制.算法和逻辑等信息.通过逆 ...
- vue3-setup中使用响应式
基本类型的响应式数据 在 Vue 3 中,ref是一个函数,用于创建响应式的数据.它主要用于处理基本类型(如数字.字符串.布尔值等)的数据响应式 当我们调用 ref 函数时,会返回一个包含一个 .va ...