百度地图内存泄露问题
百度地图中,我们会遇到循环创建marker点时内存占用不停上涨导致页面直接崩掉。或者数据量过大页面渲染CPU占用过高导致页面卡死。
如图:
需求实例:
使用百度地图展示车辆的位置,并且30s刷新一次位置信息。
面对这样的需要,展示车辆的位置,每一个车辆需要一个Marker点,车辆太对需要考虑聚合,
代码实现如下:
const clustererInstance = null;
const mapInstance = null;
// 初始化地图
...
// 创建Marker点
function addMarker(data) {
// 创建之前先清除地图上的覆盖物
mapInstance.clearOverlays();
const markers = [];
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i];
const point = new BMap.Point(item.long, item.lat);
const marker = new BMap.Marker(point);
markers.push(marker);
}
if (clustererInstance) {
clustererInstance.clearMarkers();
clustererInstance.addMarkers(totalMarkers);
} else {
clustererInstance = new BMapLib.MarkerClusterer(
mapInstance, { markers: markers });
}
}
这里只是实现创建marker点,定时器和数据请求就不展示了。在每次循环请求数据回来都调用一次addMarker方法,可以完成车辆点的展示。
问题一:数据量小时,这样的实现不会出现明显的问题,但是当数据量达到几万次,或者更大时,页面在循环创建Marker点时,虽然每次都使用clearOverlays,和clearMarkers进行了清除,但是百度的这两个方法并没有实现内存释放。导致每次循环浏览器的内存都在不断增加,直到页面崩掉。
解决办法:
当面对这个问题时,我们在第一次循环创建marker点时借助vue的store将其存储起来,下一次循环时只是修改marker的postion即可。代码如下:
import { mapState } from 'vuex';
const clustererInstance = null;
const mapInstance = null;
computed: {
...mapState(['allMcMarkers'])
},
methods: {
// 初始化地图
initMap() {
...
},
// 创建Marker点
addMarker(data) {
// 创建之前先清除地图上的覆盖物
mapInstance.clearOverlays();
const markers = [];
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i];
const marker = null;
const point = new BMap.Point(item.long, item.lat);
// 判断是否存在marker点,有则直接替换位置
if (this.allMcMarkers[i]) {
marker = this.allMcMarkers[i];
marker.setPosition(point);
} else {
marker = new BMap.Marker(point);
this.allMcMarkers.push(marker);
}
markers.push(marker);
}
if (clustererInstance) {
clustererInstance.clearMarkers();
clustererInstance.addMarkers(totalMarkers);
} else {
clustererInstance = new BMapLib.MarkerClusterer(
mapInstance, { markers: markers });
}
}
}
问题二:
当数据量过大时,前端页面渲染内存占用会很大。也会导致页面卡死。所以我们可以考虑分批渲染。分批渲染可以使用setInterval或setTimeout、requestAnimationFrame来实现。
setTimeout实现分批渲染
import { mapState } from 'vuex';
const clustererInstance = null;
const mapInstance = null;
computed: {
...mapState(['allMcMarkers'])
},
methods: {
// 数据分组 每组5000条数据
group (data) {
var result = [];
const _l = Math.ceil(data.length / 5000);
for (var i = 0; i < _l; i++) {
result.push(data.slice(i * 5000, (i + 1) * 5000));
}
return result;
},
batchRender (data) {
return new Promise((resolve, reject) => {
var groups = this.group(data);
let totalMarkers = [];
const that = this;
this.renderMarkersLoading = true;
const zoom = 5;
for (let i = 0; i < groups.length; i++) {
// 闭包, 保持i值的正确性
// eslint-disable-next-line wrap-iife
window.setTimeout(function () {
var group = groups[i];
var index = i;
return function () {
// 分批渲染
totalMarkers = totalMarkers.concat(that.addMarker(group, zoom, index));
if (totalMarkers.length === data.length) {
that.createMarker(totalMarkers);
that.renderMarkersLoading = false;
}
};
}(), 1);
}
resolve();
});
},
// 创建Marker点
addMarker(data) {
// 创建之前先清除地图上的覆盖物
mapInstance.clearOverlays();
const markers = [];
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i];
const marker = null;
const point = new BMap.Point(item.long, item.lat);
// 判断是否存在marker点,有则直接替换位置
if (this.allMcMarkers[i]) {
marker = this.allMcMarkers[i];
marker.setPosition(point);
} else {
marker = new BMap.Marker(point);
this.allMcMarkers.push(marker);
}
markers.push(marker);
}
return markers;
},
createMarker (totalMarkers) {
if (totalMarkers.length) {
if (clustererInstance) {
clustererInstance.clearMarkers();
clustererInstance.addMarkers(totalMarkers);
} else {
clustererInstance = new BMapLib.MarkerClusterer(this.map, { markers: totalMarkers });
}
}
},
}
同样在实现动画的渲染时我们最优选择requestAnimationFrame实现分批渲染。
上述解决办法可能不是最优的,如果你有更好的解决办法,欢迎留言。