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

Posted

技术标签:

【中文标题】Safari 中的数据 URI 泄漏(原为:HTML5 画布的内存泄漏)【英文标题】:Data URI leak in Safari (was: Memory Leak with HTML5 canvas) 【发布时间】:2011-12-16 19:04:27 【问题描述】:

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

// 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"  ></canvas>

2011 年 12 月 19 日更新

我可以通过使用 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/


2011 年 12 月 21 日更新

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


2012 年 1 月 3 日更新

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

【问题讨论】:

如果您在绘制图像之前添加一个 clearRect 调用,或者如果您使用将宽度设置为自身的重置技巧会发生什么? (来自***.com/questions/2142535/…) 我已经尝试过 context.clearRect(0, 0, canvas.width, canvas.height);在drawImage之前,但内存泄漏仍然发生。 您确定它不只是存储图像以防万一您返回页面以便它可以从内存中加载它吗?可能是运行时优化。 2018 : Safari v11 --> 数据 uri 仍然像 CRAZY 一样泄漏(“我会注意到把它放在 prod 中”有点疯狂)。 Firefox Quantum --> 泄漏了一点。 Chrome --> 完全没问题。 【参考方案1】:

没有实际工作代码,我们只能推测原因。

如果您一遍又一遍地发送同一张图片,那么您每次都在制作一张新图片。这是不好的。你会想做这样的事情:

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 代码,并且我没有检查图像是否已经完成)。不过,我会把这些部分留给你,你懂的。

【讨论】:

如果每张图片都是独一无二的,并且随着您制作每个新的 HTMLImageElement (new Image()),内存使用量会增加,这很正常! 那如果我注释掉对drawImage的调用,为什么内存使用保持不变? 现在,在您的代码中,每次调用drawImage,也会调用new Image()。除非您只调用 drawImage X 次,其中 X 是唯一图像的数量,否则您会遇到很大的问题,因为您制作的 new Image()s 比您想要的要多。有多少张图片,new Image() 被调用了多少次? 有无限数量的独特图像,与我的 Websocket 服务器想要发送的数量一样多。我的观点是,当我放弃调用 drawImage (通过将其注释掉)时,似乎 new Image() 分配的内存在一段时间后被释放,可能是通过垃圾收集。但是当我离开对 drawImage 的调用时,内存永远不会被释放。 啊,我明白了。只是为了好玩,尝试注释掉 drawImage 调用并将其替换为 context.fillRect(0,0,50,50) 调用。还会漏水吗?【参考方案2】:

您绘制图像的次数可能比您预期的要多得多。尝试添加一个计数器并将数字输出到警报或页面中的 div 以查看图像被绘制了多少次。

【讨论】:

【参考方案3】:

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

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

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

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

【讨论】:

【参考方案4】:

我不认为这是一个错误。问题似乎是图像堆叠在一起。所以要清理内存,你需要在画布之前使用 clearRect() 来清理你的画布。

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

How to clear your canvas matters

【讨论】:

我注意到由于某种原因,画布高度在我的情况下似乎是错误的。在代码示例中使用图像高度作为“canvas.height”似乎可以解决问题。

以上是关于Safari 中的数据 URI 泄漏(原为:HTML5 画布的内存泄漏)的主要内容,如果未能解决你的问题,请参考以下文章

重定向时如何在 Safari 中保留 uri 片段?

使用 Safari Web 检查器调试内存泄漏?

iOS Safari 内存泄漏导致应用程序无法使用

仅字母排序规则(原为:Emacs 中的奇怪文件排序与我的语言环境有关)

从 WebView 中的链接打开 Mobile Safari

Spring:没有为带有 URI 的 HTTP 请求找到映射