Blog

Blog

PHODAL

【架构拾集】基于混合应用架构的跨平台实时聊天应用

基于 Web 与混合应用框架的架构,设计一个实时聊天工具,并不是一件轻松的工作:

  • 实时性。在创建应用的时候,我们遇到这么一个质疑:你们的聊天应用做不到很好的体验。细问之下,找到了原因:他们之前开发的 IM 是基于类似 Ajax 轮询/WebSocket 的方式开发的,网络质量差,或者不活跃时,就无法保证实时性。
  • 流畅度。在开发的时候,我们还不得不考虑流畅度的问题,聊天应用是一个带有大量消息的应用。一旦消息过多,可能会导致消息滑动不流畅的问题。为了支持获取历史消息,及对大量消息的处理,我们进行了大量的研究。从性能上来看,Web 应用的长列表不可能像原生应用那么好。
  • 用户体验。在开发完的时候, 当业务、开发、测试人员都习惯了微信,那么就会有一种先入为主的想法——觉得这个地方应该这么做,每个地方应该和微信等保持一致,而这些功能并没有在最先的规划中。这一点尤为重要,当我们在做一个类似于 blabla 的应用,应该以既定业务功能为主,其它的需求都是未协商的新需求。

不论怎样,让我们来看看这个应用是怎么设计的吧。

背景

我们所在的团队,是一个框架(混合应用框架、平台化框架、Web 前端框架)的开发团队。业务方需要基于一个聊天的 SDK,开发一个聊天应用。而这个聊天应用,不仅仅是一个业务方所需要的。其它的业务方,都有可能针对于这个聊天应用有定制需求。

技术远景

作为一个框架的底层提供方,我们预期我们开发的聊天应用:

  1. 接近微信、内部聊天工具的使用体验
  2. 方便第三方进行修改,而不需要为每个使用方定制

技术架构

在这个聊天应用的设计过程中,我既承当了部分的业务分析职责,又作为一个 “架构师” 设计整个系统的架构。当然了,我主要做的部分是前端 UI 部分的设计,及对应的与原生部分的接口设计、参数传递等。与此同时,在早期的 POC 阶段(概念验证),为了验证理论的可能性,我还作为一个 Android 开发,测试了一系列的接口。

对应的系统架构设计

  1. 使用 Web 技术来开发 IM 的前端页面,使用原生技术来提供聊天支持。
  2. 将 IM 作为一个独立的 Web 容器抽离出来。修改后,可以替换相应的 Web 资源目录即可;也可部署在远程的服务器上。
  3. 通过原生存储来实现容器间的参数传递。
  4. Angular + Lazyload,可以直接复制、粘贴到其它 Angular 应用中。

针对于第二点做一个补充说明。事实上,这已经是我开发的第二个 IM 应用了。在上一个 IM 应用中,我犯了一个不大不小的错误,将 IM 代码和业务代码混合在一起。导致了,这个 IM 部分的功能,无法在后期直接抽离出来。我写第一个版本的 IM 应用时,由于当时的技术限制(只有 Android 开发能力)、背景限制(只开发应用),而没有考虑到二次复用的问题。不过,由于使用的是 Angular.js,也不得不再次用 Angular 实现一个版本。

适用场景

对应的,写一个简单的电梯演进:

关键因素 描述
对于 想集成聊天应用的团队
我们的 跨平台聊天应用
是一个 可定制的混合聊天应用方案
它可以 方便地集成与修改
但他不同于 基于 Web 的聊天应用
它的优势是 实时的聊天感受、流畅地用户体验

C4 Model

TBC

领域与技术模型

从业务上来看,可以分为这么几部分:

发送文本消息。文本消息的发送,实际上是最简单的功能。只需要调用一个原生部分的接口,我们也可以实现相应的功能。过程中,唯一比较复杂的地方是,对于消息框的高度的处理。这里的文本框,有最大高度和最小高度的限制,即最小是一行,最长是 3 ~ 4 行。当输入的内容是一行的时候,显示的是一行的高度,二行的时候显示的是二行的高度,三行、四行及以上的时候,显示的是三行的可见区域。

文本消息展示。文本消息的接收属于其中最简单的一部分,并不涉及过于复杂的处理逻辑。

发送语音消息。语音消息是几种消息中最复杂的部分,它涉及到长按输入按钮、变换输入按钮、计时、语音动画、上滑取消、下滑继续、松开发送等一系列的业务。在这几个部分中,最复杂的地方便在于长按和滑动相关的计算。

语音消息播放。由于 SDK 本身的限制,所以播放语音的时候,需要交给原生来处理。于是,前端只需要播放相应的动画即可。然而,过程中有一个比较大的坑是,消息会有一个下载的过程。

发送图片消息。关于图片有一些基本的业务需求:

  1. 图片是以附加工具栏的方式,由下往上滑动。为此,需要一个简单的 toggle 来显示和隐藏。
  2. 图片的类型分为两类:一个是从相册中选取,一个是拍照获取。

图片消息播放。由于 iOS 平台对于文件的访问限制,因此提供给前端的是 Base64 编码后的缩略图,点击后又需要显示原来的大图。过程中,即需要获取缩略图的接口,又需要展示大图的接口。

从 UI 来看,我们可以推算出基本的前端数据模型:

  • 发送状态
  • 消息时间
  • 消息内容
  • 发送/接收者
  • 消息负载
    • 图片的缩略图 ID(可选)
    • 图片的大图 ID(可选)
    • 语音消息 ID(可选)
    • 语音时长(可选)

技术实现

前端

从技术上来看,前端比较麻烦的地方在于各个组件的研究。而这时呢,我们使用使用的前端框架是 Angular:

  • 无限滚动。Angular 7 本身是自带的,不过当前(2018.03)还是 ngx-infinite-scroll 好用,其中的 infiniteScrollDistance 需要动态计算。
  • 虚拟滚动。使用的组件是 ngx-virtual-scroller,导是没有遇到什么大坑。
  • 长按输入。网上找了一个 longpress 的 directive,添加了对 touchmove 事件的支持。
  • 自动调整输入框。使用提 ngx-autosize,不过稍微修改了一下。

然后,比较坑的地方就是适配 Android 和 iOS 机型上的问题了。

JavaScript Bridge

为了保持有消息的状态,即创建、发送、取消,需要保持有原有的回调函数。在 Cordova 中,需要通过 keepCallback 来保持这个回调,以多次调用相就的返回值。

同时,值得注意的是,通过 promise 的方式,是不能在一个函数中多次返回结果,因此需要通过 callback 的方式从 JavaScript 获取结果。而在 Angular 框架中,又有外部 callback 需要手动 DetechChange 的问题。

原生

普通的 SDK 封装。

麻烦的地方在于需要多提供一些这么事件:

  • 键盘的显示和隐藏
  • iOS 的键盘高度

针对于 iOS 的键盘高度问题,主要是调用第三方输入法(如搜狗)返回的 scrollTop 不对的问题,便需要在原生中处理这个高度。

踩坑经验

iOS 的键盘问题

输入框使用的是 position:fixed,但是在 iOS 上会有遮挡的问题。网上有太多的解决方案了,最后我们采用的是:

setTimeout(function(){
    document.body.scrollTop = document.body.scrollHeight;
},300);

iOS 的 WebView 滚动问题

iOS 上需要 -webkit-overflow-scrolling: true 来解决 滚动与回弹效果的问题。这个属性用于控制元素在移动设备上是否使用滚动回弹效果.

iOS 的键盘收起影响元素问题

这是另外一个坑,在键盘收到的时候,底部的某个按钮莫名其妙的消失了。只能再加一个 setTimeout 100 来显示出来。

关于我

Github: @phodal     微博:@phodal     知乎:@phodal    

微信公众号(Phodal)

围观我的Github Idea墙, 也许,你会遇到心仪的项目

QQ技术交流群: 321689806
comment

Feeds

RSS / Atom

最近文章

关于作者

Phodal Huang

Engineer, Consultant, Writer, Designer

ThoughtWorks 技术专家

工程师 / 咨询师 / 作家 / 设计学徒

开源深度爱好者

出版有《前端架构:从入门到微前端》、《自己动手设计物联网》、《全栈应用开发:精益实践》

联系我: h@phodal.com

微信公众号: 最新技术分享

标签