Подтвердить что ты не робот

Быстрое обновление изображения с помощью URI данных вызывает кеширование, утечку памяти

У меня есть веб-страница, которая быстро передает JSON с сервера и отображает его бит примерно 10 раз/секунду. Одна часть представляет собой PNG-изображение с кодировкой base64. Я нашел несколько разных способов отображения изображения, но все они вызывают неограниченное использование памяти. Он поднимается с 50 мб до 2 гб за считанные минуты. Случается с Chrome, Safari и Firefox. Не пробовал IE.

Сначала я обнаружил использование памяти, посмотрев на Activity Monitor.app - процесс Google Chrome Renderer постоянно ест память. Затем я просмотрел инспектор ресурсов Chrome (View > Developer > Developer Tools, Resources), и я увидел, что он кэширует изображения. Каждый раз, когда я менял img src или создавал новое изображение() и устанавливал его src, Chrome кэшировал его. Я могу только представить, что другие браузеры делают то же самое.

Есть ли способ контролировать это кеширование? Могу ли я отключить его или сделать что-то скрытое, чтобы этого не произошло?

Изменить: я хотел бы использовать эту технику в Safari/Mobile Safari. Кроме того, я открыт для других методов быстрого обновления изображения, если у кого есть какие-либо идеи.

Вот методы, которые я пробовал. Каждый из них находится в функции, которая вызывается при завершении AJAX.

Метод 1 - Непосредственно установите атрибут src в теге img

Fast. Отображает красиво. Утечки, как сумасшедшие.

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

Способ 2 - Замените 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;

Способ 3 - преобразовать в двоичный код и заменить canvas содержимое

Я делаю что-то неправильно здесь - изображения отображаются маленькими и похожими на случайный шум. Этот метод использует контролируемый объем памяти (увеличивается до 100 мб и останавливается), но он медленный, особенно в Safari (~ 50% использования ЦП там, 17% в Chrome). Идея исходила из этого аналогичного вопроса SO: Утечка данных URI в Safari (была: Утечка памяти с холстом 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);
4b9b3361

Ответ 1

Я не думаю, что есть какие-либо гарантии, касающиеся использования памяти URL-адресов данных. Если вы можете найти способ заставить их вести себя в одном браузере, это гарантирует мало, если не ничего о других браузерах или версиях.

Если вы поместите свои данные изображения в блокнот, а затем создайте URL-адрес blob, вы можете затем освободить эти данные.

Вот пример, который превращает URI данных в URL-адрес blob; вам может потребоваться изменить/удалить префиксы webkit- и webkit- в браузерах, отличных от Chrome и, возможно, будущих версий 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);

И вы можете использовать свой URL-адрес blob как img src.

К сожалению, URL-адреса blob не поддерживаются в IE до v10.

Ссылка на API:

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

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

Ссылка на совместимость:

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

Ответ 2

Попробуйте установить изображение .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;

Это может помочь

Ответ 3

У меня была очень похожая проблема.

Установка img.src в DataUrl Утечка памяти

Короче говоря, я просто работал над элементом Image. Я использую javascript-декодер для декодирования и отображения данных изображения на холсте. Если пользователь не попытается загрузить изображение, они никогда не узнают разницу. Другим недостатком является то, что вы ограничены современными браузерами. Верхняя сторона заключается в том, что этот метод не течет, как сито:)

Ответ 4

исправление ответа 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, но хром может получать dataurls с быстрым огнем через websocket и не отставать от них намного лучше, чем вручную перебирать строку. И если вы знаете, что всегда будете иметь тот же тип dataurl, вы даже можете поменять регулярное выражение на подстроку (вероятно, быстрее...?)

Запуск некоторых быстрых профилей памяти, похоже, что Chrome даже способен идти в ногу с освобождением (если вы помните, чтобы делать их...):

URL.revokeObjectURL(outURL);

Ответ 5

Я использовал разные методы для решения этой проблемы, ни одна из них не работает. Похоже, что память течет, когда 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-буфер.

My Electron app обновляет img каждые 50 мс, а память не течет. Забудьте об использовании диска. Управление памятью Chrome меня раздражает.

Ответ 6

Если Safari или Mobile Safari не просачивают URL-адреса данных, серверная сторона может быть единственным способом сделать это во всех браузерах.

Вероятно, наиболее простым было бы сделать URL-адрес для вашего потока изображений, GET ting дает ответ 302 или 303, перенаправляющий на одноразовый URL-адрес, который даст желаемое изображение. Вероятно, вам придется уничтожить и воссоздать теги изображений, чтобы перезагрузить URL-адрес.

Вы также будете во власти браузера относительно поведения кэширования img. И милость моего понимания (или непонимания) спецификации HTTP. Тем не менее, если операция на стороне сервера не соответствует вашим требованиям, попробуйте сначала. Это добавляет сложности сервера, но этот подход использует браузер гораздо более естественно.

Но как насчет использования браузера неестественно? В зависимости от того, как браузер реализует iframe и обрабатывает связанный с ним контент, вы можете получить данные, которые работают без утечки памяти. Это своеобразное дерьмо Франкенштейна, и это своего рода глупость, которую никто не должен делать. Потенциал: он может работать. Даунсайд: есть множество способов попробовать, и неравномерное, недокументированное поведение - именно то, что я ожидаю.

Одна идея: встроить iframe, содержащую страницу; эту страницу и страницу, в которую она встроена, перекрестная передача сообщений (обратите внимание на GREEN в матрице совместимости!); embeddee получает строку PNG и передает ее на встроенную страницу, которая затем создает соответствующий тег img. Когда встраивание должно отображать новое сообщение, оно уничтожает встроенный iframe (надеюсь, освобождая память URL-адреса данных), затем создает новый и передает ему новую строку PNG.

Если вы хотите быть немного более умным, вы можете фактически внедрить источник встроенного фрейма на странице встраивания в качестве URL-адреса данных; однако это может привести к утечке этого URL-адреса данных, который, я думаю, был бы поэтичной справедливостью для того, чтобы попытаться достичь такого уровня.

"Что-то, что работает в Safari, было бы лучше". Технология браузера продолжает двигаться неравномерно. Когда они не передают вам функциональность на тарелке, вы должны стать коварными.

Ответ 7

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

Работала для меня по утечке памяти благодаря чуваку.

Ответ 8

Я знаю, что прошло много лет с момента публикации этой проблемы, но проблема все еще существует в последних версиях браузера 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 (например, "data: image/jpeg; base64, LzlqLzRBQ..." ), затем используйте этот код:

// Assuming base64Image = "data:image/jpeg;base64, LzlqLzRBQ..."
// and imageElement is a html img object (<img />).
// Destroy old image
if(temporaryImage) objectURL.revokeObjectURL(temporaryImage);
// Create a new image from binary data
var imageDataBlog = convertDataURIToBlob(base64Image);
// Create a new object URL object
temporaryImage = objectURL.createObjectURL(imageDataBlog);
// Set the new image
imageElement.src = temporaryImage;

Повторите этот последний код столько, сколько необходимо, и никаких утечек памяти не появится. Это решение не требует использования элемента canvas, но вы можете адаптировать код, чтобы он работал.