高德地图:崩溃率从万分之八降到十万分之八的架构奥秘
ccwgpt 2024-10-19 02:59 24 浏览 0 评论
近几年来,高德地图业务发展迅猛,团队规模迅速扩张,代码体量急剧增加,为了提高团队高效并行作战的能力,端上做了一系列架构升级。2018 年通过双端融合、组件化、研发平台搭建等技术实践,使得发版效率提升 50%, App 崩溃率从万分之八降到十万分之八。
本文整理自 ArchSummit 全球架构师峰会(深圳站)2019 峰会演讲,主要分享在一系列架构升级改进中,高德地图的具体做法、经验和思考。
大家好,我是来自高德地图的郝仁杰,本次分享的主题是“高德地图 App 架构演化与实践”。2018 年,我们通过架构的演进将发版周期缩短至一半,整个 App 的崩溃率从万分之八降低至十万分之八。在正式开始介绍之前,我先来简单介绍下高德。
1、背景介绍
高德是国内数字地图内容、导航和位置服务解决方案提供商。目前在端上,分为手机和车机两条主线。近年来,高德业务迅猛发展,人员规模迅速扩张,代码体量急剧增加,为了提高团队高效并行作战的能力,端上做了 C++ 多端融合和动态化能力建设。
回顾近几来高德地图 App 架构的演进历程:2014 年,手机端上只有几十个研发, Android 和 iOS 端由原生单体架构实现;2015 年,地图引擎下沉 C++,实现了手机和车机的多端融合;2016 年,端上启动了动态 UI 框架的开发,为未来业务的动态化铺路;2017 年,动态 UI 框架建设完成,具备了运行静态页面的能力;到了 2018 年,手机端已经成长为拥有数百研发规模的团队,双端代码量也已经达到数百万行,架构要如何继续演化来提高团队高效并行作战的能力,来支撑并赋能业务快速发展呢?
2、问题现状
为了让业务开发有节奏的进行,项目上每年会制定一些公车计划。公车就是每个 App 版本,版本里带的产品功能就是公车上的货物,公车计划即每年的发版计划。按照计划,公车会在指定的时间把组装好的货物拉走。
2018 年初,由于双端代码差异较大、耦合严重、复用率低、职责不清晰、平台工具简陋等问题,公车无法按照计划拉走货物。工具落后,货物组装慢且质量差,无法如期交货,迫使公车等待,导致整个发版周期长达 3 个月,崩溃率高达万分之八,公车变成了伪公车。
为了解决这些问题,使伪公车变为真公车,需要做到稳定、并行和高效。端上通过以下三种方式达到该目的,一是双端融合,如上图,蓝色部分上漂动态 UI,下沉 C++,以及 Android、iOS 双端拉齐,减少差异,提高可维护性;二是选择组件化方案,分而治之,解除耦合,提高复用率,做到并行、高效;三是搭建研发中台,工具升级,流程自动化以及风险质量管控,提升效率和稳定性。
3、执行方案
双端融合
2015 年,我们通过地图引擎下沉 C++,实现了手机、车机的多端融合,同理,可将部分功能下沉 C++;通过 2017 年建成的动态 UI 框架,可将部分业务上漂到动态 UI;对于既不能上漂也不能下沉的,通过双端拉齐做到融合。
那么,什么样的场景适合下沉到 C++ 呢?一,需要有稳定的逻辑,不经常变化;二是不强依赖原生;三,对性能要求较高。举例来说,导航逻辑,地图从开始建立到现在已经打磨出一套非常核心稳固的逻辑,这部分逻辑可以下沉到 C++。
哪些场景又适合上漂到动态 UI 呢?一,对性能要求不高;二,经常易变的业务代码,比如产品的 UI 需求;三,不强依赖于原生能力。
对于既不能下沉,也不能上漂的功能,选择双端拉齐:对性能有一定要求;强依赖原生的能力;需要支撑一些原生业务。例如,高德地图的页面框架,虽然 Android 和 iOS 端有原生的页面框架,但地图类应用和普通应用不太一样,地图类应用的主要功能是围绕着一张地图进行,这张地图上面的元素非常丰富,数据量非常庞大,内存占用较大,如果采用原生页面框架进行开发,就意味着每切换一个页面就得创建一张新地图,这对手机端这种资源紧缺的环境来说是非常浪费的,对于低端机型来说是不可接受的。
另外,地图应用从一个页面切换到另一个页面,或者从一个场景切换到另一个场景,并不是完全不同的两张图切换,而仅仅是一张地图的不同状态转换,此时,如果额外创建一张新的地图,显然是极大的浪费。所以,对于地图类应用,我们建设了自己的页面框架:以单系统页面控制器多视图切换的方式实现。由于原来都是单体开发,Android 和 iOS 只关注自身特性,两边的实现不太一样,跳转规则、功能特性均有差异,我们通过分析双端的规则、特性,借鉴双端各自的优点,设计了一套统一的规则、特性,实现了双端融合。
下面简单介绍高德地图页面框架的融合方案:
如上图,左边的 Activity 是 Android 的系统页面控制器,右边的 UIViewController 是 iOS 的系统页面控制器,通过虚线连接比较,我们发现两端的页面状态设计基本相同。所以,我们在设计自己的页面框架时沿用了这些系统页面状态,同时从命名上也保持一致,这样可以让 Android 和 iOS 原生开发的同学更容易理解和上手。
此外,我们吸取了双端各自的优点。比如,Android 端页面有四种启动模式,但是 iOS 端并没有这些,我们就把 Android 的四种启动模式运用到了 iOS 端;iOS 端有 Present 特性,但是 Android 端没有,那么也把这种特性融合到 Android 端的页面框架中;最后,还有一些小设计,比如 Android 的 onResult 设计,也可以借鉴融合到 iOS 端。
首先,介绍下四种启动模式,这是安卓特有的。第一种是 Standard 模式,这个模式和栈的行为是一样的,就是标准的 Push 和 Pop;第二种是 SingleTop 模式,当向一个页面栈压入另一个页面时,如果该页面已经在栈顶,那么将不会创建一个新的页面实例 C 放到栈顶,不会变成 ABCC 这样的方式,而仅仅是通知当前栈顶的 C 页面做一个数据更新;第三种是 SingleInstance 模式,当以 SingleInstance 模式 Push 一个页面时,如果该页面已经在栈中,那么就把它从栈中带到栈顶;最后一种是 SingleTask 的模式,这和原生系统略有差别,因为我们目前是基于单页面控制器的方式实现的,当以 SingleTask 的方式 Push 一个页面到页面栈时,如果该页面已经在栈中,页面框架会把其之上的所有页面全部清除出栈,使其成为新的栈顶,这就是四种启动模式。
接下来,简单介绍 iOS 的 Present 特性。当页面栈顶的 C 页面 Present D 页面时,D 页面并没有被加入到 ABC 页面栈中,而是变成了 C 页面的一个附属,当 D 页面要消失时,同样也是通过 C 页面的 dismiss 移除掉。这里有些限制,每个页面仅可以 Present 一个页面,这个页面可以是一个普通页面,也可以是一个导航页面,那么导航页面是什么呢?大家可以理解成一个新的页面栈(功能类似 UINavigationController),其上可以添加其它页面。如果 D 页面是导航页面,就可以在其上 Push 其它页面,如果业务流程有一个主流程,一个分支流程,就可以采用这种方式实现。
最后是 Android 的 onResult 特性,实现了页面间数据返回的解耦,如上示例代码就是大致的实现原理。具体来说,从 A 页面跳转到 B 页面,那么 B 页面执行了一段逻辑之后,A 希望得到执行结果,如果按照原来 iOS 的实现方式,只能通过监听 Listener 或 Delegate 等方式将 B 页面的执行结果返回给 A 页面。当 iOS 的页面框架实现了这个特性, A 页面就不需要额外注册 B 页面的 Listener 或 Delegate 了,只需重写自己的 onResult 方法并处理结果即可,这样既可以实现页面解耦,又方便了业务同学开发。
接下来,举个高德地图手机端上的具体实例。
有这样一个搜索场景,从一个具体地理位置详情页可以跳转到以它为中心的搜周边页,在搜周边页中又可以跳转到另一个具体地理位置详情页,接着可以跳转到新的搜周边页,以此递归循环,但是返回时,产品希望仅返回到之前搜索过的具体地理位置详情页,略去搜周边页。如上右侧图片展示,查询顺序是:7 天优品酒店详情页 -> 7 天优品酒店搜周边页 -> 火驴火烧肉亭详情页 -> 火驴火烧肉亭搜周边页;返回顺序是:火驴火烧肉亭搜周边页 -> 火驴火烧肉亭详情页 -> 7 天优品酒店详情页。中间的 7 天优品酒店搜周边页被去掉了。
在 iOS 页面框架未实现 launch mode 前,火驴火烧肉亭详情页在跳转到火驴火烧肉亭搜周边页前,需要自行遍历当前页面栈,将 7 天优品酒店搜周边页从页面栈中移出后,再跳转到火驴火烧肉亭搜周边页,以此保证产品逻辑的正确性。在实现了 launch mode 之后,火驴火烧肉亭详情页仅需以 SingleInstance 的方式打开火驴火烧肉亭搜周边页即可,页面框架会自动将之前的 7 天优品酒店搜周边页调到栈顶,并将该搜周边页的内容刷新为火驴火烧肉亭搜周边。极大简化了 iOS 端业务同学的开发成本,规范了 iOS 页面跳转规范,结束了由业务自行操作页面栈的混乱时代,同时双端技术能力的融合也为上层动态 UI 业务提供了一致性的体验。
上面,我们介绍了双端融合方案的三种方式,也举例说明了其带来的效果。下沉 C++,实现两套代码合一,解决了一致性问题,提高了性能,但同时也提高了开发门槛,适用于多年沉淀的核心逻辑;上漂动态 UI,同样解决了双端一致性问题,性能会稍有损失,但降低了开发门槛,使得开发速度得到提升,适用于频繁变动的业务场景;双端拉齐则是借鉴了双端优势,做到互相融合。
组件化
我们做了一些团队组件化方案的选型和参考,例如手淘的 Atlas、Beehive,网易的 LDBusMediator 等,由于这些组件化方案都比较成熟,这里不再赘述。它们都包含五个概念:容器、模块、生命周期、页面路由和对外服务(通信),我们重新命名了这些概念使其更加形象化。
容器,负责管理模块;模块,是一个独立的功能单元,可以独立编译;微应用,管理模块的生命周期,对于一个手机操作系统,是为每个应用派发生命周期,对于一个单独的应用,是为每个模块派发生命周期,就像一个应用管理着很多微应用一样,因此我们取了这个形象的名字;页面路由,负责进行 URL 的解析和页面跳转;微服务,模块中的逻辑功能,同时提供对外服务。
我们对容器在设计进行了一些改造,如上右半边图,模块被虚化了,被定义成了一个物理概念(即一个独立代码仓库),逻辑上拆分为微应用、微服务和页面路由,容器不再管理模块,而是直接管理这三个元素。之所以这样做,是因为我们希望业务更关注自身需要的服务是什么,而不是它在哪个模块,这些也是借鉴了安卓的组件化思想。
接下来,我们详细介绍下微应用生命周期的设计,如上图,微应用在 iOS 端参考的是 UIApplicationDelegate 的生命周期,而在 Android 端参考的是 Activity 的生命周期。做这样的参考选择,原因有三:一,高德地图内的应用场景大都依赖前后台切换的事件做一些逻辑处理;二,iOS 的 UIApplicationDelegate 作为应用的生命周期,同时支持前后台切换,完全吻合高德地图的场景;三,Android 选择 Activity 是因其组件化的思想,在 Android 的设计中,Application 已经弱化成了一个特殊进程的概念,并不能代表一个应用,且高德地图是基于单 Activity 实现的(上面介绍页面框架时提到过),通过 Activity 的 onStop,onRestart 生命周期中做些逻辑处理,即可判断出应用是否为前后台切换。这样,去除图中虚线框中的生命周期后,双端得到了统一的生命周期,如下左半部分图:
对于虚线中差异化的部分(如上右半部分图),设计为扩展的生命周期,做到抽象相同、扩展差异,既统一了通用生命周期,也支持了双端各自的特性。
对于微服务,我们定义了一个通信规范,只能通过接口方法,不能直接调用实现。定义微服务主要是希望 UI 展现与业务逻辑能够分离,并让业务逻辑服务化,不仅服务于当前页面,也能够服务更多页面,提高代码的复用率,降低维护成本。
有了容器框架,代码便可以抽成一个个独立的模块单元,但模块应该放在那里,上下依赖关系是什么,还需要对模块进行分层、分组,下图为分层、分组后的整体架构:
通过容器建设,架构分层、分组,我们实现了组件化,解除了模块间耦合,提高了代码复用率,为后面的高效并行打好基础。分而治之的思想,组件化的“分”也是为后面的“治”做好铺垫。
搭建研发中台
研发中台应该有哪些功能,可以结合组件化和公车流程来分解,如下图:
主流程是公车流程,分为:需求收集、需求串讲、开发、合版、提测、灰度发布和正式上线。开发流程可以分解为更细的建立迭代、选择模块、功能开发、模块构建和安装包构建。这里解释下迭代的概念,即一个发版周期内的功能开发。组件化实现了功能解耦,使得不同业务团队可以在开发阶段创建自己的迭代并行开发,开发完成后在规定的时间段进行合版。提测流程可以分成模块集成、安装包构建和集成测试,其中模块集成是以产物的方式进行集成。测试通过后,通过客户端发布流程,进行灰度发布验证,灰度通过后,再进行正式上线,上线之后,我们会对崩溃、性能等维度进行监控。通过流程拆解,我们整理出了研发中台的完整功能:
研发中台建设完成后,我们实现了研发流程、测试流程以及发布流程的自动化,提高了人效。另外,通过质量管控,提高了稳定性;通过流程管控,约束了可能产生的风险。
主副收益
首先,通过双端融合、组件化、中台建设提升了代码稳定性,实现了流程自动化,做到了开发阶段的并行,使发版周期缩短到原来的一半,从伪公车变成真公车。
其次,通过质量优化,让崩溃率从万分之八降低到十万分之八:双端融合减少了一致性问题;架构合理化提高了可维护性;关键流程管控,减少了风险源头;通过质量扫描,解决了头部质量问题,通过崩溃监控,解决头部崩溃问题。
然后,通过升级编译脚本,支持并行编译;通过模块化,基于产物构建安装包,大大降低编译时长,从原来的 40 多分钟降至现在的 8 分钟。
最后是包大小优化,iOS 端从 146M 减到 123M,纯减量达 48M,这主要是通过编译优化,资源云化,功能合并(分层、分组),svg 替代 png 小图标,删除无用图片和代码实现等手段实现。其中,资源云化主要是指将启动时的非必要资源放在云端,需要时再进行动态加载。
4、经验教训
在组件化以后,编译模式发生了一些变化,模块在集成前提前生成了产物,这些变化同时带来了一些问题,比如二进制兼容问题。以枚举功能为例,在模块化后,A 模块依赖 B 模块中定义的枚举,在 A 模块生成产物后,B 模块的枚举定义发生了变化,A 中使用的枚举值含义可能发生变化,如下图:
为了解决该问题,我们制定了一些开发规范:对于枚举的定义,不允许删除任何已定义的枚举值,不允许从中间插入任何枚举值,如果一定要添加,只能在末尾添加,以此来解决二进制兼容性问题。当然,除了枚举的问题,还有宏定义等引起的二进制兼容性问题,此处不一一详述。
此外,Android 端还可能出现代码注解丢失问题。编译期注解仅存在于编译阶段,模块化后,产物中无法保存注解信息,导致产物集成时,由于找不到注解信息而无法进行全局注册。为此,我们做了一些自定义 APT 插件,在注解处 阶段生成 Java 数据类的同时也存储一份注解信息,这样在集成阶段就可以根据注解信息进行全局注册。
5、未来展望
2018 年,高德客户端通过一系列架构治理,从伪公车变成了真公车,但这只是近几年架构演进的一个阶段性成果。未来,我们要发挥动态 UI 的优势,让业务真正动态化起来,从公车时代跨入到 Feature Team 时代,让公车变成一条条公路,每个 Feature Team 就是一个小汽车,按照自己的节奏装好货物后,就可以在修好的公路上自由的行驶,更好地做到灵活、并行和高效。
嘉宾介绍郝仁杰,高德地图无线开发专家。十余年移动客户端开发经验,曾深度参与 Nokia S60 Contacts,YY 语音,360 手机卫士的研发维护工作,对 Symbian、Android、iOS 系统有较深的理解。目前在高德地图负责 Android、iOS 端的基础架构,2018 年,带领团队实现了端上的 Bundle 化等一系列架构升级的开发工作。
相关推荐
- 如何让老师看完文章后还啧啧称奇?满分作文有框架,这3点是关键
-
历年来语文考试中,作文的分数都占着相当大的一个比例,同时作文也是最容易拉开差距的一个模块。别人拿满分,而你却只有20分左右,分数的差距就是这样拉开的。作文想拿很高的分数却是不容易,但不是完全不可能的事...
- 小学作文写作技巧和方法,万能公式框架法。家长收藏
-
小学语文老师用心整理,将写作框架编成万能公式。作文的写作是语文学习中的重要一环,从小学到初中再到高中,作文一直是语文考试中占分比重最高的部分。小学阶段的语文写作相对来说比较简单,主要是打基础,但是很多...
- 如何用爆款改写技巧提升文章吸引力结构重塑:打破原文框架悬念前
-
如何用爆款改写技巧提升文章吸引力?结构重塑:打破原文框架悬念前置法-在开头设置悬念或提出反常识的结论,吸引读者注意力。例如,将“接纳不完美是治愈的开始”改写为“天天逼自己当完美超人?别杠了!生活本就...
- 守护袁昆:是否有必要按框架去写文章,拍摄剪辑视频?
-
(文/守护袁昆)如今不管是写文章还是剪辑视频,越来越多的朋友喜欢用框架、用脚本,作为互联网创作者,我们是否有必要按框架去写文章,拍摄剪辑短视频呢?其实在内容创作过程中,是否使用框架始终是一个充满争议的...
- 揭秘!爆款文章的秘密:让读者无法抗拒的文章框架
-
说说我自己一开始写文章都会犯一个毛病,文章,通常是想到哪里,写到哪里,“管不住字儿”。这样往往会出现以下问题绊住我们继续写下去1、很容易,写着就跑偏了,最终出来的成品和最初的设想偏离很大2、会写得很慢...
- 想要写出逻辑清晰的文章,你需要掌握哪些写作结构
-
想要写出好文章,就必须要了解文章的结构和框架。一篇文章结构清晰,读者就很容易跟上作者的思路,看出文章的重点内容。如果你对新媒体的文章有进行过研究,你就会发现很多公众号的文章结构都是类似的。所以你需要掌...
- 写作结构拆解:从选题到框架,如何让文章说服力翻倍?
-
你有没有想过,为什么有些文章能轻松获得很高的阅读量,而你的文章却始终无人问津?其实,写出爆款文章并没有想象中那么难。关键在于选择一个吸引人的主题,并用一个清晰的写作框架,通过2-3个有力的子观点支撑...
- 写作总被吐槽逻辑混乱?三步搭建框架法,新手也能写出漂亮文章
-
一、结构决定论:信息传递的桥梁写作者和读者之间始终存在一道隐形的鸿沟。作者脑海中的想法如同一棵枝繁叶茂的大树,但直接倾倒给读者时,往往只剩下零散的枝叶,信息在传达过程中的丢失,作者输出的和读者读到的不...
- 如何搭建文章框架:新手写作者很有必要看
-
#头条深一度-深度阅读计划#见面好呀,我是潼臻~37岁,边上班边带娃的二胎妈妈藏起生活里的琐碎,期望你我都能遇到更好的自己~~~~~~~~~~~~~~~~~~果然多读书是可以真切学习到有用的东西最近把...
- 模型上下文协议(MCP)的可视化向导
-
最近,模型上下文协议(MCP)引起了广泛关注。你一定听说过它。今天,让我们来了解一下它是什么。直观地说,MCP就像是AI应用的USB-C接口。正如USB-C提供了一种标准化的方式,用于将...
- 97个人放一页PPT!用对Smartart架构图直接开挂!
-
从讯飞出差回来的路上,在高铁上看到一条微博,关于红楼梦人物的思维导图:下面很多家长说,帮助很大,能帮助上学的孩子梳理清人物关系,我看了下,清楚是清楚,但真的不太好看!作为一名PPT博主,我就顺带在高...
- 技术架构规范与实践(二)架构设计示例
-
1.逻辑架构1.1领域概念1.2宏观应用架构1.3宏观流程1.4微服务拆分与分层2.技术架构3.开发架构3.1后端技术栈分类名称版本描述后端框架/组件JavaJdk8(openjdk:8u342)后...
- 倾斜柱模板安装加固
-
1、适用范围:呈梯形逐层向内侧倾斜的框架柱。2、工艺流程:定位放线-配模-校正梁位置-安装加固。3、工艺方法:(1)定位放线首先现场技术管理人员对每颗不同标高的梁底、梁中边线及200mm控制线进行平面...
- 地基与基础工程、主体工程节点构造
-
#去班味吧#桩头凿除环切法工艺说明:1、根据桩头预留长度(深入承台10cm)放样桩顶标高,施工人员根据测量结果在基桩上用红油漆标注环切线;2、在切割线以上部分桩底,人工用钢钎打入桩底约15cm,打入时...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- mfc框架 (52)
- abb框架断路器 (48)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- orm框架有哪些 (51)
- 知识框架图 (52)
- ppt框架 (55)
- 框架图模板 (59)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)