技术分享|同层渲染之高德地图
一、背景
客户反馈高德地图有性能问题和兼容问题,在某些手机上会出现滑动不流畅或崩溃的情况,原因是他们使用的是web版本的高德地图,web版本的地图因为渲染于WebView上,所以天然不如原生地图所具备的性能和兼容性。
为了解决用户问题,我们想到的方案是在之前web地图的部分用原生地图来代替,非地图的部分还是用h5来显示,这样既能提高性能,又能满足业务团队的自定义业务需求。这个方案初步看来就是cordova调用原生功能的一个普通插件,实现起来没有什么难度。但是...
我们观察了下web实现的效果,发现地图上面还有多个功能入口,比如"项目总数"、"资产总数"、"资产面积"、"项目详情"等,这些元素都是悬浮在地图上的。这种h5和原生的混合渲染方式在技术上并不容易实现,而且对于手势分发(web或原生对手势的响应)也得进行额外的处理。
带着这些问题,我们进行了一系列的尝试和实验,最终将问题解决并开发了"同层渲染——高德地图插件"。
基于用户的需求和问题,我们思考和尝试了多个方案:
1. web页面和原生地图分别属于两个独立的页面,在使用地图的时候,通过cordova接口打开一个新的页面,在新的页面内,原生地图按照传入的参数,提供原生开发的业务功能。
经过了解,客户团队的APP是需要在一级页面内使用地图,所以这种方案不符合客户的需求。
2. web页面和原生地图位于同一个页面内,其中多个h5元素分别采用独立的WebView进行渲染。
显而易见的,这个方案将分别对需要渲染的H5元素生成多个WebView,不仅对于系统的性能造成浪费,而且多个WebView的通信、位置等等都没法很好的处理,所以该方案也不行。
3. 基于Android提供的WebView的PPAPI插件能力,开发一个地图的WebPlugin插件。
理论上来说,这个方案是最好的方案,一举解决了用户的问题,并且还提供了完美的自定义扩展。但是据我们查阅网上的资料,发现该方案已经在新版本的Chromium中被删除了,也就是说谷歌不再提供支持该方案。而且这个方案还存在一个问题,由于需要编译浏览器内核并集成在APP中,将会明显的增加APP的体积。基于开发周期和其他内容的综合考虑,所以也没有采用这个方案。
调研结论:没有合适的方案能够解决用户的问题。我们当然不能选择解决用户,所以...
我们重新考虑了客户的业务场景,决定把页面分为两层,下层是原生地图层,上层为WebView层,如下图所示:
我们期望的效果是:
1. 点击web元素时,点击事件由WebView处理
2. 点击原生地图区域时,点击事件由地图处理
3. web与原生地图间的信息交互由cordova插件接口来实现
实现思路:
1. 原生地图位于下层,WebView置于原生地图上,并将WebView的背景设置为透明,将可以透过WebView看到下面的地图内容。
2. 增加一个手势分发层,通过计算来判断手势发生在web元素还是地图上。如果发生在web元素上,则直接由WebView处理,如果发生在地图上,则直接由地图响应。
3. web调用地图的一些功能则通过cordova插件接口来实现。比如:绘制覆盖物、获取定位、获取中心点、设置缩放、移动地图等。
4.1 手势处理
为解决手势问题,我们需要将web元素的具体位置传入到手势分发层。所以需要在屏幕上建立一个坐标系,如下图所示:
1. 设屏幕左上角为原点,web端调用js方法分别获得web元素(绿色方框标记)的坐标,如(0, 0, 300, 50)、(20, 300, 260, 50)、(0, 380, 300, 50),格式为(left, top, width, height)。
2. 通过调用cordova接口,将元素的坐标传入手势分发层。如果Web元素的区域会发生变化,则在每次变化后,需要将新的坐标信息重新传入。
3. 当用户触摸屏幕时,在手势分发层获取用户触摸的位置,然后与传入的坐标进行比较判断。如果是发生在web元素上,则由WebView处理事件(如动画、路由等),否则交由地图处理(如移动地图、缩放地图等)。
4.2 关键代码
1. 手势分发层关键代码
按照之前设计的方案,在WebView层(背景透明)下,把地图层插入进去,这样WebView层就叠加在地图层上。用数组记录Web端传入的坐标信息,当用户触摸屏幕时,通过dispatchTouchEvent获取手指信息,然后遍历坐标数组,判断手指手指位置是否与坐标矩形区域相交,如果相交,则分发给WebView层,否则分发给地图。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isTouchedWebView = false;
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 判断是否触摸在Web元素上
isTouchedWebView = regionManager.isWebElementTouchEvent(event);
}
if(isTouchedWebView) {
// 分发给WebView
return super.dispatchTouchEvent(event);
} else {
// 分发给地图
return regionManager.dispatchMapViewTouchEvent(event);
}
}
2. WebView层
该层主要包括两类接口:同层渲染通用接口;调用地图功能接口。
3. 地图层
该层在高德地图的基础上做了封装,提供一些具体的业务功能接口,包括地图的基本操作、添加覆盖物等。
4.3 效果对比
4.4 问题及处理方案
问题一:新方案需要在WebView的下层将地图View添加进去,以及在处理手势冲突时需要重写WebView的dispatchTouchEvent方法。
处理方案:我们引入AspectJ框架来实现AOP方案,在不改变其他插件代码的同时,调用我们自定义的功能代码。充分与其他插件进行解耦。
问题二:Web在同时调用地图功能时,被调用的操作不会全部执行。
处理方案:事实上这是高德地图的一个缺陷,为了规避这个问题,我们采用了队列的方案,将用户调用地图功能的操作缓存到队列中,按次序的进行执行。
问题三:添加地图覆盖物性能问题:切换覆盖物空白期较长、切换时间过长。
处理方案:针对空白期较长问题,我们加入了覆盖物动画效果,感官减少了突兀感。针对切换时间过长问题,我们将用户需要绘制的数据按照距离屏幕中心点进行排序,距离屏幕中心最近的点先开始绘制,效果提升显著。
本文主要描述了为解决用户问题而调研并实现方案的过程,期间走了挺多的弯路,但这个方案的落地还是为未来我们提升用户体验、提高插件扩展能力提供了一些参考。
目前,基于这个同层渲染方案我们另外还实现了一个图库插件,该插件只提供基础的大图浏览功能,但是具体的业务功能,业务方将可以最大程度的进行自定义。
未来,我们将继续基于该方案实现更多的特性插件,于此同时,我们将继续研究Chromium内核,在内核层实现真正的同层渲染方案。
邹同学: 研发工程师,目前负责移动平台相关工作。