解决百度地图内存泄露问题

2,905 阅读3分钟

百度地图内存泄露问题

百度地图中,我们会遇到循环创建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实现分批渲染。 上述解决办法可能不是最优的,如果你有更好的解决办法,欢迎留言。