来源 | Facebook Engineer
不好意思,标题党了。其实这篇文章所讲述的是脸书重构 iOS Messenger 应用的一些思想和方案,通篇并未提及 React Native。不过文中说明了新的版本完全通过原生来实现,而并没有采用脸书自己的 React Native,问题可见一斑。这也是许多开发人员在推上的一个槽点,甚至开玩笑说脸书正准备杀死 React Native,兴许确实如此中。React Native 的出现有其历史机缘,也曾经辉煌过,只不过时代变得太快,新事物来得太快,老的终将沉没。要不,我们拿什么去搞 KPI 呢?
友情提示:请参考原文阅读,原文有几个图和视频。
• 我们很高兴推出了新版本的 iOS Messenger。为了让 iOS 版本的 Messenger 应用更快、更小、更简单,我们重构了架构并重写了所有代码,这是一项极为罕见的工作,涉及到公司很多工程师。
• 与之前的 iOS 版本相比,新版 Messenger 的启动速度快了两倍,而包的大小只有原来的四分之一。我们将 Messenger 核心代码减少了 84%,从 170w 行减少到 36w 行。
• 为了实现这个目标,我们尽可能使用 native OS,使用 SQLite 支持的动态模板来重用 UI,使用 SQLite 作为通用系统,以及构建服务器代理以充当 Messenger 和其服务器功能之间的通用网关。
Messenger 是在 2011 年成为独立应用程序。当时,我们的目标是为用户构建功能最丰富的体验。从那时起,我们增加了付款,相机效果,故事,GIF 甚至视频聊天功能。但是,每月有超过 10 亿人在使用 Messenger,这个表面上看起来很简单的全功能通讯应用程序在背后却要复杂得多。而帮助我们构建、测试和管理所有这些功能所需的后端使该应用程序变得更加复杂。该应用程序的二进制文件在鼎盛时期的大小大于 130 MB。庞大的代码使应用的冷启动速度变得很慢,尤其是在较老旧的设备上 -- 同时由于有 9 个不同的标签,这对于使用该应用进行导航的用户来说变得比较棘手。
在 2018 年,我们通过 Messenger 4 的发布对界面进行了重新设计和简化,但我们还想做更多。我们重新思考了如果我们从头开始,在当前应该如何去构建通讯类应用程序。自从我们十年前开始开发 Messenger 以来,发生了什么变化?事实证明,很多。实际上,移动应用程序的编写方式已发生根本变化。因此,在去年的 F8 大会上,我们宣布了我们打算让 Messenger 的 iOS 版本变得更快,更小,更简单的意图。我们称其为 LightSpeed 项目。
为了构建 Messenger 的新版本,我们需要从头开始重新构建体系结构并重写整个代码库。这项重写使我们得以利用移动应用程序领域自 2011 年以来的重大进步。此外,我们还能够利用我们在过去几年中开发的最新技术。从今天开始,我们很高兴在接下来的几周内在全球范围内在推出最新版本的 iOS Messenger。与之前的 iOS 版本相比,新版的 Messenger 的启动速度是它的两倍*,大小仅为前者的四分之一。通过这次新的迭代,我们重新构想了如何构建 Messenger,并从头开始了新的客户端核心和新的服务器框架。这项工作帮助我们提升了自己的最先进技术,新的代码库旨在在未来十年内实现可持续性和可扩展性,为隐私消息传递和跨应用程序的互操作性奠定了基础。
更小更快
我们首先提出 Messenger 应该是一个简单的、轻量级的实用程序。有些应用是沉浸式的(视频流,游戏);人们会花费数小时使用它们。这些应用占用了大量存储空间,电池时间等,因此需要进行权衡。但是消息只是一小段文本,发送时间不到一秒钟。从根本上讲,通讯应用程序应该是手机上最小,最轻量的应用程序之一。秉承这一原则,我们开始寻找使 iOS 应用显着缩小的正确方法。
不管设备类型或网络条件如何,小型应用程序的下载、安装、更新和使用速度对于使用者来说都应该很快。小型应用程序也更易于管理、更新、测试和优化。当我们开始考虑这个新版本时,Messenger 的核心代码库已增长到 170w 行以上。仅修改几个部分的代码是不够的。
最简单方法是剥离多年来我们添加的许多功能,但是对我们来说,保留所有最常用的功能(例如群组视频通话)非常重要。因此,我们退后一步,研究了如何应用过去十年中所学到的知识以及我们对当今人们需求的了解。在研究了我们的现状后,我们决定回望过去,并深入研究应用程序本身的基础结构。
完全重写代码库是一项极为罕见的工作。在大多数情况下,重写应用程序所需的大量工作只会使效率产生的实际收益很小(如果有的话)。但是在这种情况下,我们早期的原型开发显示我们可以从中大大获利,这促使我们尝试去做这件事情,而同样的事情在同等量级的应用上很少实践。这不是小事。从最初由少数工程师开始,LightSpeed 工程最终需要 100 多名工程师来完成和交付最终产品。
最后,我们将 Messenger 核心代码减少了84%,从 170w 行减少到 36w 行。我们通过重构以适应简化的体系结构和设计来实现这一目标。虽然我们保留了大多数功能,但随着时间的推移,我们将继续引入更多功能。更少的代码行使该应用程序更轻巧,更快,而简化的代码库意味着工程师可以更快地尝试更多创新。
更简单
我们的主要目标之一是最大程度地减少代码复杂度并消除冗余。我们知道统一的体系结构将允许全局优化(而不是局部优化每个功能)并允许以更聪明的方式重用代码。为了构建这种统一的体系结构,我们确立了四个原则:使用OS,重用 UI,利用 SQLite 数据库并推送到服务器。
使用原生功能
移动操作系统仍将迅速地发展着。由于用户需求和竞争压力,系统将不断添加新功能和各种创新。构建新功能时,通常很想在操作系统之上构建一层抽象,以弥补功能差异,增加工程灵活性或创建跨平台用户体验。但是现代的操作系统通常可以满足许多需求。诸如渲染、代码转换、线程和日志记录之类的操作都可以由操作系统处理。即使有更快的自定义解决方案,我们也会使用操作系统来对全局指标进行优化。
尽管 UI 框架功能强大且可以提高开发人员的生产力,但它们需要不断维护,以适应不断变化的移动 OS。我们没有重新设计轮子,而是使用了 native OS 上可用的 UI 框架来支持更广泛的应用程序功能需求。通过避免缓存/加载大型定制框架的需求,不仅减小了尺寸,还降低了复杂性。native 框架不必转换为子框架。我们还使用了许多 OS 原生库,包括 JSON 处理库,而不是在代码库中构建和存储我们自己的库。
总体而言,我们的方法很简单。如果操作系统做得很好,我们就使用它。我们充分利用了操作系统的全部功能,而无需等待任何框架公开这些功能。如果操作系统没有相应功能,我们将寻找或编写最小的库代码来满足特定需求,仅此而已。我们还采用了依赖平台的 UI 和相关工具。对于任何跨平台逻辑,我们都使用 C 代码内置的操作扩展,这些扩展具有高度的可移植性,高效性和快速性。我们将此扩展程序用于所有全局次优的类系统功能或操作系统未涵盖的所有功能。例如,所有特定于 Facebook 的联网都在扩展程序的 C 代码中完成。
重用UI
在 Messenger 中,我们有多个相同 UI 体验的版本。例如,在该项目开始时,我们有 40 多个不同的联系人列表页。每个页面的设计都有细微的差异,具体取决于渲染等因素,并且每个页面都必须进行适配以支持横向模式、Dark Mode和可访问性等功能,这使我们的工作量增加了一倍。这意味着有很多视图,这些视图在 Messenger 之类的应用程序中占了很大的比例。为了简化和消除冗余,我们限制了设计以强制为不同的视图重用同一结构。因此,我们只需要几类基本视图,并且这些视图可以由不同的 SQLite 表驱动。
在当前的 Messenger 中,联系人列表是一个动态模板。我们可以更改页面外观,而无需任何其他代码。每次有人加载列表时(要向群组发送消息,阅读新消息等),应用程序都必须与数据库交互以加载适当的名称,照片等。应用程序无需存储 40 种屏幕设计,数据库现在包含有关如何根据要加载的各种子功能显示不同构件的说明。这个联系人列表页面可扩展以支持大量功能,例如联系人管理、组创建、用户搜索、消息安全性、共享、故事共享等等。在 iOS 中,这是一个单视图控制器,有适当的灵活性来支持所有这些需求。在我们所有的设计中使用这个更优雅的解决方案,可以帮助我们减少大量代码。
使用 SQLite
大多数移动应用程序将 SQLite 用作存储数据库。但是,随着功能的增长,每种功能最终都有自己独特的存储,访问数据和实现相关业务逻辑的方式。为了构建通用系统,我们从桌面世界中汲取了一个想法。我们没有让数十个独立功能在应用程序上构建自己的缓存以存储信息,而是利用 SQLite 数据库作为通用系统来支持所有功能。
从历史上看,协调各种功能之间的数据共享需要开发自定义的复杂的内存中数据缓存和事务子系统。在数据库和 UI 之间传递此逻辑会降低应用程序的速度。我们决定放弃该方法,而只使用 SQLite 并让它处理并发、缓存和事务。之前我们支持从一个系统直接更新活动中的好友,而另一个系统来更新联系人列表中个人资料图片的更改,以及另一个系统来检索您收到的消息,而现在都是独立地去访问数据库。所有的缓存、过滤、事务和查询都在 SQLite 中完成。UI 仅反映数据库中的表。
这样可以使逻辑保持简单和有效,并限制对应用程序其余部分的影响。但是我们走得更远。我们为所有功能开发了一个集成的架构。我们使用存储过程的功能扩展了 SQLite,使 Messenger 功能开发人员可以编写可移植的,面向数据库的业务逻辑,最后,我们构建了一个平台(MSYS)来协调对数据库的所有访问,包括排队更改,延期或可重试的任务,并支持数据同步。
MSYS 是一个用 C 写的内置的跨平台库,可操作我们需要的所有原语。将所有代码整合到一个库中,使管理一切变得更加容易。我们尝试以一种单一的方式来做事情 -- 一种将消息发送到服务器的方式,一种发送媒体的方式,一种记录的方式等等。使用 MSYS,我们可以拥有全局视野。我们能够确定工作负载的优先级。如加载消息列表的任务比更新某人是否从几天前在线程中读取消息的任务具有更高的优先级;我们可以将高优先级任务上移到队列中。拥有通用系统可以简化我们对应用程序的支持。使用 MSYS,可以更轻松地一次跟踪性能,发现回归并修复所有这些功能中的错误。此外,我们通过在自动化测试方面的投入,使系统的这一重要部分变得异常坚固,从而导致了(在行业中很少见)MSYS 逻辑的 100% 代码覆盖率。
使用服务器
对于不属于上述类别之一的任何内容,我们将其推送到服务器。我们必须构建新的服务器基础架构,以支持客户端上 MSYS 的单个集成数据和同步层的操作。原始 Messenger 的客户端-服务器交互的工作方式与传统应用程序相同,每种功能都有一种明确的协议和数据格式供客户端同步数据并更新到服务器。然后,该应用程序必须实现该协议并协调正确的数据库更新以驱动 UI。这意味着对于应用程序中的每个功能,都有很多(最终是不必要的)自定义平台特定的业务逻辑。
客户端和服务器之间的协调逻辑非常复杂,并且容易出错,随着功能数量的增加而更加容易出错。例如,接收文本消息涉及消息表的更新,关联线程代码段的更新,上次修改时间的更新/线程的弹出,删除可能已插入的任何版本的消息(例如,从通知中删除),删除正在处理消息的任务,解密以及许多其他任务。这些类型的客户端/服务器交互涵盖了应用程序中的所有功能。结果,应用程序最终反复解决了类似的问题,并且在所有这些事件和交互如何组合方面,整个应用程序运行时都具有不确定的行为。随着时间的流逝,我们的应用程序已经变成了繁忙的高速公路。
在当前的Messenger中,我们有一个通用的灵活同步系统,该系统允许服务器定义和实现业务和同步逻辑,并确保客户端和服务器之间的所有交互都是统一的。与客户端上的 MSYS 相似,我们构建了服务器代理以支持所有这些情况,而实际的服务器后端基础结构则支持这些功能。服务器代理充当 Messenger 和所有服务器功能之间的通用网关,而过去,所有客户端功能都使用各种方法直接与服务器对应功能进行通信。
预防将来的代码增长
如今的 Messenger 非常轻巧 -- 代码库已从 170w 行减少到 36w 行。该应用程序的二进制大小现在是原来的四分之一。但是在将新的代码库投入生产之前,我们必须确信它不会仅仅随着添加的修补程序,更新和功能而再次膨胀。为此,我们为每个功能设置预算,并责成我们的工程师遵循上述架构原则来坚持这些预算。我们还构建了一个系统,使我们能够了解每个功能带来了多少二进制大小。我们要求工程师对达到其预算作为功能验收标准负责。按时完成功能很重要,但达到质量目标(包括但不限于二进制大小的预算)更为重要。
构造当前的 Messenger 经历了一段漫长的旅程,并且公司中的许多工程师都参与了其开发。但是,对于使用该应用程序的人来说,它的外观或感觉不会有太大不同。它的启动速度会更快,但仍将是人们期望的相同的出色通讯体验。但这仅仅是开始。
我们在重建 Messenger 方面所做的工作将使我们在迈向未来的过程中能够继续创新和扩展通讯体验。除了构建可在未来十年或更长时间保持可持续发展的应用程序之外,这项工作还为在我们的 App 矩阵中构建跨应用通讯奠定了基础。它还为我们以隐私为中心的通讯体验奠定了基础。
我们要感谢为LightSpeed项目做出贡献的每个人。
文章评论