一直以来对于Cesium中的Label和Billboard使用都是作为常规Entity来使用的,结果在自定义DataSource中也直接添加到DataSource的_entityCollection导致了内存泄露。

在做内存分析时,发现在对象到根节点的引用路径上总是有一个奇怪的BillbordCollection的引用,但是项目中是没有单独定义这个Collection的。再查看官方文档发现这个Collection是用来统一管理Billboard的:

BillboardCollection

清理掉页面中所有billboard发现还是无法释放,而且我们是没有创建这个BillboardCollection的,那这个是和创建的呢,我们找到Billboard类,在其构造函数中看到如下代码:

也就是说Billboard创建时是传入了对应的BillboardCollection的,但是我们代码中并没有创建,那这个是从哪里来的呢?我们在Billboard这里打断点后跟踪其创建流程发现:

这个Billboard的创建是因为LabelCollection的更新而创建的,突然想到我们这个页面使用了自定义DataSource,里面加入了Label。而且页面在之前是没有这个问题的,把DataSource中的Label去掉,果然如所料,内存泄露解决!

回过头来,其实有两点还是不清楚:

  • BillboardCollection和LabelCollection是从哪里来的?
  • 为什么在自定义DataSource中使用就会导致内存泄露?

首先从官方文档中我们可以看到BillboardCollection和LabelCollection是为了这两种特殊对象统一管理做的优化,所以猜测是不是就算我们没有创建Cesium本身也会自动添加呢?还好仔细查找源码还真是:

在创建过程中断点发现如下调用关系:

Label的构造函数调用其实是通过LabelCollection的add方法创建的,而add方法中可以看到会把LabelCollection当做参数传入:

LabelCollection.prototype.add = function(options) {
    var label = new Label(options, this);

    this._labels.push(label);
    this._labelsToUpdate.push(label);

    return label;
};

而代码调用链中不难看出,我们在触发Label显示时因为要触发LabelVisualizer也就是Label的展示器,而其发现这个Label还没有构建时会调用EntityCluster的createGetEntity,这里就传入了LabelCollection的构造函数,最终其会创建对应Collection并调用其add方法创建Label。

其次,关于“为什么在自定义DataSource中使用就会导致内存泄露”。 目前暂时未找到具体原因,但看LabelCollection和BillboardCollection中是定义了destroy方法的,所以对于Label、Billboard我们是需要和DataSourceCollection一样在销毁阶段调用其destroy方法。项目时间有限就未纠结其原理了,先把对应涉及到的label和billboard做移出DataSource的处理。