我们在 2016 年重写的 Uber 乘客 App 带来了丰富的功能和流畅的体验,并支持 Uber 的一系列产品(从 uberPOOL 到 uberXL)和未来体验可扩展性(如JUMP Bikes)。Uber 乘客 App 适用于全球市场,支持 50 种语言和 30 种支付方式,并且这些数字还在增加中。
尽管我们让这个 App 尽可能高效,但它的功能还是导致其体积超过了 60MB,使用了更多的网络带宽,并要求乘客的手机需要具备特定的硬件性能。
尽管越来越多的全球用户在使用我们的新 App,但我们的数据显示,拉丁美洲、印度和中东地区的乘客设备和网络连接常常不能满足要求。例如,56%的 Uber 乘客使用2015年版或更早版本的安卓手机。在这样的硬件上,我们的新乘客 App 可能无法提供最佳体验。我们希望为最广泛的乘客提供服务,尽可能给他们提供最无缝的交通体验。
为了应对这个挑战,我们构建了 Uber Lite(Uber 精简版),这是为那些使用老款安卓设备以及网络基础设施可能无法提供可靠的 LTE 数据连接的地区而设计的。
图 1 在世界范围内,40%的 Uber 乘客使用安卓手机,其硬件相当于 2015 年或更早的版本
开发 Uber Lite 背后的动机
2017 年,Uber 组建了一支团队,成员来自 Uber 的研究、设计、产品和工程团队,旨在解决在低速网络连接地区使用早期版本安卓设备乘客的痛点。在研究人员和设计人员着手了解乘客行为并提出简化的用户界面的同时,工程人员评估了现有的乘客 App,看看是否可以通过重构和精简让它变得更加轻量一些。
深度工程分析显示,只是简单地精简 App 是不够的。相反,我们提出了一个全新的架构,把安卓客户端的通信职责委派给精心编排的轻量级后端,这个后端专门为我们基础架构提供网络调用服务。这个新的移动后端通信模式和信息架构也可以减少带宽的使用,而且不会增加安卓应用程序的大小。
我们的工作促成了2018年Uber Lite的发布,也涉及对全球各地乘客需求的研究,并考虑了他们可用的手机和无线网络。从一系列严格的要求开始,我们的工程团队定下了设计决策,要最大限度地减少应用程序对手机和网络资源的需求。Uber Lite 反映了我们对超越地区和经济的交通需求的认识。
产品设计注意事项
我们最新版的乘客 App 与那些使用最新手机硬件的乘客的需求保持一致,他们能够访问快速数据网络或享受低价的数据套餐。但是,真实世界具有多样性,数据显示,仅仅在印度,就有 4 千 6 百万 Uber 乘客使用安卓手机,这些手机的配置相当于 2013 年或更早的版本。在世界范围内,40%的 Uber 乘客通过安卓手机进行预订,这些手机的硬件可以追溯到 2014 年或更早,而 33%的预订是通过 3G 数据网络进行的。
针对这些情况,为了设计一个高性能的应用程序,我们坚持了这三个指导原则:轻量(light)、即时(instant)和简单(simple)。
轻量
基于我们的研究,我们确定新的 App 应该具备轻量级的观感和操作,下载使用尽可能少的带宽,安装使用尽可能少的内存。
大小少于 5MB:把乘客 App 的下载包大小保持在 5MB 以下(所需的内存大小小于三张自拍的大小),保存在设备上的则保持在 25MB 以下,以加快应用程序的安装和下载速度,无论网络环境恶化还是使用 2015 年或更早的安卓手机都没关系。
点选地图:Uber Lite 只显示地图,乘客在必要时选择查看基本的实时地图。这种体验的前提是减少地图的使用,以减少对数据网络的要求。
由****服务器驱动的客户端:Uber Lite 使用了一个服务编排层和后端组件来计算和呈现需要从 Uber 平台获取和编排大量数据的作业。这种工程策略将网络有效载荷减少到不到 1 个最大传输单元(maximum transmission unit,简称MTU)。
即时
新的 App 应该具备很快的响应速度,即使是在 3G 无线网络中。
急切****的要求:新的 App 要求在两个相继出现的屏幕之间的切换响应时间不超过 300 毫秒。
保留单一 Dalvik 可执行文件(Dalvik Executable,简称DEX):在安卓应用程序包(Android application package,简称APK)中的方法总数超过 65536 个时,就会变成多 DEX。智能编译技术的使用与高效库及独立设计相结合确保了 Uber Lite 可以使用单一的 DEX,并具有乘客 App 所有的核心功能。
实时通知:Uber Lite 支持实时通知,可以给乘客传递重要的告警信息。
简单
使用一个简单易懂的 5 步流程为乘客带来良好的使用体验,这样可以获得更好的成行比率(完成的乘行与收到的会话之前的比例),这与使用更强大但在较旧的硬件上或在较慢的网络中性能不佳的应用程序形成了鲜明对比。
引导式接****车:Uber Lite 通过使用兴趣点(points of interest,简称 POI)而不是输入接车位置简化了目的地的输入。POI 往往离新兴区域的实际位置很近,易于司机辨识,使它们成为接车的有用地标。
可点击的目的地:Uber Lite 根据乘车人的乘车历史记录和乘车频率呈现目的地列表,并通过缓存它们让乘客能够离线访问并在恶劣的网络条件下进行快速选择。新的 App 用户界面还可以让用户点击推荐的目的地,而不是让用户输入目的地。
安全:乘客能够轻松地与家人及朋友分享他们的行程,与完整版乘客 App 中的分享行程状态功能类似。
语言选择:本地化是 Uber Lite 设计的核心,内置了包括葡萄牙语、西班牙语和印地语在内的多种语言。
图 2 Uber Lite 使用乘客附近的地标简化了目的地输入
App 瘦身
对任意一款应用程序来说,各种库都占据了很大比例的体积,因此,在构建 Uber Lite 时,我们非常关注要包含哪些库。我们仔细评估了大量 Uber 开发库及第三方库的大小、内存和网络使用量。因为我们有独特用例需求,并且对 App 体积和代码方法数量有非常激进的目标,所以我们和不同的内部库作者合作,把它们拆分成更小的模块,这样我们就可以只使用真正需要的功能。我们非常谨慎地选择最基本的已经模块化的库。
例如,我们使用RIBs——Uber 的一个开源跨平台移动架构框架——来实现我们的 App 架构。我们通过 RIBs 的插件框架将乘客 App 的核心代码与非核心代码区分开来,从而形成一个系统,其中的核心流程(基本功能)可以与非核心代码完全隔离。不过,这个框架增加了网络调用有效负载大小。为了避免 Uber Lite 也出现这个问题,我们决定不使用 RIBs 的插件框架。
此外,由于 Uber Lite 涉及的屏幕切换会少一些,因此,我们只在 RIBs 屏幕栈中创建了一些小模块,只包含原生的屏幕切换。这样就可以去掉大约 200KB 大小的额外依赖项。
除了限制库的大小之外,我们还通过以下方式减小 Uber Lite 的体积:
只传送特定于应用程序位置的资源(如字符串),以避免应用程序变得太大;
通过 Uber 现有的 linting 结构使用矢量图像格式,避免 PNG 资源签入;
从合成访问器和协变覆盖等操作生成的 Uber Lite 字节码中删除多余的方法。
选择架构设计
通过研究其他公司现有的轻量级应用程序,我们看到了三种设计范式:WebView、带有服务器渲染 UI 的原生 App 架构和标准的原生 App 架构。在探索了这三种不同的选择之后,我们决定使用原生 App 设计。
使用原生 shell 虚拟机,并从服务器推送内容和 UI,这意味着 App 二进制文件中不包含产品代码。这个方法的优势是 App 的下载包和磁盘占用空间都很小,并且能够在不导致原生代码膨胀的情况下添加新功能。不过,持续向乘客手机推送 UI 组件会导致网络开销变大,因此,我们探索了以下的替代方案:
React Native/Flutter:尽管库很灵活,但其大小在 6 至 8MB 之间,所以不考虑这个方法。
通过Uber的移动网站提供 WebView:在我们自己开发的 Web 界面上使用 WebView 层最初看起来更有吸引力,但在经过试验之后,我们发现了几个问题,如:跨不同安卓版本的碎片化问题、无法整合关键功能、性能低下,以及难以使用现有的移动基础设施。
尽管我们决定使用原生 App 方案,但对于非关键流程,我们仍然使用了基于动态 UI 的框架,这些流程是对核心乘车体验的辅助,例如支付、帮助和支持。
Uber Lite 信息流
对于 App 来说,最慢的操作通常是从网络获取数据,而发展中市场的低网速加剧了这个问题。在构建 Uber Lite 的信息流时,我们用了 8 个关键设计决策来适应 2G 网络:
使用服务器端事件进行后台更新。后台更新对于在不影响乘客体验的情况下保持数据刷新来说是至关重要的。从服务器推送后台更新让我们可以掌控更新的时间和频率。
提前推送信息。上线后立即推送信息(如乘坐的类型和支付选项)可以减少网络调用开销,并通过在离线时也能够在应用程序 UI 中提供产品信息来改善乘客体验。
在发生变更时推送信息。我们实时推送有关状态变化的信息给应用程序,因此,乘客在司机接受其行程时就收到通知。
每个屏幕只允许一个网络请求。联合网络请求可以提高网络性能,增强乘客体验。
使用单个 TCP 连接。在 2G 网络上,其上传速度比下载速度慢得多,单单设置连接就有可能比传输数据要耗费更多的时间。保持数据请求和响应的大小少于 1 个 MTU,以确保只使用一个 TCP 连接就足够了。
避免冗余通信。避免重复和不必要的信息可以减少网络使用。比如,不要进行定时轮询,而是只在状态变化时发送信息,这样有助于避免冗余通信。
在后端决定操作。让后端决定需要向应用程序发送什么内容,而不是在 App 中进行不必要的计算。
在客户端缓存数据。Uber Lite 缓存静态数据以避免网络调用,如特定用户的产品和支付配置信息。此外,我们还在用户处于 wifi 网络下以及手机电量充足时通过智能缓存位置信息来实现离线搜索。
图 3 Uber Lite 预加载数据(如可用的产品类型),以免因过度的网络调用而给应用程序带来负担
接下来要做的事
Uber Lite 的旅程才刚开始,我们看好它的未来前景。随着时间的推移,我们将继续构建新的功能,同时寻找方法来控制应用程序的大小,并坚持我们的设计原则:轻量级、即时和简单。下面,我们来讨论一些未来 Uber Lite 要添加的内容。
用于非常规流的 WebView/动态 UI
我们开始研究如何为了某些非常规流在 App 中使用 WebViews 或动态 UI 框架。我们希望在 WebView 中尝试不同的缓存策略,让它们在慢速网络中更快地运行。优化缓存应该有助于 App 在不影响其大小的情况下进行扩展。选择原生体验还是优化 App 大小是一种有意识的权衡,我们希望保持本地用户最重要的流并提供更流畅的体验。
ProGuard、ReDex 和应用程序包
我们也在探索第三方库瘦身策略。尽管我们可以轻松地清除 Uber 自身的内部库,但是像 ReactiveX 和 Dagger 这样的开源库仍然是 Uber Lite 大小的重要组成部分。我们已经使用ProGuard来优化 APK,同时也在积极寻求使用 ProGuard 和ReDex进行进一步优化。与此同时,我们也在探索新的解决方案(比如 App 捆绑包)以便能够快速发布新功能。
工具
保持小体积的 App 不是件容易的事情,一次大的提交就可以摆脱我们的大小限制。为了保持我们的设计原则,我们积极致力于开发工具来避免多 DEX 文件、生成 App 大小指标、删除未使用的依赖项,以及提供依赖包白名单(这些白名单依赖包通过传递性避免了多余的依赖项)。我们还增强了 CI 管道来抛出这些错误,这样开发人员就可以获得拉取请求时实时反馈。
查看英文原文:Expanding Access: Engineering Uber Lite
评论