概述
消息业务作为有赞移动的共享业务,在微商城、零售、美业等 B 端 App 中承担着多客服的角色,多客服是有赞为商家提供的连接商家和买家的即时消息客服工具;在精选、有赞客 C 端产品中扮演着用户联系商家的角色。在整个有赞产品中,是商家和用户沟通的桥梁,起着非常重要的作用。
痛点
我们通常来讲把出现在消息会话页面内的内容称做消息卡片,目前消息业务常见的消息卡片有文字、富文本、语音、照片、视频、通知消息,除此之外还有订单详情、推荐商品、核对订单等共计 30 余种消息卡片。目前我们消息卡片都是采用原生来开发,随着业务的日益增多,消息卡片的数量也在成倍的增长,业务方经常提出增加卡片的需求,去开发和维护这些卡片工作量比较大,并且依赖业务方的客户端发版,不够灵活。
现状
目前消息业务在 iOS 端和 Android 端均采用二方库的方式开发,供各个有赞的 App 接入,在 iOS 中采用 Cocoapods 进行二方库的依赖,Android 采用 gradle 来进行二方库的依赖;如果业务方需要增加一种消息卡片从开发到上线按照正常的周期是 1 周到 1 周半,发版周期长、开发效率低。
动态化技术指不依赖 APP 发版,就能进行动态的增加或者修改来更新页面的技术。对于消息卡片这种需要快速迭代、实时调整的业务,动态化具有非常重要的意义。主要优势体现在:
提高人效
缩短版本迭代试错周期
解决版本长尾问题
减少包大小等
整个消息的业务场景如下图所示:
消息聊天以及消息模版都是由消息卡片组成
原生消息卡片
先来说说原生消息卡片的实现方案,这里以 iOS 为例。整体架构设计我们采用 TableView 作为整个页面的容器,采用 MVVM 的架构去设计,架构分为以下模块:
消息卡片数据源配置类 Configurator 负责 VC 和数据源处理类的绑定以及消息管理处理
消息卡片接口管理类负责接收/发送消息数据处理,重发的操作、界面接口(增、删、改、查)
数据源操作类 TableAdapter 负责 TabelView 的代理方式、数据源代理方法的实现,以及一些视图刷新等相关操作
消息卡片内容视图工厂类 ContentFactory 会根据消息类型注册对应的 ContentConfig 进行一个缓存
消息卡片配置类 ContentConfig 是根据消息类型从工厂类中获取的,返回当前消息卡片的宽高(size)、消息体 message、当前渲染的消息卡片视图
消息模型 layout ,根据不同的消息类型生成,会保存当前的消息体以及消息卡片的宽高,为了提升性能,缓存在 TableAdapter 数据源操作类中
消息卡片 cell,根据后端下发不同的卡片类型,从消息卡片配置类中获取不同的 contentView 加到 cell 中,进行消息卡片的展示
消息卡片 contentView,负责消息卡片 UI 布局、数据的渲染、绑定
整个架构如下图所示:
消息原生架构图
方案
关于动态化技术栈的选择
上面介绍了 iOS 端消息卡片渲染架构设计,那么为了让消息卡片具有动态化的能力,但是不会打乱现有原生架构的情况下,我们采用了结合 weex 技术栈来做这件事。为什么用 weex 来提供动态化这样的能力?首先该技术栈已经在有赞移动中扮演者重要的角色,我们团队之前已经做了 weex 无线开发平台,我们团队主导建设了 ZanWeex ,它是一整套解决方案,从开发、构建到发布、热修、数据、监控,全生命周期的平台和工具,目前有赞业务很多模块都是以 weex 来实现,也接入了长连接功能,无须重启 App 实现实时下发的功能,对 weex 有兴趣 的,可以查阅 weex 官网。
因此我们的核心思路是利用 weex 来达到消息动态化的目的。
创建 weex 容器,为了避免容器臃肿, 这里我们没有选择 ViewController 来充当 weex 的容器,而是用 View 来充当容器,里面包裹了一个 WXSDKInstance 进行 weex 的渲染。
原生端创建渲染 weex 的 TableViewCell,将 weex 容器视图添加到 cell 的 contentView 中去,利用 TableView 的重用机制进行 cell 的缓存和重用。
动态化能力
技术架构设计
在架构设计上面,我们在以前原生的 MVVM 的基础上,利用 JS 动态库,配合 weex 达到动态化卡片的能力,做到了 App 不发版的情况下,只需发布 weex 就可以动态添加消息卡片,也可以修改消息卡片的 UI 样式。鉴于这样的设计目标,在这个框架里,主要考虑以下方面:
在不改变原有原生架构的基础上去做消息卡片动态化
页面布局动态化,意思是页面的排版布局,可以通过 weex 端和 JS 端发布来达到动态更新
组件的复用,为了在多种类型的卡片中保持良好的性能,需要对 weex 端容器进行缓存和复用
开发、构建、发布整个流程需要拥有一个完善的平台
JS 动态库
JS 动态库是我们团队出的一个动态化方案的框架,主要功能是提供动态下发的能力;快速修复线上问题;两端特殊复杂的逻辑保持一致等。JS 动态库也是利用 ZanWeex 平台来开发、构建到发布,一整套流程都是完善的,这里我们不去过多的讨论 JS 动态库的相关内容,我们仅仅是用到动态下发的能力,核心思路是:
利用 JS 动态库根据消息类型下发对应的 weex 卡片的 url
利用 JS 动态库根据消息内容和消息类型来计算原生承载 weex 容器的 TableView 的 cell 宽高
利用 JS 动态库下发需要加载 weex 卡片的消息类型
利用 JS 动态库根据消息类型来判断当前的 cell 是否是 weex 卡片 cell
接入 JS 动态库
我们在 JS 动态库中,暴露出获取 weex 卡片对应的 url 方法,入参是消息类型
获取 weex 卡片所在的 cell 的宽高,入参是消息卡片内容和消息类型,JS 将卡片内容解析出来,进行高度、宽度的计算返回给原生。
获取需要支持 weex 卡片的消息类型,原生根据这些类型进行容器 cell 的注册
性能
性能较好,在多个 weex 卡片消息交替出现的时候,为了避免达到性能瓶颈,因此首先考虑到利用原生 TableView / RecyclerView 的重用机制,拿 iOS 来举例,用 weex 的 url 和 TableView 的 identifier 做一个绑定,这样不必自己去维护 weex 卡片的重用和回收。第一版做出来的时候发现,这样在持续加载 weex 卡片的时候 WXSDKInstance 会不停的渲染调用 renderWithURL 方法,这样无疑是消耗比较大的性能去创建 weex 视图。因此我们在 weex 容器中做一个标记,记录上次该容器加载的 url,每次在加载 weex 卡片 cell 的时候去判断当前的 url 是否是上次记录的 url,如果是,就说明这个类型的 weex 卡片之前已经渲染过,WXSDKInstance 实例可以拿到原生传过来的数据进行 refreshInstance;否的话用 WXSDKInstance 进行 renderWithURL,这样可以节省一次 render 重新渲染,从而解决卡片太多,上下滑动的时候会出现短暂的空白现象。
整体流程
消息通道连接的时候,weex 模块去拉取对应的注册的 weex 页面;初始化 JS 动态库,其实也就是拉取 JS 相关的逻辑配置
后端下发消息数据,经过数据解析器将消息经过转化生成 layoutModel 缓存起来
在消息卡片视图工厂类 contentFactory 中,将消息类型传到 JS 端来判断当前消息类型是否需要注册成 weex 卡片
在获取卡片宽高、消息模型的卡片内容配置类 contentConfig 中,将消息类型、消息内容传到 JS 端,返回 weex 卡片宽高
数据操作类去持有 LayoutModel 和 contentConfig 类,在 TableView 代理方法回调的时候,去加载对应的 weex 卡片和宽高,并且将 weex 卡片的 url 作为 identifier 重用标记
TableView 加载 weex 容器的 Cell 时传递消息类型到 JS 动态库,然后返回对应的 weex 卡片 url,然后根据 url 将消息卡片内容传递到 weex 容器端去进行渲染或者刷新
架构设计如下图所示:
消息动态化架构图
优化
目前整个流程已经上线,情况比较稳定,极大的提高了开发效率,增加/修改消息卡片做到完全动态化,但是目前我们的方案也有很多不足之处,比如我们期望更好的性能和更高的运行效率、更加动态化的事件处理能力、更加方便的开发体验,对此我们也做了更进一步的规划建设:
组件创建、布局计算、数据绑定机制、从 JS 端获取数据做缓存的优化,提升性能
规范事件点击回调处理,提供跳转商品详情、订单详情、原生特定页面 router 以及 webView 的事件处理
拆分出动态化框架 SDK ,让其他需要用到的业务可以接入,比如一些活动页面
总结
以上是我们整个消息卡片动态化的整体方案,总结来讲就是在原生架构基础上配合 weex 技术栈、JS 动态下发,达到整个动态化的目的,可以算作一个轻量化的动态化方案,算是在动态化方面做一些尝试,也希望可以和广大开发同学一起交流。
参考链接:
本文转载自公众号有赞 coder(ID:youzan_coder)。
原文链接:
评论