javascript 使用 Data URI 快速更新图像导致缓存、内存泄漏
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9913765/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Rapidly updating image with Data URI causes caching, memory leak
提问by Dave Ceddia
I have a webpage that rapidly streams JSON from the server and displays bits of it, about 10 times/second. One part is a base64-encoded PNG image. I've found a few different ways to display the image, but all of them cause unbounded memory usage. It rises from 50mb to 2gb within minutes. Happens with Chrome, Safari, and Firefox. Haven't tried IE.
我有一个网页可以从服务器快速流式传输 JSON 并显示它的位,大约 10 次/秒。其中一部分是 base64 编码的 PNG 图像。我找到了几种不同的显示图像的方法,但所有这些方法都会导致无限制的内存使用。它在几分钟内从 50mb 上升到 2gb。发生在 Chrome、Safari 和 Firefox 上。IE没试过。
I discovered the memory usage first by looking at Activity Monitor.app -- the Google Chrome Renderer process continuously eats memory. Then, I looked at Chrome's Resource inspector (View
> Developer
> Developer Tools
, Resources
), and I saw that it was caching the images. Every time I changed the img src
, or created a new Image() and set its src
, Chrome cached it. I can only imagine the other browsers are doing the same.
我首先通过查看 Activity Monitor.app 发现了内存使用情况——Google Chrome Renderer 进程不断地占用内存。然后,我查看了 Chrome 的资源检查器 ( View
> Developer
> Developer Tools
, Resources
),我看到它正在缓存图像。每次我更改img src
或创建一个新的 Image() 并设置它时src
,Chrome 都会缓存它。我只能想象其他浏览器也在做同样的事情。
Is there any way to control this caching? Can I turn it off, or do something sneaky so it never happens?
有没有办法控制这种缓存?我可以关闭它,或者做一些偷偷摸摸的事情来让它永远不会发生吗?
Edit: I'd like to be able to use the technique in Safari/Mobile Safari. Also, I'm open to other methods of rapidly refreshing an image if anyone has any ideas.
编辑:我希望能够在 Safari/Mobile Safari 中使用该技术。此外,如果有人有任何想法,我愿意接受其他快速刷新图像的方法。
Here are the methods I've tried. Each one resides in a function that gets called on AJAX completion.
以下是我尝试过的方法。每个都驻留在一个在 AJAX 完成时调用的函数中。
Method 1 - Directly set the src
attribute on an img
tag
方法 1 - 直接src
在img
标签上设置属性
Fast. Displays nicely. Leaks like crazy.
快速地。显示很好。像疯了一样泄漏。
$('#placeholder_img').attr('src', 'data:image/png;base64,' + imgString);
Method 2 - Replace img
with a canvas
, and use drawImage
方法 2 - 替换img
为canvas
, 并使用drawImage
Displays fine, but still leaks.
显示正常,但仍然泄漏。
var canvas = document.getElementById("placeholder_canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "data:image/png;base64," + imgString;
Method 3 - Convert to binary and replace canvas
contents
方法 3 - 转换为二进制并替换canvas
内容
I'm doing something wrong here -- the images display small and look like random noise. This method uses a controlled amount of memory (grows to 100mb and stops), but it is slow, especially in Safari (~50% CPU usage there, 17% in Chrome). The idea came from this similar SO question: Data URI leak in Safari (was: Memory Leak with HTML5 canvas)
我在这里做错了——图像显示很小,看起来像随机噪声。这种方法使用受控的内存量(增长到 100mb 并停止),但速度很慢,尤其是在 Safari 中(大约 50% 的 CPU 使用率,Chrome 中的 17%)。这个想法来自这个类似的 SO 问题:Safari 中的数据 URI 泄漏(原为:HTML5 画布内存泄漏)
var img = atob(imgString);
var binimg = [];
for(var i = 0; i < img.length; i++) {
binimg.push(img.charCodeAt(i));
}
var bytearray = new Uint8Array(binimg);
// Grab the existing image from canvas
var ctx = document.getElementById("placeholder_canvas").getContext("2d");
var width = ctx.canvas.width,
height = ctx.canvas.height;
var imgdata = ctx.getImageData(0, 0, width, height);
// Overwrite it with new data
for(var i = 8, len = imgdata.data.length; i < len; i++) {
imgdata.data[i-8] = bytearray[i];
}
// Write it back
ctx.putImageData(imgdata, 0, 0);
回答by Panchosoft
I know it's been years since this issue was posted, but the problem still exists in recent versions of Safari Browser. So I have a definitive solution that works in all browsers, and I think this could save jobs or lives!.
我知道这个问题已经发布好几年了,但是这个问题在 Safari 浏览器的最新版本中仍然存在。所以我有一个适用于所有浏览器的明确解决方案,我认为这可以挽救工作或生命!。
Copy the following code somewhere in your html page:
将以下代码复制到您的 html 页面中的某处:
// Methods to address the memory leaks problems in Safari
var BASE64_MARKER = ';base64,';
var temporaryImage;
var objectURL = window.URL || window.webkitURL;
function convertDataURIToBlob(dataURI) {
// Validate input data
if(!dataURI) return;
// Convert image (in base64) to binary data
var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
// Create and return a new blob object using binary data
return new Blob([array], {type: "image/jpeg"});
}
Then when you receive a new frame/image base64Image
in base64 format (e.g. data:image/jpeg;base64, LzlqLzRBQ...
) and you want to update a html <img />
object imageElement
, then use this code:
然后,当您收到base64Image
base64 格式的新框架/图像(例如data:image/jpeg;base64, LzlqLzRBQ...
)并且想要更新 html<img />
对象时imageElement
,请使用以下代码:
// Destroy old image
if(temporaryImage) objectURL.revokeObjectURL(temporaryImage);
// Create a new image from binary data
var imageDataBlob = convertDataURIToBlob(base64Image);
// Create a new object URL object
temporaryImage = objectURL.createObjectURL(imageDataBlob);
// Set the new image
imageElement.src = temporaryImage;
Repeat this last code as much as needed and no memory leaks will appear. This solution doesn't require the use of the canvas element, but you can adapt the code to make it work.
根据需要尽可能多地重复最后一段代码,不会出现内存泄漏。此解决方案不需要使用画布元素,但您可以调整代码以使其工作。
回答by Jakke
Try setting image.src = "" after drawing.
绘制后尝试设置 image.src = ""。
var canvas = document.getElementById("placeholder_canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
//after drawing set src empty
img.src = "";
}
img.src = "data:image/png;base64," + imgString;
This might be helped
这可能会有所帮助
回答by ellisbben
I don't think there are any guarantees given about the memory usage of data URLs. If you can figure out a way to get them to behave in one browser, it guarantees little if not nothing about other browsers or versions.
我认为对于数据 URL 的内存使用没有任何保证。如果你能找到一种方法让它们在一个浏览器中运行,那么它对其他浏览器或版本几乎没有任何保证。
If you put your image data into a blob and then create a blob URL, you can then deallocate that data.
如果将图像数据放入 blob 中,然后创建 blob URL,则可以取消分配该数据。
Here's an example which turns a data URI into a blob URL; you may need to change / drop the webkit-
& WebKit-
prefixes on browsers other than Chrome and possibly future versions of Chrome.
这是一个将数据 URI 转换为 blob URL 的示例;您可能需要更改/删除除 Chrome 和未来版本的 Chrome 之外的浏览器上的webkit-
&WebKit-
前缀。
var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/);
//assume base64 encoding
var binStr = atob(parts[3]);
//might be able to replace the following lines with just
// var view = new Uint8Array(binStr);
//haven't tested.
//convert to binary in ArrayBuffer
var buf = new ArrayBuffer(binStr.length);
var view = new Uint8Array(buf);
for(var i = 0; i < view.length; i++)
view[i] = binStr.charCodeAt(i);
//end of the possibly unnecessary lines
var builder = new WebKitBlobBuilder();
builder.append(buf);
//create blob with mime type, create URL for it
var URL = webkitURL.createObjectURL(builder.getBlob(parts[1]))
return URL;
Deallocating is as easy as :
解除分配就像这样简单:
webkitURL.revokeObjectURL(URL);
And you can use your blob URL as your img
's src
.
并且您可以使用您的 blob URL 作为您img
的src
.
Unfortunately, blob URLs do not appear to be supported in IE prior to v10.
不幸的是,在 v10 之前的 IE 中似乎不支持 blob URL。
API reference:
API参考:
http://www.w3.org/TR/FileAPI/#dfn-createObjectURL
http://www.w3.org/TR/FileAPI/#dfn-createObjectURL
http://www.w3.org/TR/FileAPI/#dfn-revokeObjectURL
http://www.w3.org/TR/FileAPI/#dfn-revokeObjectURL
Compatibility reference:
兼容性参考:
回答by Paul Milham
I had a very similar issue.
我有一个非常相似的问题。
Setting img.src to dataUrl Leaks Memory
Long story short, I simply worked around the Image element. I use a javascript decoder to decode and display the image data onto a canvas. Unless the user tries to download the image, they'll never know the difference either. The other downside is that you're going to be limited to modern browsers. The up side is that this method doesn't leak like a sieve :)
长话短说,我只是围绕 Image 元素工作。我使用 javascript 解码器来解码图像数据并将其显示到画布上。除非用户尝试下载图像,否则他们也永远不会知道其中的区别。另一个缺点是您将受限于现代浏览器。好处是这种方法不会像筛子一样泄漏:)
回答by user3669490
patching up ellisbben's answer, since BlobBuilder is obsoleted and https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringViewprovides what appears to be a nice quick conversion from base64 to UInt8Array:
修补 ellisbben 的答案,因为 BlobBuilder 已过时并且https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView提供了从 base64 到 UInt8Array 的快速转换:
in html:
在 html 中:
<script src='js/stringview.js'></script>
in js:
在js中:
window.URL = window.URL ||
window.webkitURL;
function blobify_dataurl(dataURL){
var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/);
//assume base64 encoding
var binStr = atob(parts[3]);
//convert to binary in StringView
var view = StringView.base64ToBytes(parts[3]);
var blob = new Blob([view], {type: parts[1]}); // pass a useful mime type here
//create blob with mime type, create URL for it
var outURL = URL.createObjectURL(blob);
return outURL;
}
I still don't see it actually updating the image in Safari mobile, but chrome can receive dataurls rapid-fire over websocket and keep up with them far better than having to manually iterate over the string. And if you know you'll always have the same type of dataurl, you could even swap the regex out for a substring (likely quicker...?)
我仍然没有看到它实际上更新了 Safari 移动版中的图像,但是 chrome 可以通过 websocket 快速接收数据 URL,并且比手动迭代字符串要好得多。如果您知道您将始终拥有相同类型的 dataurl,您甚至可以将正则表达式换成子字符串(可能更快......?)
Running some quick memory profiles, it looks like Chrome is even able to keep up with deallocations (if you remember to do them...):
运行一些快速的内存配置文件,看起来 Chrome 甚至能够跟上释放(如果你记得这样做......):
URL.revokeObjectURL(outURL);
回答by Keven Sun
I have used different methods to solve this problem, none of them works. It seems that memory leaks when img.src = base64string and those memory can never get released. Here is my solution.
我使用了不同的方法来解决这个问题,它们都不起作用。似乎当 img.src = base64string 时内存泄漏并且这些内存永远不会被释放。这是我的解决方案。
fs.writeFile('img0.jpg', img_data, function (err) {
// console.log("save img!" );
});
document.getElementById("my-img").src = 'img0.jpg?'+img_step;
img_step+=1;
Note that you should convert base64 to jpeg buffer.
请注意,您应该将 base64 转换为 jpeg 缓冲区。
My Electron app updating img every 50ms, and memory doesn't leak. Forget about disk usage. Chrome's memory management piss me off.
我的 Electron 应用程序每 50 毫秒更新一次 img,并且内存不会泄漏。忘记磁盘使用情况。Chrome 的内存管理让我很生气。
回答by Jay
var inc = 1;
var Bulk = 540;
var tot = 540;
var audtot = 35.90;
var canvas = document.getElementById("myCanvas");
//var imggg = document.getElementById("myimg");
canvas.width = 550;
canvas.height = 400;
var context = canvas.getContext("2d");
var variation = 0.2;
var interval = 65;
function JLoop() {
if (inc < tot) {
if (vid.currentTime < ((audtot * inc) / tot) - variation || (vid.currentTime > ((audtot * inc) / tot) + variation)) {
contflag = 1;
vid.currentTime = ((audtot * inc) / tot);
}
// Draw the animation
try {
context.clearRect(0, 0, canvas.width, canvas.height);
if (arr[inc - 1] != undefined) {
context.drawImage(arr[inc - 1], 0, 0, canvas.width, canvas.height);
arr[inc - 1].src = "";
arr[inc - 1].src = "";
//document.getElementById("myimg" + inc).style.display = "block";;
// document.getElementById("myimg" + (inc-1)).style.display = "none";
//imggg.src = arr[inc - 1].src;
}
$("#audiofile").val(inc);
// clearInterval(ref);
} catch (e) {
}
inc++;
// interval = 60;
//setTimeout(JLoop, interval);
}
else {
}
}
var ref = setInterval(JLoop, interval);
});
Worked for me on memory leak thanks dude.
为我解决内存泄漏问题,谢谢老兄。
回答by ellisbben
Unless Safari or Mobile Safari don'tleak data urls, server-side might be the only way to do this on all browsers.
除非 Safari 或 Mobile Safari不泄漏数据 URL,否则服务器端可能是在所有浏览器上执行此操作的唯一方法。
Probably most straightforward would be to make a URL for your image stream, GET
ting it gives a 302 or 303 response redirecting to a single-use URL that will give the desired image. You will probably have to destroy and re-create the image tags to force a reload of the URL.
可能最直接的是为您的图像流创建一个 URL,GET
它会提供 302 或 303 响应,重定向到将提供所需图像的一次性 URL。您可能必须销毁并重新创建图像标签以强制重新加载 URL。
You will also be at the mercy of the browser regarding its img
caching behavior. And the mercy of my understanding (or lack of understanding) of the HTTP spec. Still, unless server-side operation doesn't fit your requirements, try this first. It adds to the complexity of the server, but this approach uses the browser much more naturally.
您还将受浏览器img
缓存行为的支配。以及我对 HTTP 规范的理解(或缺乏理解)的怜悯。尽管如此,除非服务器端操作不符合您的要求,否则请先尝试此操作。它增加了服务器的复杂性,但这种方法更自然地使用浏览器。
But what about using the browser un-naturally? Depending on how browsers implement iframe
s and handle their associated content, you mightbe able to get data urls working without leaking the memory. This is kinda Frankenstein shit and is exactly the sort of nonsense that no one should have to do. Upside: it could work. Downside: there are a bazillion ways to try it and uneven, undocumented behavior is exactly what I'd expect.
但是不自然地使用浏览器呢?根据浏览器如何实现iframe
s 和处理其关联内容,您可能能够在不泄漏内存的情况下使数据 url 正常工作。这有点像弗兰肯斯坦狗屎,正是那种没人应该做的胡说八道。好处:它可以工作。缺点:有无数种方法可以尝试,而不平衡的、未记录的行为正是我所期望的。
One idea: embed an iframe
containing a page; this page and the page that it is embedded in use cross document messaging(note the GREEN in the compatibility matrix!); embeddee gets the PNG string and passes it along to the embedded page, which then makes an appropriate img
tag. When the embeddee needs to display a new message, it destroys the embedded iframe
(hopefully releasing the memory of the data url) then creates a new one and passes it the new PNG string.
一个想法:嵌入一个iframe
包含页面;此页面和它嵌入的页面使用跨文档消息传递(注意兼容性矩阵中的绿色!);embeddee 获取 PNG 字符串并将其传递给嵌入的页面,然后生成一个适当的img
标签。当嵌入需要显示新消息时,它会破坏嵌入的iframe
(希望释放数据 url 的内存)然后创建一个新的并将新的 PNG 字符串传递给它。
If you want to be marginally more clever, you could actually embed the source for the embedded frame in the embeddee page as a data url; however, this might leak that data url, which I guess would be poetic justice for trying such a reacharound.
如果你想稍微聪明一点,你实际上可以将嵌入框架的源作为数据 url 嵌入到 embeddee 页面中;但是,这可能会泄漏该数据 url,我想这对于尝试这种方法来说是一种诗意的正义。
"Something that works in Safari would be better." Browser technology keeps on moving forward, unevenly. When they don't hand the functionality to you on a plate, you gotta get devious.
“在 Safari 中工作的东西会更好。” 浏览器技术不断向前发展,但并不均衡。当他们不把功能交给你时,你就会变得狡猾。