当前位置: 首页 > news >正文

扩展访问:Uber Lite App开发始末

\"\"

我们在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

相关文章:

  • 嵌入式开发教程,学习嵌入式怎么入门和提高?
  • 5G来之前,视频UGC选择产品解决方案?
  • nodejs处理高并发的原理机制
  • 关于List、List?、ListObject的区别
  • 如何合理的规划jvm性能调优
  • 异步
  • 这一次,彻底弄懂TCP三次握手,四次挥手
  • 线程的等待和唤醒
  • js中forEach回调同异步问题
  • 整行读字符串且需分割计算字符串个数
  • 比特大陆新一轮裁员50%,回应称系人员调整
  • zabbix linux系统模板更新
  • 2019.2.20 c++ 知识梳理
  • 微信全局登录设计与实现
  • 朝鲜APT集团Lazarus通过KEYMARBLE Backdoor瞄准俄罗斯组织
  • 【译】JS基础算法脚本:字符串结尾
  • [Vue CLI 3] 配置解析之 css.extract
  • [译] React v16.8: 含有Hooks的版本
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • chrome扩展demo1-小时钟
  • css系列之关于字体的事
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • overflow: hidden IE7无效
  • Python学习之路13-记分
  • REST架构的思考
  • Ruby 2.x 源代码分析:扩展 概述
  • Vue2.x学习三:事件处理生命周期钩子
  • yii2权限控制rbac之rule详细讲解
  • 编写符合Python风格的对象
  • 彻底搞懂浏览器Event-loop
  • 精彩代码 vue.js
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 马上搞懂 GeoJSON
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 微信公众号开发小记——5.python微信红包
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 原生Ajax
  • PostgreSQL之连接数修改
  • ​批处理文件中的errorlevel用法
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #Linux(权限管理)
  • ( 10 )MySQL中的外键
  • (12)Linux 常见的三种进程状态
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (33)STM32——485实验笔记
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (day6) 319. 灯泡开关
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (二十三)Flask之高频面试点
  • (分类)KNN算法- 参数调优
  • (转)关于pipe()的详细解析
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl