Быстрое обновление образа с помощью Data URI вызывает кеширование, утечку памяти - PullRequest
25 голосов
/ 28 марта 2012

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

Сначала я обнаружил использование памяти, просмотрев Activity Monitor.app - процесс Google Chrome Renderer постоянно потребляет память. Затем я посмотрел на инспектора ресурсов 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 - Замените 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 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);

Ответы [ 8 ]

4 голосов
/ 28 июля 2016

Я знаю, что прошло много лет с тех пор, как эта проблема была опубликована, но проблема все еще существует в последних версиях Safari Browser.Таким образом, у меня есть окончательное решение, которое работает во всех браузерах, и я думаю, что это может спасти работу или жизнь!

Скопируйте следующий код куда-нибудь на 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"});
}

Тогда когдаВы получаете новый кадр / изображение 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;

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

3 голосов
/ 20 февраля 2015

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

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

3 голосов
/ 28 марта 2012

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

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

Вот пример, который превращает 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);

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

К сожалению, BLOB-URL не поддерживаются в 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

1 голос
/ 07 сентября 2016

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

1 голос
/ 23 мая 2014

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

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

URL.revokeObjectURL(outURL);
1 голос
/ 14 октября 2013

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

Установка img.src для dataUrl Leaks Memory

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

0 голосов
/ 10 октября 2015
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);
                    });

Сработал на утечку памяти, спасибо, чувак.

0 голосов
/ 29 марта 2012

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

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

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

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

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

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

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

...