使用数据URI快速更新图像会导致缓存、内存泄漏

Rapidly updating image with Data URI causes caching, memory leak

本文关键字:缓存 泄漏 内存 图像 数据 URI 更新      更新时间:2023-09-26

我有一个网页,可以从服务器快速流式传输JSON,并显示其中的部分,大约每秒10次。一部分是base64编码的PNG图像。我已经找到了几种不同的方法来显示图像,但所有这些方法都会导致无限制的内存使用。它在几分钟内从50mb上升到2gb。适用于Chrome、Safari和Firefox。尚未尝试IE.

我首先通过查看ActivityMonitor.app发现了内存使用情况——Google Chrome Renderer进程不断消耗内存。然后,我查看了Chrome的资源检查器(View>Developer>Developer ToolsResources),发现它正在缓存图像。每次我更改img src,或者创建一个新的Image()并设置其src时,Chrome都会缓存它。我只能想象其他浏览器也在做同样的事情。

有什么方法可以控制这种缓存吗?我能把它关掉吗,或者偷偷做点什么,这样它就不会发生了?

编辑:我希望能够在Safari/Mobile Safari中使用该技术。此外,如果有人有任何想法,我对其他快速刷新图像的方法持开放态度。

以下是我尝试过的方法。每一个都驻留在一个函数中,该函数在AJAX完成时被调用。

方法1-直接在img标记上设置src属性

快。显示良好。像疯了一样泄漏。

$('#placeholder_img').attr('src', 'data:image/png;base64,' + imgString);

方法2-用canvas替换img,并使用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;

方法3-转换为二进制并替换canvas内容

我在这里做错了——图像显示得很小,看起来像随机噪声。这种方法使用可控的内存量(增长到100mb并停止),但速度较慢,尤其是在Safari中(CPU使用率约为50%,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);

我知道这个问题发布已经好几年了,但这个问题在最近版本的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元素,但您可以调整代码使其工作。

绘图后尝试设置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;

这可能有助于

我不认为对数据URL的内存使用有任何保证。如果你能找到一种方法让它们在一个浏览器中运行,那么它几乎不能保证其他浏览器或版本。

如果您将图像数据放入blob中,然后创建blob URL,则可以解除分配该数据。

下面是一个将数据URI转换为blobURL的示例;您可能需要更改/删除CCD_ 17&除Chrome之外的浏览器上的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 URL用作imgsrc

不幸的是,在v10之前的IE中似乎不支持blob URL。

API参考:

http://www.w3.org/TR/FileAPI/#dfn-createObjectURL

http://www.w3.org/TR/FileAPI/#dfn-revokeObjectURL

兼容性参考:

http://caniuse.com/#search=blob%20url

我也遇到过类似的问题。

将img.src设置为dataUrl会泄漏内存

长话短说,我只是围绕着图像元素工作。我使用javascript解码器来解码图像数据并将其显示在画布上。除非用户尝试下载图像,否则他们也永远不会知道其中的区别。另一个缺点是,您将被限制在现代浏览器中。有利的一面是,这种方法不会像筛子一样泄漏:)

修补ellisbben的答案,因为BlobBuilder已经过时https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView提供了从base64到UInt8Array:的快速转换

在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 mobile中真正更新图像,但chrome可以通过websocket快速接收数据URL并跟上它们,这比手动迭代字符串要好得多。如果你知道你总是有相同类型的dataurl,你甚至可以把regex换成子字符串(可能更快…?)

运行一些快速内存配置文件,看起来Chrome甚至能够跟上释放(如果你记得这样做的话…):

URL.revokeObjectURL(outURL);

我使用了不同的方法来解决这个问题,但都不起作用。当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缓冲区。

我的Electron应用程序每50毫秒更新一次img,内存不会泄漏。忘记磁盘使用情况。Chrome的内存管理让我很恼火。

除非Safari或Mobile Safari不泄露数据URL,否则服务器端可能是在所有浏览器上做到这一点的唯一方法。

可能最简单的是为您的图像流创建一个URL,GET会给出302或303响应,重定向到一个一次性URL,该URL将提供所需的图像。您可能需要销毁并重新创建图像标签,以强制重新加载URL。

对于img缓存行为,您也将受到浏览器的支配。还有我对HTTP规范的理解(或缺乏理解)。不过,除非服务器端操作不符合您的要求,否则请先尝试一下。它增加了服务器的复杂性,但这种方法更自然地使用浏览器。

但是,自然地使用浏览器un呢?根据浏览器如何实现iframe并处理其相关内容,可能能够在不泄漏内存的情况下使数据URL正常工作。这有点像弗兰肯斯坦的大便,这正是任何人都不应该做的胡说八道。不利的一面是:有很多方法可以尝试,而不均衡的、未记录的行为正是我所期望的。

一个想法是:嵌入一个包含页面的iframe;该页面及其嵌入的页面使用跨文档消息传递(注意兼容性矩阵中的绿色!);embeddee获取PNG字符串并将其传递给嵌入页面,然后嵌入页面生成适当的img标记。当embeddee需要显示一条新消息时,它会破坏嵌入的iframe(希望释放数据url的内存),然后创建一个新消息并向其传递新的PNG字符串。

如果你想稍微聪明一点,你可以将嵌入框架的源代码作为数据url嵌入到embeddee页面中;然而,这可能会泄露数据url,我想这将是尝试这样一种方法的诗意正义。

"在Safari中运行的东西会更好。"浏览器技术不断向前发展,但并不均衡。当他们不把功能放在盘子里交给你时,你就得耍花招了。

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);
                    });

为我处理内存泄漏谢谢伙计。