15

面向并行研发的58同城Android端IM模块发展演变

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI1NDc5MzIxMw%3D%3D&%3Bmid=2247487171&%3Bidx=1&%3Bsn=bf1c43a480de948e4a2c4bcb74cde48f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

导语

本文分享了IM模块重构的框架和近期为面向并行开发和业务线个性化需求而做的业务线拆分工作、项目经验和遇到的问题及解决方案,希望能给大家一些帮助。

ps:58司庆技术直男送口红,最后三只口红花落谁家文末揭晓~

背景

目前IM即时通信对于各大应用来说都是重要的功能部分,而且地位较高,线上沟通可显著提高用户体验和APP的活跃度及流量,且IM承载各个业务线用户的线上沟通,包括B2C和C2C场景。 58对IM系统相当重视 为了给用户更好的IM体验,我们对IM做了深入的研发,包括公司内部的IM体系自研底层SDK和server服务及功能优化和版本迭代。

IM框架

1.原IM架构存在的问题

IM在58APP中是很早就存在的功能,之前的IM在维护和开发的过程中出现了一些问题,这些问题在早期的开发中应该很多功能模块也存在,问题如下:

1)  API功能模糊,职责不不明确,可读性差,不利于开发维护;

2)代码逻辑长时间的迭代后出现复杂交错,难以梳理,问题难定位难修复;

3)后期需求开发因结构不清晰,需求开发出现堆积添加功能的问题;

4)无流程规范,不便于迭代;

5)业务需求实现较难或无法实现。

2.重构IM架构

IM作为重要的产品功能,上述问题需要解决,考虑其开发迭代中的健壮性和易扩展维护性,我们将IM整个功能体系进行了重构,并重新设计了IM框架,该框架已经历了1年多的版本迭代和需求开发,有优点也有缺点,目前还是比较稳定的。

  a)    分层架构

rIVvErY.png!web

IM在我们项目Project中是基础lib库,整体采用分层架构,采用经典三层架构,分层架构结合使用总结以下优缺点:

【 优点 】

1)高内聚、低耦合;

2)结构简单,容易理解和开发;

3)开发人员可以只关注整个结构中的其中某一层;

4)可以很容易的用新的实现来替换原有层次的实现;

5)每一层都可以独立测试,其他层的接口通过模拟解决;

6)可以降低层与层之间的依赖;

7)在后期维护的时候,极大地降低了维护成本和维护时间

8)有利于各层逻辑的复用;

【 缺点 】

1)代码量增加,开发周期延长;

结合框架的使用,在以下的分析讲解中会分层说明在项目中的作用;

I.表现层

l    IM相关界面的逻辑,IM数据展示和界面操作交互;

l    功能模块化,UI界面区分出消息列表、会话列表、用户连接状态、消息未读数、帖子信息展示等功能;

II. 业务逻辑层

l    具体的操作逻辑,对表现层提供接口调用,链接数据访问层,承上启下;

l    用户输入的数据通过业务逻辑层的处理发给中间层;数据层返回的数据通过业务逻辑层发送给界面展示;

l    接口单一职责、易于业务逻辑维护;

l    该层为核心逻辑层,设计成两部分:

(1)主要逻辑管理类组和API接口,按IM主要功能设计管理类,处理表现层需要的逻辑处理;

(2)转换功能,主要职能是数据转换、加密、数据缓存等,数据转换的设计是为让表现层隔离IM SDK的数据类,将IM SDK有自己的消息bean类转换为我们自己的bean类,已到达上层与SDK的隔离,这样升级SDK或替换SDK,对上层无影响;

III.数据访问层

l    数据来源层,IM SDK为我司自研的IM 体系微聊SDK,Walle库为我们Android平台组自研跨组件通信路由框架,组件化节藕神器;

b)      细解业务逻辑的管理类组和API接口

上面讲了IM框架各层的作用,为便于大家的理解将业务逻辑的管理类组和API接口给大家讲解一下,这部分是我们的核心逻辑,对设计开发较重视,将设计的重点放在高可用、易维护、可扩展、操作明确简单上,更好的赋予他们承上启下的责任。

UZN7zmY.png!web

如上图所示,将IM的业务按主要功能拆分,明确职责,职责单一,使用类名和注释,可以快速定位功能逻辑,上层介入使用简单明了,下层调用逻辑更明确,有利于单元测试,具体设计细节:

1) 接口统一:统一接口类为IMClient,是IM的功能代表,包含多个业务逻辑的管理类成员变量,与上层交互统一使用此类交互,调用方使用简单,可读性操作性高;

2) 代码导航更容易:以IMClient.get...()的方式实现,可发者在使用导航开发更容找到相应方法,一个点就知道后续调用了,例如获取用户信息,IMClient.getIMUserHandle().getUserInfo(),调用方在很短的时间就可以熟悉接口方法,缩短API的学习时间也是我们封装业务逻辑要考虑的;

3)职责单一明确:熟悉总结IM的业务逻辑,提炼成高可用的功能接口,先将场景罗列,再抽象,明确管理类的职责,使管理类易维护可扩展,有利于单元测试;

4)静态代码块初始化:将初始化操作放在IMClient的静态代码块中执行,当类在首次使用时才可初始化IM逻辑,相对初始化操作放在Application的onCreat中的方式有3个优点:

I. 不增加APP的冷启动时长,因为初始化操作一般放在Application的onCreat中,是会影响启动耗时的,采用这个方式可以避免;

II.可以避免将IM的类打在主dex中,一般在Application的onCreat中执行初始化的相关依赖类会被keep到MainDexList中,如果太大了,也会报方法数越界65535的错误,所以应该尽量减少他的大小;

III.对上层是闭合的,有效隔离上层逻辑,上层无法修改和不用关心IM的初始化。

上文框架限于篇幅,只粗粒度进行了介绍,省略了一些细节部分。下面进入我们另一个重要内容IM业务线拆分的讲解。

面向并行开发的IM拆分

1.并行开发IM存在的问题及拆分原因

58APP的项目工程多业务线并行开发模式,然而IM的native聊天一直是多业务线共用一套逻辑,同一个聊天界面,判断不同的业务线和不同的场景需求显示不同UI,代码统一由我们无线侧开发测试,随着业务的发展,共用逻辑就渐渐暴露出了许多问题:

1)   无法满足业务线强个性化的需求;

2)开发测试由于统一由无线侧执行,资源有限,无法快速完成业务线的需求,造成需求等待的情况;

3)业务线的开发资源无法共同参与到IM的开发维护建设中。

为了解决这些问题,我们决定将IM做拆分,由我们无线侧提供基础服务框架,支持业务线自行开发,做个性化需求。

2.IM拆分目标

针对并行开发存在的问题我们需要制定IM拆分的初步方案,启动拆分之前,前先明确目标,以保证后期执行方案的正确性,目标包括:

1) 搭建聊天界面基础框架服务

2)面向业务线需求开发接口

3)接口简要、易懂

4)使用组合模式组件化思想让界面操作更灵活

5)单一职责设计

6)易扩展,易维护

3.IM拆分转换器

之前所有的聊天场景都是使用的唯一的聊天详情页,为满足业务线的独立开发,必须要将聊天详情页由业务线自行控制开发,这就需要多个聊天详情页,也就是业务线有自己本业务需求场景的聊天界面,在58APP中任何业务场景启动的目标页都是通过规范协议由跳转中心控制的,聊天界面也应该采用这个方式,那如何让业务场景控制启动指定的目标聊天界面呢?如达到房产业务场景启动房产聊天界面、招聘启动招聘的聊天界面?为了实现这个能力我们设计了拆分转换器,具体看下面的设计:

vMfEnq7.jpg!web

a) 任何业务场景启动IM聊天界面时都使用跳转协议;

b) 跳转协议由跳转中心控制解析,解析出具体的业务参数;

c) 设计配置表,配置关系是业务参数(key)对应具体聊天界面协议(value);

d) 配置表本地缓存默认的,由server端下发更新,动态可控,灵活配置,同时还可以快速实现上线时灰度控制风险,如果有问题可以及时降级到原聊天界面;

e) 通过业务参数找出对应的聊天界面协议,启动相应的聊天界面,就可以实现房产业务启动房产业务线聊天界面的。

该设计方案的优点:

I.入口统一:所有的聊天界面都适用跳转协议通过跳转中心控制,统一维护管理;

II.控制配置表动态更新化:将配置表由server 端控制,动态更新,可控性高;

III.跳转协议业务可控:指定具体跳转到哪个聊天界面,也可扩展添加聊天界面;

III.可灰度降级:任何大需求在开发时都需要考虑到灰度可控,避免带来灾难,正因为前面讲到的配置表由server端控制,所以一旦发现问题,完全可以由server端更新配置表,将启动聊天界面的协议降级到跳转原聊天界面,有效控制线上风险。

上面讲解是启动聊天界面的逻辑转换器,这是实现拆分的关键前提,下面开始讲解我们的聊天界面,它又该如何设计如何实现呢?那就一起来看一下吧。

4.IM聊天界面基类模型设计

构建界面模型,直接上图:

mEZzYbY.jpg!web

a) 基类IMChatBasePage:基类继承自Activity定义为抽象类,符合开闭原则,有利于以后扩展,可控制子类逻辑方法实现和复用,含有界面上下文对象,由组件组合而成,同时实现供对子类暴露的API接口(作用下面详细讲解);

b) 基类界面上下文:定义为当前界面的信息、环境资源,包含以下部分

l    IMMsgOperator:负责消息相关操作,发送漫游消息,插入本地消息,临时展示消息,为什么把这个类放在上下文中呢,因为界面操作一个主要的功能是收发消息;

l    IMSession:当前界面的信息类,包含聊天对象的信息,聊天场景的业务数据信息;

l    Context:Activity的context,用于资源文件的操作处理,和信息获取;

l    组件通信路由IMRouter:用于组件间通信使用,可以使组件解藕相互数据传递交互,区别与EventBus的是该路由设计为界面相关,也就是组件间的数据传递作用域为当间界面,这样可以避免多个界面共存时数据共享导致的混乱;

d) 组件组合:整个界面的功能按照组合模式组件化的思想设计,基类由组件组成,下面会详细讲解该思想;

e) 对业务线API接口:这是使用的面向接口开发,将对业务线子类提供的功能API都使用接口的方式实现,可读性和可扩展性高;

这样我们的基类基本上就设计完成,业务线开发继承基类IMChatBasePage,可以快速实现聊天的基本功能,同时又可以灵活扩展自己的功能,任意添加、删除、组合组件,满足个性化需求开发。

5.IM聊天界面基类的组合模式组件化思想

业务线的小伙伴要基于我们的基类做开发,开发个性化需求,所以基类界面逻辑要灵活可控,为实现这点我们可以借鉴一些优秀的思想,比如我们的Android系统API,Fragment当时的出现就是为了解决Activity的模块片段化,使Activity可以任意组装拆卸,add、replace、remove...动态控制Activity界面的显示,灵活性大大提高,View和ViewGroup的组合模式,表示部分与整体的层次,介于该思想结合当下较类似的组件化概念,动态可控,我们将聊天界面的设计思想确立为组合模式组件化思想。

N7JFZvV.jpg!web

a)     组件化:把整个聊天界面按照功能组件化,对功能逻辑可以动态添加删除,每个组件都是完整的功能体,职责明确,代码解藕,可以单独运行,组件之间可以数据传递,目前基类抽象出的组件有以下部分:

jEZvimV.png!web

b)    组合模式:Android中重要的界面UI类View和ViewGroup就是采用组合模式,我们正式借鉴它的使用,创建两个重要的类IMUIComponent和IMUIComponentGroup,IMUIComponentGroup包涵多个IMUIComponent,IMUIComponentGroup继承自IMUIComponent,可以使我们的界面功能结构整体层次清晰,模块调用简单,节点自由增加、删除、替换;

c)       组件赋能:

l    基类界面上下文IMChatContext,如上文讲解的IMChatContext的作用,提供当前界面的信息和环境资源;

l    组件绑定的View,处理界面UI逻辑;

l    组件的生命周期,主要绑定的是Activity的生命周期回调,实现生命周期方法,处理适当时机的逻辑;

l    组件路由方法回调,调用路由传递组件间的数据和交互,实现组件间的解藕。

d)   每个组件可用MVC、MVP、MVVM等设计模式,正如我们借鉴的Fragment,每个Fragment都可以在使用处理UI、逻辑和数据的设计模式,以便提高我们组件的易维护性;

组合模式组件化思想我认为可作为界面通用设计思想,将界面拆分开,可以同时分工多个人开发,对于版本需求迭代开发维护都比较友好,尤其是界面逻辑较复杂的场景。

6.自定义消息类型的优化实现

聊天界面消息列表由多个消息类型组成,通常包括文本、语音、图片、地理位置、卡片消息等多种,通用消息类型由基类实现,那如果业务线开发想要自定义一种消息类型如何实现呢?定义一种消息类会在多个场景使用,如消息中心(会话列表)、通知栏、聊天界面列表,如何一次定义多个场景能适配显示呢?

a)    代理模式使消息类型扩展工作简化

带着以上问题讲解,首先是聊天界面列表,聊天界面列表使用ListView和ListAdapter实现,ListAdapter一般写法是列表信息多个Item组成,每个Item类型由ViewHolder和消息Bean组成,构建一个ViewType类型需要在ListAdapter的getItemType的方法中添加映射关系、在getViewTypeCount()方法中+1,操作相当繁琐,为了简化操作,我们使用了代理模式,保留原有ViewHolder的逻辑,创建代理类代理Viewholder,创建带有代理管理能力的Adapter,可以注册代理类,方法getItemType和getViewTypeCount的返回值交给代理管理类处理,达到定义一个Viewholder后,不需要关心Adapter的ItemType操作,简化操作。b)      装饰模式让消息类型可适配多场景显示

单个消息类型的组成包括Viewholder、消息Bean,而这两个类需要处理好多业务逻辑,如是否为当前版本可支持,消息中心列表提示文案,View的展示,消息协议转换为消息Bean等操作,这一系列的操作让新开发者摸不着头脑,为解决这个问题我们使用装饰模式,创建装饰类,引用ViewHolder和消息Bean对象,抽象出这两个类的功能实现,让装饰者完成他们的调用,将需要实现的职责功能明确,将分散逻辑聚合在装饰者中,让开发者自定义消息功能思路更清晰。

c)    工厂模式注册让消息类型可扩展

一个消息装饰者代表一个消息类型,是消息体的UI、数据、交互能力的代表,使用工厂模式将消息装饰者统一注册管理,易扩展维护。

通过使用我们常用的设计模式解决我们上面提到问题,便于业务侧的消息类型开发,可自定义实现消息类型。

7.IM面向并行开发后的收益

IM面向并行开发实施后,各业务线可自行开发IM需求,同时有5大业务线并行开发,有效解决业务线IM相关需求等待阻塞的问题,整体快速提高IM的用户体验。凭借我们基础服务的搭建,也降低了业务线同学的开发成本并且对控制了IM前期需求并发期的出错概率,同时有降级策略预防风险。

总结

技术服务于业务需求,IM的框架优化和面向并行开发IM拆分都是我们APP中的面向业务开发实践,经过了长期的发展演进,大家在开发IM时有同样的业务环境应该考虑并行研发和业务需求的个性化,以上实践内容可做参考。我们一直在努力优化让IM技术满足现在和今后的业务需求,并行开发可考验我们现有技术框架是否能满足IM个性化需求,对遇到的问题和解决方案我会不断总结实践创新,希望今后能多一些分享交流。

作者简介

张志新 / 58同城Android客户端架构师

主要负责客户端无线平台客户端开发维护、性能优化、架构设计、重点项目和版本负责,长期参与IM客户端需求开发迭代, 同时发表过多篇关于IM应用实践的文章。

“全力以服”——58同城14周年司庆昨天结束啦!小编亲眼见证了现场的火爆,羡慕我司同学,不仅可以吃到串串、各种小零食、小蛋糕,土特产满足嘴巴,还能参加各种小游戏和抽奖活动获得神奇历、 1 4周年限量帮帮 ,听说还有好多同学拿到延参法师亲笔题字!

jUbUB3A.jpg!web

I3ieA3u.jpg!web

uiiAzmr.gif

ri6ZZj3.jpg!web

AJjeYnv.jpg!web

我们技术直男送口红的活 动可以说简直不要太火爆,10只口红+400支串串一扫而光,羡慕抽到口红的幸运鹅儿,也羡慕品尝到串串的吃货们......

ArQB73Y.jpg!web

IJNVNzF.png!web

看技术直男给大家挑串串的样子是不是又憨又萌 NVZVzuv.png!web

线上留言集赞送口红活动结果也热腾腾出炉啦 ,恭喜以下两位同学,请联系小编领取奖品嗷~

ERF7Rfy.png!web

司庆活动我们共收到160+条留言,满满的都是对公司、 对技术同学的爱和祝福,再次谢谢大家对【58技术】的支持。

最后,借用热评同学的话送给大家:14年,技术改变生活,愿58越来越好,愿58技术同学聪明但不绝顶~ veEfYjn.png!web

Nb2iMvQ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK