地图组件设计

问题

公司项目里有很多用到高德地图的页面,绘制 marker 和 polygon。一直以来都是各页面直接调用高德地图API,存在很多问题。最近抽出时间设计实现了一个地图组件用来解决这些问题。

主要问题有以下几点

  1. 直接调用三方 API,如果三方 sdk 升级发生 API 不兼容变动,会导致 sdk 升级成本非常大(这个问题2020年就碰到过,当时高德sdk三年没升级了,就是因为接口发生不兼容变更,升级成本较高)。
  2. 默认 marker 和 polygon 默认在主线程创建,是耗时操作,大量绘制时候会出现明显卡顿。虽然可以自己调用的时候控制在子线程调用,但是需要各页面自己实现线程同步及取消等逻辑,可能存在线程安全问题。
  3. 默认没有缓存能力。由于公司产品需要在地图上绘制大量mark,如果全量绘制势必导致卡顿,而且数据量过大。所以地图mark一次只获取并渲染当前屏幕范围内的数据。这样有一个问题,用户在拖动地图的时候,每次都会清除上一次的mark,然后重新绘制。用户每次拖动范围其实不大,一半以上 mark 其实是可以复用的。

为了解决以上问题,设计实现了一个地图库。

地图库设计

test

整体设计如上图。最外层包含两部分,一部分是地图基础操作,另一部分是marker 和polygon 绘制。本来想专注第二部分,但是第一部分如果没有抽象,业务层需要操作地图的时候还是需要直接引用第三方接口,所以这里把基础操作部分也抽象出来了。这块最好单独拆分成一个模块,可以作为后续的优化项。
绘制部分对比原来接口简化了很多。
高德Api:

1
2
3
4
5
6
7
8
9
10
11
12
// 这里简单展示了主线程绘制的代码,如果要加异步的话涉及同步管理、线程取消等,代码量会大量增加
val list = loadMarkerData()
list.forEach {
LatLng latLng = new LatLng(it.lat,it.lng);
val markerOptions = MarkerOptions()
.position(latLng)
.title("北京")
.icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory
.decodeResource(getResources(),R.drawable.location_marker)))
val marker = aMap.addMarker(markerOptions));
marker.setTag(it)
}

封装后:

1
2
3
val cover = MapCoverManagerFactory.buildCoverManager(context, mapView)
val list = loadMarkerData()
cover.submitCoverItems(list, true)

可以看出封装后的地图库对外接口主要是CoverManager,使用者只需要构建数据对象,提交到CoverManager 里,CoverManager 会管理异步线程绘制、线程取消、marker等缓存。
CoverManager 里分别依赖了:
IMapDrawer(负责根据提交的数据类型。实际绘制marker、polygon。主要方法是drawItem(item: ICoverData));
ICacheStrategy(负责设计缓存模型。1. marker 移出屏幕的时候不需要马上销毁对象,可能会再移回来;2. 不再显示的marker 对象可以通过修改marker 图片和定位,变成新的marker,比重新创建marker 成本低很多,并且可以降低gc。默认实现了一个LRU缓存模型和无缓存模型,可以根据需求使用,也可以自行定制)
ICoverJobSchedule(CoverManager 使用的协程进行任务调度)

Author

Lyuku

Posted on

2021-09-06

Licensed under