社交场景下的统一即时通讯im消息流交互层模块化技术实践
网易技术团队旭风分享有排版优化和修订。1、引言一款社交产品的诞生离不开即时通讯IM场景。随着团队业务版图在社交领域的布局诞生了多个社交场景APP涉及的IM场景包含私聊、群聊、聊天室等。这些IM场景在消息流的展示形式上是极为相似的同时每个业务又有着自己特殊的交互需求。基于此我们对IM消息流能力做了标准化的构建来减少IM功能的业务接入成本同时也是为了统一各个业务的技术方案减少跨业务开发的理解和维护成本。本文主要针对iOS端在IM消息流交互层的设计上提供一些实践思路。2、业界的实现方案目前业界有各种即时通讯服务商提供的配套交互层解决方案其大多以牺牲灵活性来满足快速集成需要在定制能力上远不能胜任我们业务需要。再诸如 MessageKit之类的社区IM框架其在视觉交互表现上功能完备能帮助我们快速、灵活搭建IM消息流结构但业务需要的是一套完整的携带消息交互能力的方案因此对此类框架仍需要做不小的改造才能适应我们的业务另一参考方案MobileIMSDKGitee源码托管地址。3、我们的想法对于一个IM消息流交互层方案主要考虑几个方面1规范的消息流结构提供消息流视图结构规范化的构建方式2标准的消息交互能力统一消息交互能力业务方按需使用快速集成3业务拓展性针对数据源、消息交互能力提供业务灵活拓展点4业务接入成本内置通用交互方案降低业务接入成本。目前我们存量业务中的IM场景底层IM能力主要由云信引擎提供。同时又存在基于业务服务端通过HTTP去交互的场景。另外还需要预留后期切换IM引擎的可能性因此需要将交互层IM能力抽象出来。此外为了适应团队现状减小业务接入成本考虑将云信提供的交互能力内置在方案中。4、整体设计设计愿景提供标准化的能力同时对拓展开放。我们期望一套通用的IM消息流能力能够在方案上标准化。这里的标准化主要包含消息流结构构建的标准化以及消息交互能力的标准化。同时方案需要在交互能力上适应不同业务场景因此采用依赖注入的方式提供业务定制能力。按照职能划分将框架整体分为了两层1消息流结构层负责消息流结构的构建定义消息视图、布局、数据上的规范提供业务层分别在「消息」、「会话」两个维度的配置能力。2消息交互层提供消息能力、消息流、消息数据方面的交互能力向下依赖交互接口内置标准交互能力的同时也支持业务按需注入交互实现。5、聊天消息流的显示结构5.1 消息组件不同的业务场景消息流样式表现必然有所差异。下面列出了我们几个业务中的消息流界面如何设计一套通用的消息流视图结构满足不同业务需要经过对各个业务以及一些主流IM工具的观察将消息视图结构设计成如下结构是能够满足我们各个IM场景需要的见下图。我将消息结构拆分成了5部分对应5个消息组件 MessageView 每个消息组件都支持业务对其「样式」、「显隐」、「布局」进行配置从而满足不同场景定制需要。MessageView作为基础消息组件提供了一些标准能力例如是否响应菜单动作 canPerformMenuAction 、视图重用回调时机 prepareForReuse 、尺寸策略等。open class MessageView: MessageAbstractView {public var canPerformMenuAction falseopen func refresh(with message: Message) {}open func prepareForReuse() {}open class func createSizeStrategy(message: Message, fittingSize: CGSize) - MessageLayoutSizeStrategy? {// ...}}5.2 尺寸策略消息组件尺寸作为消息流布局上不可或缺的要素方案提供了多种尺寸计算策略 MessageLayoutSizeStrategy 。具体是1自动布局计算策略业务方对消息组件使用 AutoLayout 布局时使用内部会依据约束自动计算好组件尺寸2SizeThatFit 策略依据组件 SizeThatFit 方法返回的尺寸进行布局3自定义策略提供自定义尺寸计算方式。public protocol MessageLayoutSizeStrategy {func caclulateSize(_ sizeViewType: MessageView.Type,message: Message,fittingSize: CGSize) - CGSize}public struct MessageAutoLayoutSizeStrategy: MessageLayoutSizeStrategy {public func caclulateSize(_ sizeViewType: MessageView.Type,message: Message,fittingSize: CGSize) - CGSize {// ...省略其他代码return sizeView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)}}public struct MessageSizeThatFitsStrategy: MessageLayoutSizeStrategy {public func caclulateSize(_ sizeViewType: MessageView.Type,message: Message,fittingSize: CGSize) - CGSize {// ...省略其他代码return sizeView.sizeThatFits(fittingSize)}}5.3 布局快照我们还针对消息组件维度支持了布局快照。通常当一个消息组件尺寸固定在交互过程中尺寸不会发生的情况下打开布局快照以减少布局计算消耗。同时也提供了快照清除的能力。我们对多个消息流在快速滚动过程中的CPU峰值做了统计在使用自动布局尺寸策略的情况下开启布局快照峰值降低了10%~20%。5.4 交互事件另外在手势交互上对外暴露了各个消息组件的一系列交互事件。常见的场景例如单击浏览消息内容长按展示消息菜单等。方案内部提供了基于系统样式的长按菜单并提供上层菜单配置能力同时也可以基于暴露的长按手势事件来自定义菜单。5.5 消息流一个会话对应一个流方案也提供了消息流在会话维度上的一些标准化配置。例如消息分页数量、是否自动拉取历史消息、是否开启增量刷新以及在时间展示上的样式配置等。此外为了减少列表重绘消息流也支持增量刷新。通常情况下业务层不需要主动刷新列表只需对消息数据进行增删改操作内部会触发对数据源的「diff-update」计算从而驱动列表的增量更新。6、聊天消息交互层6.1 概述对于业务方而言在消息交互上通常关心这么几点1提供了哪些标准化的交互能力2如何拓展自定义的交互实现3如何对交互流程进行干预。结合团队现状我们在方案内部内置了基于某信的IM交互能力同时定义了相关交互接口供业务方按需注入实现。在实际业务中一个APP内可能存在多个IM场景因此交互能力支持按会话维度进行注入各个会话之间的交互是相互隔离的。6.2 消息源不同的IM场景消息数据来源可能存在差异。例如我们私聊、群聊的数据源来自云信数据同步服务聊天室数据需要通过云信提供的历史消息接口拉取另外也存在诸如通过业务服务端接口来拉取消息数据的场景。因此方案上设置了数据源接口 SessionMessageProvider 提供不同场景消息源的定制能力。public protocol SessionMessageProvider {func messages(in session: Session,anchorMessage: Message?,limit: Int,completion: escaping ([Message]) - Void)}方案设置了一个负责管理消息数据源的 DataManager 实例 其依赖 SessionMessageProvider 提供的数据源。同时内置了基于云信的数据源获取实现能够根据当前会话类型获取私聊、群聊、聊天室的数据源。如果当前场景是通过HTTP拉取消息的则需要业务上层手动注入一个从接口获取数据源的 SessionMessageProvider 实例。6.3 交互源方案提供了IM标准交互能力例如消息收发、消息撤回、保存等以统一各业务交互姿势。具体的交互源除了要考虑目前包含的云信及业务服务端也要适应其他交互源因此将交互实现部分也抽象出了接口 MessageServiceInterface 。业务根据当前实际场景注入具体的交互实现即可。下面列出了一些交互申明public protocol MessageServiceInterface {func send(message: Message, in session: Session, completion: escaping MessageServiceInterfaceCompletion)func resend(message: Message, completion: escaping MessageServiceInterfaceCompletion)func forward(message: Message, to session: Session, completion: escaping MessageServiceInterfaceCompletion)func revoke(message: Message, completion: escaping MessageServiceInterfaceCompletion)func save(message: Message, in session: Session, completion: escaping MessageServiceInterfaceCompletion)func delete(message: Message, completion: escaping MessageServiceInterfaceCompletion)}同样我们也内置了一些通用交互方案例如支持云信提供的私聊群聊交互能力以及由中台提供的通用聊天室服务交互能力以支持相关场景下快速接入。6.4 交互钩子在实际IM业务开发过程中往往需要对交互流程做一些干预或是在交互过程中做一些定制化的动作。因此方案也提供了一些交互钩子支持「交互前置校验」、「交互前准备」。以消息发送流程为例提供了「发送前校验」、「发送准备」两个消息发送过程的回调钩子public protocol MessageServicePrechecker {// 消息发送前置校验func shouldSend(message: Message, in session: Session) - Bool// ...省略其他代码}public protocol MessageServicePreparation {/// 准备发送准备func prepareSend(message: Message, in session: Session, callback: escaping MessageServicePreparationCallback)// ...省略其他代码}整体的发送流程如图所示前置校验阶段用来作消息发送前的校验工作根据实际状态决定消息是否可以发送。发送准备阶段则可以在消息投递前做最后的准备工作例如海外业务可以在这里处理消息资源附件上传Amazon或是在此处对消息塞入一些客户端信息、反作弊Token等支持异步操作。7、业务接入能力业务只需要在上层提供针对消息以及会话两个维度的配置就能基于内置的交互能力构建出一套基础的IM消息流能力。在具体的消息样式呈现上则通常需要业务层维护一组关于「消息类型-消息组件类型-消息结构」的映射关系。具体关联如下在交互能力上提供了IM场景的标准能力业务可以按需使用。另外实际IM场景可能需要一些更为丰富的定制能力则可以依据方案提供的消息数据源接口、消息交互接口来对具体交互实现进行定制。同时也可以使用相关的交互钩子对交互过程进行干预以适应自己的业务。8、本文小结本文对团队IM场景的现状做了简单介绍撇开具体实现细节就如何搭建一套能够适应多业务需要的通用IM消息流交互层方案提供了一些思考和实践经验。从结果来看该方案稳定支撑了团队多个IM场景抹除各场景实现差异有效降低了维护成本和新业务接入成本。9、参考资料[1] 零基础IM开发入门(一)什么是IM聊天系统[2] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)[3] 一套原创分布式即时通讯(IM)系统理论架构方案[4] 从游击队到正规军(二)马蜂窝旅游网的IM客户端架构演进和实践总结[5] 社交软件红包技术解密(十)手Q客户端针对2020年春节红包的技术实践[6] 微信团队分享来看看微信十年前的IM消息收发架构你做到了吗[7] 携程技术分享亿级流量的办公IM及开放平台技术实践[8] 百度公共IM系统的Andriod端IM SDK组件架构设计与技术实现[9] 转转平台IM系统架构设计与实践(一)整体架构设计[10] 一年撸完百万行代码企业微信的全新鸿蒙NEXT客户端架构演进之路[11] 转转客服IM聊天系统背后的技术挑战和实践分享[12] B站IM消息系统的新架构升级实践[13] 企业微信针对百万级组织架构的客户端性能优化实践[14] 企业微信的IM架构设计揭秘消息模型、万人群、已读回执、消息撤回等[15] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制[16] 现代移动端网络短连接的优化手段总结请求速度、弱网适应、安全保障[17] IM消息ID技术专题(一)微信的海量IM聊天消息序列号生成实践算法原理篇[18] IM开发干货分享有赞移动端IM的组件化SDK架构设计实践[19] 阿里技术分享闲鱼IM基于Flutter的移动端跨端改造实践[20] IM开发干货分享万字长文详解IM“消息“列表卡顿优化实践[21] IM开发干货分享IM客户端不同版本兼容运行的技术思路和实践总结[22] 百度统一socket长连接组件从0到1的技术实践[23] 淘宝移动端统一网络库的架构演进和弱网优化技术实践[24] 抖音技术分享飞鸽IM桌面端基于Rust语言进行重构的技术选型和实践总结[25] 大型IM工程重构实践企业微信Android端的重构之路即时通讯技术学习- 移动端IM开发入门文章《新手入门一篇就够从零开发移动端IM》- 开源IM框架源码https://github.com/JackJiang2011/MobileIMSDK备用地址点此本文同步发布于http://www.52im.net/thread-4905-1-1.html