28

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

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

有没有办法控制这种缓存?我可以把它关掉,或者做一些偷偷摸摸的事情让它永远不会发生吗?

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

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

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

快速地。显示很好。像疯了一样泄漏。

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

方法 2 - 替换imgcanvas, 并使用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 问题: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);
4

8 回答 8

5

我知道这个问题发布已经有好几年了,但这个问题在最近版本的 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"});
}

然后,当您收到base64Imagebase64 格式的新帧/图像(例如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 元素,但您可以调整代码以使其工作。

于 2016-07-27T23:14:13.340 回答
3

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

如果将图像数据放入 blob,然后创建 blob URL,则可以释放该数据。

这是一个将数据 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;

解除分配很简单:

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

于 2012-03-28T18:56:05.323 回答
3

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

这可能会有所帮助

于 2015-02-20T08:32:02.387 回答
2

我有一个非常相似的问题。

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

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

于 2013-10-14T15:08:02.973 回答
1

修补 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 移动设备中的图像,但 chrome 可以通过 websocket 快速接收 dataurls 并跟上它们,这比手动迭代字符串要好得多。如果您知道您将始终拥有相同类型的 dataurl,您甚至可以将正则表达式换成子字符串(可能更快......?)

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

URL.revokeObjectURL(outURL);
于 2014-05-23T15:37:33.913 回答
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 缓冲区。

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

于 2016-09-07T07:51:12.967 回答
0

除非 Safari 或 Mobile Safari泄漏数据 url,否则服务器端可能是在所有浏览器上执行此操作的唯一方法。

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

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

但是自然地使用浏览器呢?根据浏览器如何实现iframes 并处理其相关内容,您可能能够在不泄漏内存的情况下使数据 url 正常工作。这有点像弗兰肯斯坦的狗屎,完全是一种没有人应该做的废话。好处:它可以工作。缺点:有无数种方法可以尝试,而且不均衡、无证的行为正是我所期望的。

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

如果你想稍微聪明一点,你实际上可以在嵌入页面中嵌入嵌入框架的源作为数据 url;但是,这可能会泄漏该数据 url,我想这将是尝试这种解决方法的诗意的正义。

“在 Safari 中运行的东西会更好。” 浏览器技术不断向前发展,不平衡。当他们不把功能交给你时,你就会变得狡猾。

于 2012-03-29T14:47:19.023 回答
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);
                    });

为我工作的内存泄漏谢谢老兄。

于 2015-10-10T11:10:03.187 回答