这篇文章是a系列涵盖优步的移动工程团队如何开发出最新版本的我们的驱动程序应用程序,代号为碳,碳,是我们执教业务的核心组成部分。在其他新功能之外,该应用程序让我们的人口超过300万司机合作伙伴查找票价,获得指示并跟踪其收入。我们在2017年开始与我们的司机伙伴的反馈结合设计新应用程序,并于2018年9月开始向其推出生产。
当优步决定我们的驱动程序的完整重写,我们将其作为一个有机会退步并查看我们如何改进应用程序的核心技术。我们专注于的一项技术是地图显示,这难以与司机遇到的许多错误涉及。
我们以前的驱动程序应用程序的地图显示遭受遗留软件的典型问题,旨在记住一个目的并随着时间的推移获取新功能。这些功能偶尔会在控制地图显示屏的控制中冲突,从而导致用户的子达经验。
Carbon,我们的驱动程序应用重写,给了我们一个机会重新建立地图,我们提出了一个框架,既又要更容易使用和比以前的实现更可靠。将地图设置为背景层并构建三个不同的组件来调节显示器,证明了一种优雅的解决方案,可以为增长扩大,让我们无缝地实现新功能,使我们能够更好地为我们的驾驶员提供服务。
遗产冲突
当我们在2013年首次推出以前的驱动程序应用程序时,其架构更简单。例如,我们的地图的UI仅显示了驾驶员的位置以及拾取位置。鉴于此有限功能集,有一个单级管理地图是有意义的;在我们的情况下,它被称为MapViewController.。
随着时间的推移,我们在应用中添加了许多新的地图相关功能,比如应用内导航、加油站查找器、乘客位置共享和司机目的地过滤。每一个特性都添加了额外的代码MapViewController.,引入复杂性,使改装它或增加更多功能困难。
2015年,我们试图通过将其逻辑分成两个新视图控制器来简化代码:OfftripMapViewController.和OntripMapViewController.。这个部门有所帮助,但它也引入了新的问题。例如,为了避免每次驾驶员继续或休假时都会重新创建地图,我们通过两个视图控制器之间来回通过地图视图。不幸的是,有时时机关闭了,我们会将地图发送到错误的视图控制器。每当发生这种情况时,地图都会完全消失,如图2所示,下面导致司机的混淆:
当存在某些功能时,发生了另一个问题,在休假时需要存在一些特征。例如,应用内导航长期允许驱动程序在旅行时导航,但我们还希望驱动程序能够在休假时导航到首选目的地。我们最终重复了大量的导航代码,以便它将在两个视图控制器中使用。此问题阐述了架构的基本问题:通过应用状态对地图进行分割,但需要使用该地图的功能具有不适合这些段的寿命。
新地图架构
对于新的驱动程序应用,我们提出了一个全新的地图架构,以防止这些类型的漏洞出现。我们将地图作为背景视图,并在应用的整个生命周期中持续存在,这样我们就不再需要将其从一个控制器移动到另一个控制器。
三个组件提供对地图的访问:图层管理器,填充提供商和相机导演。这些对象可供代码的任何部分可用,因此地图特征可以以分散的方式编写。使用新应用程序,不再需要将地图逻辑放在专用视图控制器中,并且每个与地图相关的功能都可以拥有自己的寿命。
图层管理器:为地图元素构建沙箱
驱动程序应用程序功能通常希望在地图上绘制元素,例如标记或叠加。在先前版本的驱动程序应用程序中,功能代码通过调用地图视图本身调用函数来实现元素。只要代码完全乖巧,这种方法就效果很好,但在实践中并非总是如此。Sometimes features inadvertently forgot to remove their map markers, leaving stray elements on the map that persisted until the app was relaunched, an example of which can be seen in Figure 5. Other times, features removed others’ elements because they wanted a blank canvas for the map; the other feature had no idea that its elements had been removed, and behaved as if the driver could still see these elements on the map.
在我们的新驱动程序中实现,层管理器通过为每个地图特性提供一个沙箱来解决这些问题。不同于直接将元素添加到地图视图,特性创建自己的地图层,并在图层管理器中注册它们。地图层的接口允许工程师添加和删除地图元素,但不提供对任何其他层的访问。功能不能再干扰不属于它们的地图元素。
但是在某些情况下,一个特性确实需要清除映射中的所有其他元素。例如,当司机接受一个新的乘坐请求时,我们希望调度屏幕只显示与建议的提车相关的地图元素,以保持UI整洁和易于遵循。在这些情况下,特性可以要求层管理器注册独家的地图层。这暂时隐藏了所有其他地图层;一旦独家层未注册,另一层可以返回。
图层管理器还具有内置的保障保障,以防止忘记清理其元素的功能。在优步肋骨架构,业务逻辑通常存在于互动工类。在注册映射层时,必须提供一个交互器来绑定该层的生命周期,以便当交互器被停用时,映射层及其所有元素将被自动删除。迷路的地图元素不太可能持续存在,因为它们不能比创建它们的功能存活更长时间。
图层管理器的基本接口使映射层被隔离,因此它们无法相互冲突,如下所示:
类maplayermanager {
Func添加(图层:maplayer,interactorscope:互动仪,独家:bool)
Func删除(层:maplayer)
}
班级maplayer {
Func添加(Marker:MapMarker)
函数删除(标志:MapMarker)
Func添加(多边形:mappolygon)
Func删除(多边形:mappolygon)
Func添加(折线:mappolyline)
Func删除(折线:matmolyline)
func add (tileOverlay MapTileOverlay):
Func删除(Tileoverlay:maptileoverlay)
}
填充提供商:处理地图顶部的应用码头
将地图作为背景视图意味着应用程序的chrome,换句话说,面板和按钮等元素通常会覆盖它。地图需要考虑到我们的应用程序中的chrome,以便它可以显示司机的街道和他们需要的位置。例如,如果拾取针或司机的当前位置被不透明的面板遮挡住,那就很不幸了。
由于应用程序没有内置方式,以知道地图被遮挡,因此我们将填充提供程序创建为跟踪地图可见部分的组件。任何部分覆盖地图的Chrome将自己用填充提供商作为填充源注册。这些Chrome元素让提供商知道它们将其扩展到地图的界限中(相对于屏幕的边缘)。填充提供商聚合其所有源,并产生整个边缘插孔的可观察流。任何想要向用户显示某些映射区域的代码都使用此流以确保将可见所需的区域。
与地图层一样,每个地图填充源都需要绑定到创建它的特性的生命周期。但是,与其绑定到互动工(包含业务逻辑),填充源绑定到视图控制器,因此当视图消失时,其地图填充将自动消失。
填充提供商允许注册填充源,控制可以覆盖和促进地图的哪个部分,并促进驱动器的改进的导航体验。其基本界面如下所示:
Class MappaddingProvider {
var edgeinsetsstream:观察到的
Func添加(paddingsource:mappaddingsource,
viewControllerScope viewControllerScope):
函数删除(paddingSource MapPaddingSource):
}
类MapPaddingSource {
var edgeinset:edgeinset {get set}
}
摄像机导演:鼓励各功能之间的合作
地图相机是一个术语,它描述了3D空间中的点,一个有利度,在上面并在地图上俯视。它类似于胶片相机,地图是正在拍摄的场景,并且用户正在通过相机镜头看。功能通常会更改地图相机以向用户显示特定区域,例如当驾驶员被调度并且我们想要显示接送位置时。
在先前版本的驱动程序应用程序中,任何功能都可以通过直接在地图视图上更改其属性来更改相机的Vantage点。有时,功能将希望连续更新相机,例如在应用程序导航期间,在其驱动器时遵循用户的位置。这种方法的问题在于,功能通常不知道可以同时控制相机的任何其他功能。在这种情况下,如果两个特征试图同时控制地图相机,则地图将在两个区域之间来回跳跃,驾驶员的迷向体验。
新的驱动程序通过Camera Director解决了这些问题。而不是让功能自由地控制地图摄像机,摄像机导演为他们提供了一种方式影响通过注册相机规则,相机。相机规则具有用于提供应在屏幕上包含的纬度/经度坐标的界面。大多数功能不需要显示特定区域,但可以被编程为确保其标记或其他元素正在向用户展示。相机导演聚合这些规则来提出包含所有所需坐标的整体地图相机。
基于规则的方法允许以合作方式运行许多特征,比以前的方法更好的结果,通过该方法可以从位置跳到位置,并且可以在用户看到的内容中进行保证。
然而,有时基于规则的方法是不够的,因为它不允许任何摄像机控制超出可见地图区域。例如,当点击显示骑手需求高的区域的地图标记时,我们希望显示一个显示到该区域的路线的检查表,如下面的图7所示。在这种情况下,地图UI被放置在一个临时模式中,它聚焦于一个特定的标记,我们不希望它包含可见地图区域中不相关的位置。对于这些类型的情况,我们可以要求独家相机控制从相机总监。这提供了一个对象,让我们可以设置一个特定的地图摄像头,但一次只有一个功能可以独占访问,并且功能在失去摄像头控制时将被通知。
与层数一样(并且类似于填充提供商),相机导演需要一个肋骨互动工要绑定到功能注册新规则或请求专用摄像机访问。以这种方式,功能只能在主动时影响地图相机。
相机导演的基本接口让功能设置相机要求,其中要求使用规则和/或独家访问定义,如下所示:
class mapcameradirector {
Func添加(Camerarule:MapCamerarule,interacterscope:互动仪)
函数删除(cameraRule MapCameraRule):
func requestCameraControl(interactorScope: interactorScope) -> MapCameraHandle
Func RelinquishcamerAcontrol(手柄:MapCamerahandle)
}
class mapcamerarule {
var boundingLocations: [LocationCoordinate2D] {get set}
}
class mapcamerahandle {
var isactivestream:观察到的
Func Set(相机:MapCamera,持续时间:TimeInterval)
}
向前进
在新的驱动程序应用程序中,我们拿走了个人功能直接控制地图的能力,这可能导致冲突和杂散的视觉元素,正如我们过去所观察到的那样。通过我们的新框架,功能通过层数管理器,填充提供商和相机导演的调解,更安全地访问地图。我们也利用了肋骨架构,这需要工程师通过使用互动器和视图控制器绑定以这样的方式编写特征,即它们在不使用时不会留在地图上的可视元素。
提供这些护栏已导致更强大和可扩展的地图架构。此外,地图功能的开发更加毫不费力,不太需要为其他功能添加解决方法。最终,最重要的好处是我们的驾驶员合作伙伴 - 与新的驱动程序应用程序,他们将拥有更可靠的体验,拾取骑手。
Uber驱动程序应用程序系列中的文章索引
- 为什么我们决定重写优步司机应用
- 在肋骨中构建优步的新司机应用
- 优步新司机应用如何克服网络滞后
- 在优步缩放现金支付
- 如何运送应用程序重写,而不会冒险整个业务
- 为驱动程序构建可扩展和可靠的地图界面
- 工程优步灯塔:匹配乘客和司机在24位RGB颜色
- 为驱动程序首选项构建一个安全的、可伸缩的、服务器驱动的平台
- 将实时收益跟踪器建立在Uber的新驱动程序应用程序中
- 活动/服务作为依赖项:在优步新驱动程序应用程序中重新思考Android体系结构
有兴趣每天开发数百万人使用的移动应用程序吗?考虑加入我们的团队作为一个安卓或者iOS.开发人员!






