Safari中的数据URI泄漏(是:HTML5画布的内存泄漏)

问题描述 投票:17回答:4

我创建了一个网页,通过Websocket接收base64编码的位图,然后将它们绘制到画布上。它完美地运作。除此之外,浏览器(无论是Firefox,Chrome还是Safari)的内存使用量随着每张图像而增加,并且永远不会下降。因此,我的代码中必须存在内存泄漏或其他一些错误。如果我注释掉对context.drawImage的调用,则不会发生内存泄漏(但当然不会绘制图像)。以下是我网页上的摘要。任何帮助表示赞赏。谢谢!

// global variables
var canvas;
var context;

...

ws.onmessage = function(evt)
{
    var received_msg = evt.data;
    var display_image = new Image();
    display_image.onload = function ()
    {
        context.drawImage(this, 0, 0);
    }
    display_image.src = 'data:image/bmp;base64,'+received_msg;
}

...

canvas=document.getElementById('ImageCanvas');
context=canvas.getContext('2d');

...

<canvas id="ImageCanvas" width="430" height="330"></canvas>

更新12/19/2011

我可以通过使用createElement / appendChild和removeChild每100个图像动态创建/销毁画布来解决此问题。之后,我对Firefox和Chrome没有更多内存问题。

但是,Safari仍然存在内存使用问题,但我认为这是一个与Canvas无关的问题。在Safari中反复更改图像的“src”似乎存在问题,好像它永远不会释放这个内存。

display_image.src = 'data:image/bmp;base64,'+received_msg;  

这与以下网站上描述的问题相同:http://waldheinz.de/2010/06/webkit-leaks-data-uris/


更新12/21/2011

我希望通过将我收到的base64字符串转换为blob(我在此站点上找到“dataURItoBlob”函数)并返回到带有window.URL.createObjectURL的URL,将我的图像src设置为此来解决此Safari问题。 URL,然后通过调用window.URL.revokeObjectURL释放内存。我得到了这一切,Chrome和Firefox正确显示图像。不幸的是,Safari似乎不支持BlobBuilder,因此它不是我可以使用的解决方案。这很奇怪,因为包括O'Reilly“Programming HTML5 Applications”在内的很多地方都说Safari / WebKit Nightly Builds支持BlobBuilder。我从http://nightly.webkit.org/下载了最新的Windows夜间版本并运行了WebKit.exe,但BlobBuilder和WebKitBlobBuilder仍未定义。


更新01/03/2012

好吧,我最后通过使用atob()解码base64编码的数据URI字符串然后创建像素数据数组并使用putImageData将其写入画布来修复此问题(请参阅http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/)。这样做(而不是不断修改图像的“src”并在onload函数中调用drawImage),我不再在Safari或任何浏览器中看到内存泄漏。

javascript html5 canvas safari websocket
4个回答
3
投票

没有实际的工作代码,我们只能推测为什么。

如果您一遍又一遍地发送相同的图像,则每次都会创建一个新图像。这是不好的。你想做这样的事情:

var images = {}; // a map of all the images

ws.onmessage = function(evt)
{
    var received_msg = evt.data;
    var display_image;
    var src = 'data:image/bmp;base64,'+received_msg;
    // We've got two distinct scenarios here for images coming over the line:
    if (images[src] !== undefined) {
      // Image has come over before and therefore already been created,
      // so don't make a new one!
      display_image = images[src];
      display_image.onload = function () {
          context.drawImage(this, 0, 0);
      }
    } else {
      // Never before seen image, make a new Image()
      display_image = new Image();
      display_image.onload = function () {
          context.drawImage(this, 0, 0);
      }
      display_image.src = src;
      images[src] = display_image; // save it for reuse
    }
}

有更有效的方法来编写它(例如,我复制了onload代码,我没有检查图像是否已经完成)。不过,我会把这些部分留给你,你明白了。


1
投票

我不相信这是一个错误。问题似乎是图像堆叠在彼此之上。因此,为了清理内存,您需要使用clearRect()清除画布,然后再在其中绘制新图像。

ctx.clearRect(0,0,canvas.width,canvas.height);

How to clear your canvas matters


0
投票

你可能比你想象的要多画几次。尝试添加计数器并将数字输出到警报或页面中的div,以查看图像被绘制的次数。


0
投票

那非常有趣。这值得报告为各种浏览器供应商的错误(我的感觉是它不应该发生)。您可能会回答“不要这样做,而不是这样做”,但至少那时你会知道正确的答案,并有一个有趣的事情要写博客文章(更多的人肯定会遇到这个问题)。

要尝试的一件事是在调用drawImage之后立即取消设置图像src(和onload处理程序)。它可能不会释放所有内存,但它可能会恢复大部分内存。

如果这不起作用,您可以始终创建一个图像对象池,并在绘制到画布后重新使用它们。这很麻烦,因为您必须跟踪这些对象的状态,并将池设置为适当的大小(或根据流量使其增长/缩小)。

请报告您的结果。我非常感兴趣,因为我在noVNC中使用了类似技术的一个tightPNG编码(我相信其他人也会感兴趣)。