web-dev-qa-db-ja.com

データURIで画像を迅速に更新すると、キャッシュ、メモリリークが発生します

サーバーからJSONを高速にストリーミングし、そのビットを1秒あたり約10回表示するWebページがあります。一部はbase64でエンコードされたPNG画像です。画像を表示するいくつかの異なる方法を見つけましたが、それらはすべて無制限のメモリ使用量を引き起こします。それは数分以内に50mbから2gbに上昇します。 Chrome、Safari、Firefoxで発生します。 IEを試したことがありません。

最初にActivityMonitor.appを調べてメモリ使用量を発見しました。GoogleChromeレンダラープロセスは継続的にメモリを消費します。次に、Chromeのリソースインスペクター(View> Developer> Developer ToolsResources)、そして私はそれが画像をキャッチしているのを見ました。 img srcを変更するか、新しいImage()を作成して、そのsrcを設定しました、Chromeキャッシュされました。他のブラウザが同じことをしていると想像できます。

このキャッシュを制御する方法はありますか?それをオフにすることはできますか、それともそれが決して起こらないように卑劣なことをすることはできますか?

編集:Safari/MobileSafariでこのテクニックを使用できるようにしたいと思います。また、誰かアイデアがあれば、画像をすばやく更新する他の方法も利用できます。

これが私が試した方法です。それぞれは、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の内容を置き換えます

私はここで何か間違ったことをしています-画像は小さく表示され、ランダムなノイズのように見えます。この方法では、制御された量のメモリを使用します(100 MBに増加して停止します)が、特に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);
25
Dave Ceddia

この問題が投稿されてから何年も経っていますが、最近のバージョンの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"});
}

次に、新しいフレーム/画像を受け取ったとき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要素を使用する必要はありませんが、コードを調整して機能させることができます。

4
Panchosoft

データURLのメモリ使用量については何の保証もないと思います。それらを1つのブラウザーで動作させる方法を理解できれば、他のブラウザーやバージョンについてはほとんど保証されません。

画像データをblobに配置してからblobURLを作成すると、そのデータの割り当てを解除できます。

これは、データURIをblobURLに変換する例です。 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);

また、blobURLをimgsrcとして使用できます。

残念ながら、blobURLはv10より前のIE)ではサポートされていないようです。

APIリファレンス:

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

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

互換性リファレンス:

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

3
ellisbben

描画後、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
Jakke

私は非常によく似た問題を抱えていました。

img.srcをdataUrlリークメモリに設定

簡単に言うと、私は単にImage要素を回避しました。私はjavascriptデコーダーを使用して、画像データをデコードしてキャンバスに表示します。ユーザーが画像をダウンロードしようとしない限り、違いもわかりません。もう1つの欠点は、最新のブラウザーに制限されることです。利点は、この方法がふるいのように漏れないことです:)

1
Paul Milham

私はこの問題を解決するためにさまざまな方法を使用しましたが、どれも機能しません。 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アプリは50msごとにimgを更新し、メモリはリークしません。ディスク使用量を忘れてください。 Chromeのメモリ管理は私を怒らせます。

1
Keven Sun

blobBuilderは廃止されており、 https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView は、base64からへの素早い変換のように見えるため、ellisbbenの回答にパッチを適用します。 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を介してdataurlsを迅速に受信し、文字列を手動で繰り返すよりもはるかに優れた方法でそれらに対応できます。また、常に同じタイプのdataurlを使用することがわかっている場合は、正規表現を部分文字列に交換することもできます(おそらくより高速です...?)

いくつかのクイックメモリプロファイルを実行すると、Chromeは割り当て解除に追いつくことさえできます(覚えていれば...):

URL.revokeObjectURL(outURL);
1
user3669490

SafariまたはMobileSafari do n'tデータのURLをリークしない限り、すべてのブラウザでこれを行う唯一の方法はサーバー側である可能性があります。

おそらく最も簡単なのは、画像ストリームのURLを作成することです。GETtingは、目的の画像を提供する1回限りのURLにリダイレクトする302または303応答を提供します。 URLを強制的に再読み込みするには、画像タグを破棄して再作成する必要があります。

また、imgのキャッシュ動作に関してブラウザに翻弄されることになります。そして、HTTP仕様の私の理解(または理解の欠如)の慈悲。それでも、サーバー側の操作が要件に合わない場合を除いて、最初にこれを試してください。サーバーの複雑さが増しますが、このアプローチではブラウザーがはるかに自然に使用されます。

しかし、ブラウザを使用するのはどうですかn-当然ですか?ブラウザがiframesを実装し、関連するコンテンツを処理する方法に応じて、mightメモリをリークすることなくデータURLを機能させることができます。これはちょっとフランケンシュタインのたわごとであり、誰もする必要のない一種のナンセンスです。利点:それはうまくいく可能性があります。欠点:それを試す方法は無数にあり、不均一で文書化されていない動作はまさに私が期待するものです。

1つのアイデア:ページを含むiframeを埋め込みます。このページとそれが使用に埋め込まれているページ クロスドキュメントメッセージング (互換性マトリックスの緑に注意してください!); embeddeeはPNG文字列を取得し、それを埋め込みページに渡します。埋め込みページは、適切なimgタグを作成します。埋め込み者が新しいメッセージを表示する必要がある場合、埋め込まれたiframeを破棄し(できればデータURLのメモリを解放します)、新しいメッセージを作成して新しいPNG文字列を渡します。

少し賢くしたい場合は、実際に埋め込みフレームのソースをデータURLとして埋め込みページに埋め込むことができます。ただし、これによりそのデータURLがリークする可能性があります。これは、このようなリーチアラウンドを試みるための勧善懲悪になると思います。

「Safariで機能するものの方が良いでしょう。」ブラウザ技術は、不均一に前進し続けています。彼らがプレート上であなたに機能を渡さないとき、あなたは邪悪にならなければなりません。

0
ellisbben
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
Jay