Javascript Safari中的数据URI泄漏(was:HTML5画布内存泄漏)
我创建了一个网页,通过Websocket接收base64编码的位图,然后将它们绘制到画布上。它工作得很好。除此之外,浏览器(无论是Firefox、Chrome还是Safari)的内存使用量都会随着每个图像的增加而增加,而且不会下降。所以,我的代码中一定有内存泄漏或其他错误。如果我注释掉对context.drawImage的调用,则不会发生内存泄漏(当然,该图像永远不会被绘制)。下面是我的网页中的片段。感谢您的帮助。谢谢Javascript Safari中的数据URI泄漏(was:HTML5画布内存泄漏),javascript,html,canvas,safari,websocket,Javascript,Html,Canvas,Safari,Websocket,我创建了一个网页,通过Websocket接收base64编码的位图,然后将它们绘制到画布上。它工作得很好。除此之外,浏览器(无论是Firefox、Chrome还是Safari)的内存使用量都会随着每个图像的增加而增加,而且不会下降。所以,我的代码中一定有内存泄漏或其他错误。如果我注释掉对context.drawImage的调用,则不会发生内存泄漏(当然,该图像永远不会被绘制)。下面是我的网页中的片段。感谢您的帮助。谢谢 // global variables var canvas; var co
// 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/21/2011 我希望通过将收到的base64字符串转换为blob(使用我在该网站上找到的“dataURItoBlob”函数)并返回带有window.URL.createObjectURL的URL,将我的图像src设置为该URL,然后通过调用window.URL.revokeObjectURL释放内存,从而绕过这个Safari问题。我把这些都做好了,Chrome和Firefox都能正确显示图像。不幸的是,Safari似乎不支持BlobBuilder,所以它不是我可以使用的解决方案。这很奇怪,因为包括O'Reilly的《编写HTML5应用程序》一书在内的许多地方都指出,BlobBuilder在Safari/WebKit夜间构建中受支持。我从下载了最新的Windows夜间版本并运行了WebKit.exe,但BlobBuilder和WebKitBlobBuilder仍然没有定义
更新日期:2012年3月1日
好的,我最终通过使用atob()对base64编码的数据URI字符串进行解码,然后创建一个像素数据数组,并使用putImageData将其写入画布来解决这个问题(请参阅)。这样做(与不断修改图像的“src”和在onload函数中调用drawImage相反),我不再在Safari或任何浏览器中看到内存泄漏 您绘制图像的次数可能比预期的要多。尝试添加一个计数器并将数字输出到页面中的警报或div,以查看图像被绘制了多少次。如果没有实际的工作代码,我们只能推测原因 如果你一次又一次地发送相同的图像,你每次都在制作一个新图像。这很糟糕。您可能希望执行以下操作:
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代码,而不是检查图像是否已经完成)。不过,我将把这些部分留给你,你会明白的。这很有趣。这是值得向不同的浏览器供应商报告的一个bug(我觉得这不应该发生)。你的回答可能是“不要那样做,而是那样做”,但至少你会知道正确的答案,并且在博客上写一篇有趣的文章(肯定会有更多的人遇到这个问题) 要尝试的一件事是在调用drawImage之后立即取消设置image src(和onload处理程序)。它可能无法释放所有内存,但可能会将大部分内存取回 如果这不起作用,您可以创建一个图像对象池,并在它们绘制到画布上后重新使用它们。这是一个麻烦,因为您必须跟踪这些对象的状态,并将池设置为适当的大小(或根据流量使其增长/收缩)
请报告你的结果。我非常感兴趣,因为我对中的一个tightPNG编码使用了类似的技术(我相信其他人也会感兴趣)。我不相信这是一个bug。问题似乎是图像堆叠在一起。因此,要清除内存,需要使用clearRect()在画布中绘制新图像之前清除画布 clearRect(0,0,canvas.width,canvas.height)
如果在绘制图像之前添加clearRect调用,或者使用重置技巧将宽度设置为自身,会发生什么情况?(from)我尝试了context.clearRect(0,0,canvas.width,canvas.height);在drawImage之前,但内存泄漏仍然发生。您确定它不仅仅是存储图像,以防您返回页面,以便它可以从内存加载图像吗?可能是运行时优化。2018年:Safari v11-->数据uri仍然疯狂地泄漏(“我会注意到把它放在prod中”有点疯狂)。Firefox Quantum-->有点泄漏。Chrome-->完全可以。如果每个图像都是唯一的,并且随着您创建每个新的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)
调用替换它。它还漏吗?我注意到由于某种原因,在我的情况下画布的高度似乎是错误的。在代码示例中,使用图像高度作为“canvas.height”似乎可以做到这一点。
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
}
}