有用户反馈美团打车地图的性能有一些问题,美团打车技术团队在调研分析之后,采用了一套Native地图与Web的融合框架,不仅实现了用户手势事件智能分发的机制,还解决了WebView与Native地图在同一页面内布局困难的问题,同时性能也得到全面的优化。本文系融合技术的经验总结与分享。
1. 背景
美团打车业务很早就在美团App与点评App中提供了服务入口,并在技术上采用了H5与Native的混合开发技术。随着业务上线,有用户反馈我们的地图性能有一些问题,原因是我们打车地图使用的是Web版的地图(通过腾讯地图JavaScript API),业内同类产品使用的是Native版的地图SDK,Native地图相比Web地图具有天然的性能优势,所以美团打车地图从首屏地图加载到后续的地图操作体验都有一定差距。
问题与挑战
为了改善打车业务的地图体验,我们想到的方案是在展示地图的部分使用Native地图,而非地图部分使用H5页面来显示,这样既能追平与竞品的地图性能差距,又能充分发挥H5的开发效率。
这种方案乍一看似乎是传统的Hybrid开发,没什么难度与新奇。比如地图使用预先内置到App中的地图SDK实现,H5与Native的交互使用业界成熟的JSBridge技术。但从打车业务角度来看,因为打车业务有很多功能入口需要漂浮在地图之上,如起终点卡片、用户中心入口等,这种漂浮功能在技术上并不容易实现,而且还要保证用户触摸动作在漂浮元素与地图上发生时,分别派发给各自的事件系统,Hybrid技术在这方面没有经验可以借鉴。
带着这些挑战,我们进行一系列的尝试与试验,最终将问题解决并封装出我们打车业务的地图调用框架,我们称之为Native地图与Web融合框架(下文简称融合框架)。在这个过程中,我们总结出了一些经验,希望能给从事相关研究的同学带来一些帮助。
2. 调研
基于混合技术开发体系,我们研究了市面上大部分H5页面与Native地图的应用场景,主要分为如下两类:
H5页面与Native地图分别是2个独立的页面:H5业务逻辑用到地图时候,通过交互技术打开一个新地图页面,在新页面内,Native地图按照传入参数调用对应地图组件,完成业务功能的展示。
H5页面与Native地图位于同一页面内:两者将屏幕分割为两部分,如下图所示:Native地图位于上半部分,WebView H5页面位于下半部分。
经过分析后,我们发现这两种形式都无法满足打车业务场景的需求,因为目前市面上主流的打车业务场景由4部分构成,如下图所示:
起终点选择面板:占据页面下半部分,可以上下滑动露出更多内容。
地图部分:页面上半部分,显示起终点、线路等地图要素信息。
更多菜单:左上角图标,点击后跳转到H5功能菜单页面。
广告入口:右上角图标,点击后跳转到H5运营页面。
上文第一类,H5页面与Native地图分别位于两个独立页面中,只能满足部分地图场景的需求,无法布局为上图H5与地图同框显示的效果。
上文第二类,实现这样的布局需要多个WebView才能实现,存在如下缺点:
下方WebView与上方Native地图是平级的组件,各占屏幕的一半,相互间不存在压盖关系,实现起终点面板上下滑动效果困难。
左上角、右上角的更多菜单,广告入口位置需要新增2个WebView组件才能实现覆盖在地图之上,WebView组件再加载对应H5页面实现上述布局,整个步骤比较繁琐。
多个WebView组件构成的页面布局,由于内存空间不共享,它们之间信息的同步比较困难,太多的WebView组件对系统性能也是一种浪费。
调研结论是:市面上现存技术都无法满足打车场景的需求。
全新方案的提出
基于打车场景的特殊性,我们做了一个大胆的假设:把页面分为2层,下层是Native地图层,布满屏幕;上层是WebView层,完全覆盖到Native地图层之上,如下图所示:
我们期望的效果是:
点击H5元素时,点击事件会派发给H5 WebView容器处理。
点击地图区域时,点击事件会派发给Native地图组件处理。
H5与Native地图间的信息交互,可采用成熟的JSBridge技术实现。
具体实现思路有如下几点,参照下图:
Native地图位于下层,WebView置于Native地图之上,WebView背景透明,透过WebView可以看到下边的地图。红框区域是上层WebView打开的H5页面元素。
增加一个手势消息分发层,该层会智能判断手势事件落在H5元素还是地图元素中。举例:点击红框区域,消息会传递到WebView层的H5逻辑处理,点击红框之外的区域,消息会传递到Native地图层处理(地图移动、缩放等操作)。
H5与Native地图交互使用JSBridge完成。比如在地图中添加一个Marker,H5层业务逻辑发出添加Marker的消息,H5层通过JSBridge技术将消息发送到Native地图层,Native地图收到消息后在地图中添加Marker元素。
为了验证想法是否正确,我们首先通过Android平台开发出Demo,验证这种分层智能传递消息的做法是可行的,该方案最大优点是兼顾了H5的开发效率与Native地图的高性能特性,非常符合美团业务地图场景的需求。为了让想法落地时更规范、更系统,我们进行了如下的框架设计。
3. 框架设计
3.1 热区数据介绍
先介绍一个“热区数据”的概念,下图(3.2节)在手势分发层存在着消息分发热区数据部分,下文简称热区数据。热区数据是针对上层WebView的一个概念,只对WebView层有效,对下层Native地图层无效。如果用户点击屏幕事件想让H5来捕获处理,可以在屏幕区域内设置一个逻辑上的矩形区域,如:[0, 0, 50, 50](上图左上角区域),这个数据被称为热区数据。
我们通过编写代码逻辑,控制手势消息分发的策略,如果手势消息发生在热区数据矩形范围内,我们把消息发送给WebView处理,否则发送给Native地图处理。如上图所示,可以在同一屏幕内设定多个热区,[0, 0, 50, 50]、[430, 0, 50, 50]、[0, 200, 480, 200],热区的格式可以自己定义,我们这里采用的基于WebView组件左上角为原点的像素坐标格式:[left, top, width, height]。
3.2 框架图介绍
手势消息分发给WebView层流程
主要为上图1-->2-->3-->4过程,如下:
用户触摸动作首先被手势分发层捕获,手势分发层判断用户点击到热区数据范围内,将消息分发到WebView H5层处理。
WebView H5层收到消息,对消息进行处理(比如:在地图中添加一个终点Marker),通过通讯桥将消息传递到Native地图层。
Native地图层收到消息,并执行添加Marker操作,完成后返回成功信息。上述总体流程为:手势分发层-->1-->2-->3-->6-->7。
手势消息分发给Native地图层流程
主要为上图5-->6-->7过程,如下:
手势分发层捕获到消息,发现用户手势与当前热区数据矩形没有交集,于是将获取的消息分发到Native地图层。
如果消息是拖动操作,则Native地图自动识别拖动地图消息,实现移动地图的效果,涉及流程为:手势分发层-->5。
如果消息是点击操作,比如我们想实现点击地图中的Marker,将消息传递给H5处理的功能。实现步骤为我们事先在添加Marker时增加一个点击事件(Native地图层实现),Marker被点击时Native地图层会派发此事件,事件消息会通过JSBridge技术从Native地图层传到H5层,最后H5层获取到点击消息。整个操作流程为:手势分发层-->5-->6-->7。
热区数据的动态更新策略
因为打车业务底部的面板高度是可伸缩的,所以底部的热区数据并不是静止不动的,需要考虑热区数据也要随着DOM元素的拉伸做同步调整。可以通过在WebView H5层监控DOM的变化,DOM元素发生变化时,获取变化后的DOM元素位置、大小,格式化为热区数据,并更新到消息分发热区数据部分。因为拉伸动作是一个连续的动画效果,为了高效,我们只在动画结束的那一刻更新热区数据,中间过渡期不做处理。此整体流程为:2-->3-->4。
4. 点评App中的落地实践
4.1 手势分发层关键代码
这部分功能需要Native端同学实现,包括iOS与Android。两端分别在启动App时设置三层内容,最上层是手势触摸事件接收层,中间是WebView层(背景设置透明),最下层是Native地图层(如腾讯地图SDK)。用数组记录当前热区数据,当手势分发层有事件发生时,通过Touch事件获取手指位置信息,遍历热区数组判断手指位置是否与热区的矩形相交,如相交则将消息分发给WebView层,否则分发给Native层。下边是Android与iOS消息分发关键代码:
Android分发层关键代码
@Override public boolean dispatchTouchEvent(MotionEvent event){ if(event.getAction() == MotionEvent.ACTION_DOWN) { // 分发层接收到手势触摸消息,通过dispatchService类判断手势是否落在热区内,从而确定消息分发的对象 this.touchHandler = dispatchService.inRegion(event) ? TouchHandler.WebView : TouchHandler.MapView; } // 分发给Native地图层 if(this.touchHandler == TouchHandler.MapView) { return this.mapView.dispatchTouchEvent(event); } // 分发给WebView H5层 return super.dispatchTouchEvent(event); }
iOS分发层关键代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *hitTestView = nil; // 分发层接收到手势触摸消息,通过pointInHotspot判断手势是否落在热区内,从而确定消息分发的对象 if ([self pointInHotspot:point]) { // 分发给WebView H5层 hitTestView = [self.WebView hitTest:point withEvent:event]; }else{ // 分发给Native地图层 hitTestView = [self.mapView hitTest:point withEvent:event]; } return hitTestView; }
4.2 WebView H5层
该层有2个功能:
提供设置热区的JS接口setHotRegion,业务可通过此接口设置屏幕中的热区。
封装一层JS形式的地图接口,为上层业务提供地图服务,该层借助JSBridge通讯桥实现H5与Native层的异步通讯。
4.3 通讯桥简介
通讯桥即JSBridge技术,主要实现H5与Native的信息交互,这方面的技术都已比较成熟,业界有非常多的JSBridge实现,原理也都类似,常见的有:原生对象注入到H5层、URL拦截技术,Native调用JS常用的内置函数stringByEvaluatingJavaScriptFromString等。美团内部有比较成熟的KNB框架,所以项目中直接使用了KNB框架。
4.4 Native地图层
该层在地图SDK(如腾讯地图SDK)基础上进行了封装,提供一些打车业务友好的接口,如地图基本操作、打车起终点Marker添加、接送驾司机小车动画、地图事件、各种Marker的信息弹窗等。
4.5 Dom元素热区数据的自动维护技术
打车业务前端的技术栈是: Vue + VueX + Vue-Router构建的单页系统。如下图所示,页面中存在很多H5元素需要添加热区,逐个元素编写代码添加的话会很繁琐,而且页面元素的位置、大小变化时还需要同步更新热区数据,这里我们使用了Vue中的directive(指令)来解决了此问题。
以上左右2图是用户操作时页面展示的不同状态,很明显右图底部卡片变高了,卡片变化同时需要同步更新对应的热区数据,directive技术可以很方便解决此问题,原理如下:
在添加元素时,Vue指令的bind钩子函数被触发,此时计算出弹窗元素的大小和位置:使用getBoundingClientRect函数可以获取到元素的left、top、width、height等信息,将新的热区数据通过setHotRegion函数更新到手势分发层。
在移除元素时,unbind钩子函数被触发,此时将热区数据移除,这样便实现了热区的自动添加删除功能了。
使用指令技术很简捷,编写好指令的逻辑后注册到全局,在需要动态更新热区的元素上设置个v-hotRegion标签就可以了。
4.6 调试工具及测试
调试工具使用模拟器、真机都可以,开发期间我们使用的模拟器开发,测试期间QA使用真机验证。调试过程中主要验证2部分功能,分别是热区的验证与地图接口验证。
热区验证
主要验证主页面设置的热区是否正确,包括是否可以点击、底部卡片是否能正常拖拉、业务功能是否正常等。因为热区数据是一串数字,形如:[0, 0, 50, 50],无法直观判断出该数据是否有误,于是我们开发了一个可视化工具,将设置热区的元素都用红色矩形高亮显示,如下图所示,这样就能快速诊断出热区数据是否有异常。工具是使用Canvas画布实现的,画布大小与屏幕大小完全重合,借助画布就可以将矩形热区数据在屏幕中实时绘制出来。
地图接口验证
主要是编写单元测试完成的,本项目封装了50多个地图接口,每个接口都编写单测用例,观察入参、出参、控制台输出结果,地图展示效果是否正确等。测试主要在iOS模拟器中完成,这样方便在控制台打印一些调试信息进行诊断。
5. 上线效果
该框架在大众点评App中上线后地图体验明显提升,主要有体现在以下几个方面:
地图的操作体验,如地图移动、缩放明显好于H5地图,用户利用Native地图选择起终点、下单叫车、接送驾小车动画效果更加流畅。
首屏地图数据指标也有显著提升,如下表所示:
目前线上运行稳定,上线2月期间,Crash数量为个位数,Crash率远低于0.1‰。
框架上线后,大众点评App中业务迭代可以按照H5节奏上线,实现随时发版的开发效率。
Native地图层代码接口稳定、功能丰富,基本满足地图场景的业务需求。只需首次跟版发布,后续只需要迭代H5的业务逻辑即可。
6. 本文小结
本文将WebView与Native地图组件叠加到一起,实现了用户手势事件智能分发的机制,解决了WebView与Native地图在同一页面内布局困难的问题。这种融合机制为打车业务提升迭代效率同时保障地图体验提供了一种有效的途径,日常业务功能上线采用H5技术迭代,Native地图作为不常更新的基础能力首次发版时安装到用户手机上,实现业务需求随时发版的能力,不再受各大应用商店的限制,用户操作地图的体验也更加流畅。该融合框架适合以下业务场景:
业务中使用了地图功能,并对地图的加载、操作体验等有较高要求的业务。
业务属于Hybrid业务,并且H5页面与地图在同一页面内布局的功能。
如果你的业务是基于多个WebView与Native地图构建的系统,非常建议你了解下此文章。