我有一个网页可以快速从服务器流式传输JSON并显示其中的一些内容,大约10次/秒。一部分是base64编码的PNG图像。我发现了几种不同的显示图像的方法,但是所有这些方法都会导致无限的内存使用。它在几分钟内从50mb上升到2gb。适用于Chrome,Safari和Firefox。没试过IE。
我首先通过查看Activity Monitor.app发现了内存使用情况 - Google Chrome渲染器进程不断地占用内存。然后,我查看了Chrome的资源检查器(View
> Developer
> Developer Tools
,Resources
),我看到它缓存图像。每次我更改img src
或创建新的图片()并设置其src
时,Chrome都会将其缓存。我只能想象其他浏览器也在做同样的事情。
有没有办法控制这个缓存?我可以把它关掉,或做一些鬼鬼祟祟的事情,所以它永远不会发生吗?
编辑:我希望能够在Safari / Mobile Safari中使用该技术。此外,如果有人有任何想法,我会对其他快速刷新图像的方法持开放态度。
以下是我尝试过的方法。每个都驻留在一个在AJAX完成时调用的函数。
src
代码img
属性
快速。显示得很好。像疯了一样泄漏。
$('#placeholder_img').attr('src', 'data:image/png;base64,' + imgString);
img
替换为canvas
,然后使用drawImage
显示正常但仍有泄漏。
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;
canvas
内容我在这里做错了 - 图像显示小而且看起来像随机噪音。此方法使用受控量的内存(增长到100mb并停止),但速度很慢,尤其是在Safari中(那里的CPU使用率约为50%,Chrome中为17%)。这个想法来自这个类似的SO问题:Data URI leak in Safari (was: Memory Leak with HTML5 canvas)
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);
答案 0 :(得分:4)
我知道这个问题已发布多年以来,但最近版本的Safari浏览器仍然存在这个问题。所以我有一个适用于所有浏览器的确定解决方案,我认为这可以节省工作或生活!。
在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"});
}
然后,当您收到base64格式的新框架/图片base64Image
(例如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;
根据需要重复此最后一个代码,不会出现内存泄漏。此解决方案不需要使用canvas元素,但您可以调整代码以使其工作。
答案 1 :(得分:3)
我认为没有任何关于数据URL的内存使用的保证。如果你能想出一种方法让他们在一个浏览器中表现出来,那么它对其他浏览器或版本几乎没有任何保证。
如果将图像数据放入blob然后创建blob URL,则可以取消分配该数据。
这是一个将数据URI转换为blob URL的示例;您可能需要更改/删除webkit-
&amp;浏览器以外的浏览器上的WebKit-
前缀以及可能的未来Chrome版本。
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;
解除分配就像以下一样简单:
webkitURL.revokeObjectURL(URL);
您可以将blob网址用作img
的{{1}}。
不幸的是,在v10之前的IE中似乎不支持blob URL。
http://www.w3.org/TR/FileAPI/#dfn-createObjectURL
http://www.w3.org/TR/FileAPI/#dfn-revokeObjectURL
答案 2 :(得分:3)
尝试设置image.src =&#34;&#34;画完后。
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;
这可能会有所帮助
答案 3 :(得分:1)
我有一个非常类似的问题。
Setting img.src to dataUrl Leaks Memory
长话短说,我只是简单地处理了Image元素。我使用javascript解码器解码并在画布上显示图像数据。除非用户试图下载图像,否则他们也不会知道其中的差异。另一个缺点是你将被限制在现代浏览器中。好的一面是,这种方法不会像筛子那样泄漏:)
答案 4 :(得分:1)
in html:
<script src='js/stringview.js'></script>
在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;
}
我仍然没有看到它实际上在Safari移动设备中更新图像,但是chrome可以通过websocket快速接收数据,并且比不得不手动迭代字符串更好地跟上它们。如果你知道你总是拥有相同类型的dataurl,你甚至可以将正则表达式换成子串(可能更快......?)
运行一些快速的内存配置文件,看起来Chrome甚至可以跟上解除分配(如果你还记得这样做......):
URL.revokeObjectURL(outURL);
答案 5 :(得分:1)
我使用了不同的方法来解决这个问题,但没有一种方法可行。当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;
请注意,您应该将base64转换为jpeg缓冲区。
我的电子应用程序每隔50ms更新一次img,内存不会泄漏。 忘记磁盘使用情况。 Chrome的内存管理让我感到沮丧。
答案 6 :(得分:0)
除非Safari或Mobile Safari 不泄漏数据网址,否则服务器端可能是在所有浏览器上执行此操作的唯一方法。
可能最简单的方法是为您的图片流制作一个网址,GET
它会将302或303的响应重定向到一次性网址,该网址会提供所需的图片。您可能必须销毁并重新创建图像标记以强制重新加载URL。
您也将受到浏览器的img
缓存行为的支配。而且我对HTTP规范的理解(或缺乏理解)是仁慈的。但是,除非服务器端操作不符合您的要求,否则首先尝试。它增加了服务器的复杂性,但这种方法更自然地使用浏览器。
但是使用浏览器 un - 自然呢?根据浏览器如何实现iframe
并处理其相关内容,可能能够在不泄漏内存的情况下使数据网址正常工作。这有点像弗兰肯斯坦的狗屎,正是那种无人应该做的废话。好处:它可以工作。缺点:有许多方法可以尝试,不均衡的,无证件的行为正是我所期待的。
一个想法:嵌入一个包含页面的iframe
;此页面及其嵌入使用的页面cross document messaging(请注意兼容性矩阵中的GREEN!); embeddee获取PNG字符串并将其传递给嵌入页面,然后嵌入页面生成适当的img
标记。当嵌入器需要显示新消息时,它会销毁嵌入的iframe
(希望释放数据URL的内存),然后创建一个新的消息,并将新的PNG字符串传递给它。
如果你想要稍微聪明一些,你实际上可以将embeddee页面中的嵌入式框架的源代码作为数据URL嵌入;然而,这可能会泄漏那个数据网址,我想这对于尝试这样的反应是一种诗意的正义。
“在Safari中运行的东西会更好。”浏览器技术不断向前发展。当他们没有将功能交给你时,你必须变得狡猾。
答案 7 :(得分:0)
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 =“”;
//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);
});
感谢老兄的内存泄漏。