2

ビットマップの読み込みに Fedor のコード (https://github.com/thest1/LazyList) を使用しています。要件に応じて、数行のコードを変更しました。ヒープ メモリがしきい値を超えると、メモリ不足エラーが発生します。同じトピックにさまざまな質問が投稿されています。それらのほとんどは、SoftReference とビットマップ recycle() を使用することを提案しています。SoftReference を使用していますが、まだ問題に直面しています。また、ビットマップリサイクルメソッドをどこで使用するかについても混乱しています。

MemoryCache.java

public class MemoryCache {

private static final String TAG = "MemoryCache";
private static Map<String, SoftReference<Bitmap>> cache=Collections.synchronizedMap(
        new LinkedHashMap<String, SoftReference<Bitmap>>(16,0.75f,false));//Last argument true for LRU ordering
private long size=0;//current allocated size
private long limit=30000000;//max memory in bytes

public MemoryCache(){

    long cacheSize = Runtime.getRuntime().maxMemory();
    setLimit(cacheSize);
}

public void setLimit(long new_limit){
    limit=new_limit;

}

public Bitmap get(String id){
    try{
        if(!cache.containsKey(id))
            return null;
        //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 
        return cache.get(id).get();
    }catch(NullPointerException ex){
        ex.printStackTrace();
        return null;
    }
}

public void put(String id, Bitmap bitmap){
    try{
        if(cache.containsKey(id))
            size-=getSizeInBytes(cache.get(id).get());
        cache.put(id, new SoftReference<Bitmap>(bitmap));
        size+=getSizeInBytes(bitmap);
        checkSize();
    }catch(Throwable th){
        th.printStackTrace();
    }
}

private void checkSize() {
    Log.i(TAG, "cache size="+size+" length="+cache.size());

    if(size>limit+5000000){
       cache.clear();
        }
        Log.i(TAG, "Clean cache. New size "+cache.size());

}

public void clear() {
    try{
        //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 
        cache.clear();
        size=0;
    }catch(NullPointerException ex){
        ex.printStackTrace();
    }
}

long getSizeInBytes(Bitmap bitmap) {
    if(bitmap==null)
        return 0;
    return bitmap.getRowBytes() * bitmap.getHeight();
}
}

ImageLoader.java

public class ImageLoader {

MemoryCache memoryCache = new MemoryCache();
FileCache fileCache;
private Map<ImageView, String> imageViews = Collections
        .synchronizedMap(new WeakHashMap<ImageView, String>());
ExecutorService executorService;
Handler handler = new Handler();
Context con;
ProgressBar pb;

public ImageLoader(Context context) {
    fileCache = new FileCache(context);
    this.con = context;
    executorService = Executors.newFixedThreadPool(5);
}

final int stub_id = R.drawable.icon_loading;

public void DisplayImage(String url, ImageView imageView, ProgressBar pb) {
    this.pb = pb;

    imageViews.put(imageView, url);
    Bitmap bitmap = memoryCache.get(url);
    if (bitmap != null) {
        pb.setVisibility(View.GONE);
        imageView.setImageBitmap(bitmap);

    } else {
        queuePhoto(url, imageView);


    }
}

private void queuePhoto(String url, ImageView imageView) {
    PhotoToLoad p = new PhotoToLoad(url, imageView);
    executorService.submit(new PhotosLoader(p));
}

private Bitmap getBitmap(String url) {
    Bitmap result = null;

    File f = fileCache.getFile(url);

    // from SD cache
    Bitmap b = decodeFile(f);
    if (b != null)
        return b;

    // from web
    try {
        Bitmap bitmap = null;
        URL imageUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) imageUrl
                .openConnection();
        conn.setInstanceFollowRedirects(true);
        InputStream is = conn.getInputStream();
        OutputStream os = new FileOutputStream(f);
        Utils.CopyStream(is, os);
        os.close();
        is.close();
        conn.disconnect();
        bitmap = decodeFile(f);
        //Log.v("bitmap size", bitmap.getByteCount() + "");
        //bitmap.recycle();
        return bitmap;
    } catch (Throwable ex) {
        ex.printStackTrace();
        if (ex instanceof OutOfMemoryError)
            memoryCache.clear();
        return null;
    }


}



// decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {


        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        FileInputStream stream1 = new FileInputStream(f);
        BitmapFactory.decodeStream(stream1, null, o);
        stream1.close();


        final int REQUIRED_SIZE = 70;
        int width_tmp = o.outWidth, height_tmp = o.outHeight;
        int scale = 1;

        BitmapFactory.Options o2 = new BitmapFactory.Options();

        if (f.length() > 300000) {

            o2.inSampleSize = 4;
        } else if (f.length() > 200000) {

            o2.inSampleSize = 2;
        } else {

            o2.inSampleSize = 1;
        }
        FileInputStream stream2 = new FileInputStream(f);
        Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
        stream2.close();
        return bitmap;
    } catch (FileNotFoundException e) {
    } catch (IOException e) {
        memoryCache.clear();
        e.printStackTrace();
    }
    return null;
}

// Task for the queue
private class PhotoToLoad {
    public String logo_url;
    public ImageView imageView;

    public PhotoToLoad(String u, ImageView i) {
        logo_url = u;
        imageView = i;
    }
}

class PhotosLoader implements Runnable {
    PhotoToLoad photoToLoad;

    PhotosLoader(PhotoToLoad photoToLoad) {
        this.photoToLoad = photoToLoad;
    }

    @Override
    public void run() {
        if (imageViewReused(photoToLoad))
            return;
        Bitmap bmp = getBitmap(photoToLoad.logo_url);
        // Log.v("bitmap size",bmp.getByteCount()+"");


        memoryCache.put(photoToLoad.logo_url, bmp);

        if (imageViewReused(photoToLoad))
            return;
        BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
        //bmp.recycle();
        // Activity a=(Activity)photoToLoad.imageView.getContext();
        // a.runOnUiThread(bd);
        handler.post(bd);
    }
}

boolean imageViewReused(PhotoToLoad photoToLoad) {
    String tag = imageViews.get(photoToLoad.imageView);
    if (tag == null || !tag.equals(photoToLoad.logo_url))
        return true;
    return false;
}

// Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable {
    Bitmap bitmap;
    PhotoToLoad photoToLoad;

    public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
        bitmap = b;
        photoToLoad = p;
    }

    public void run() {
        if (imageViewReused(photoToLoad))
            return;
        if (bitmap != null) {
            //bitmap.recycle();
            // pb.setVisibility(View.GONE);
            photoToLoad.imageView.setImageBitmap(bitmap);
            //bitmap.recycle();
        } else {
            // photoToLoad.imageView.setImageResource(stub_id);
            // pb.setVisibility(View.VISIBLE);
        }
    }
}

public void clearCache() {
    memoryCache.clear();
    fileCache.clear();
}

}

Logcat出力を添付:

01-21 16:54:47.348: D/skia(20335): --- decoder->decode returned false
01-21 16:54:47.408: I/dalvikvm-heap(20335): Clamp target GC heap from 69.438MB to 64.000MB
01-21 16:54:47.408: D/dalvikvm(20335): GC_FOR_ALLOC freed 67K, 5% free 62767K/65416K, paused 54ms, total 54ms
01-21 16:54:47.408: I/dalvikvm-heap(20335): Forcing collection of SoftReferences for 228816-byte allocation
01-21 16:54:47.468: I/dalvikvm-heap(20335): Clamp target GC heap from 69.438MB to 64.000MB
01-21 16:54:47.468: D/dalvikvm(20335): GC_BEFORE_OOM freed <1K, 5% free 62767K/65416K, paused 64ms, total 64ms
01-21 16:54:47.468: E/dalvikvm-heap(20335): Out of memory on a 228816-byte allocation.
01-21 16:54:47.468: I/dalvikvm(20335): "pool-21-thread-4" prio=5 tid=63 RUNNABLE
01-21 16:54:47.468: I/dalvikvm(20335):   | group="main" sCount=0 dsCount=0 obj=0x42e4e878 self=0x67693b00
01-21 16:54:47.468: I/dalvikvm(20335):   | sysTid=20520 nice=0 sched=0/0 cgrp=apps handle=1735190240
01-21 16:54:47.468: I/dalvikvm(20335):   | state=R schedstat=( 2851815000 268321000 1461 ) utm=276 stm=9 core=0
01-21 16:54:47.468: I/dalvikvm(20335):   at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-21 16:54:47.468: I/dalvikvm(20335):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:529)
01-21 16:54:47.468: I/dalvikvm(20335):   at com.main.util.ImageLoader.decodeFile(ImageLoader.java:211)
01-21 16:54:47.468: I/dalvikvm(20335):   at com.main.util.ImageLoader.getBitmap(ImageLoader.java:85)
01-21 16:54:47.468: I/dalvikvm(20335):   at com.main.util.ImageLoader.access$1(ImageLoader.java:79)
01-21 16:54:47.468: I/dalvikvm(20335):   at com.main.util.ImageLoader$PhotosLoader.run(ImageLoader.java:244)
01-21 16:54:47.468: I/dalvikvm(20335):   at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
01-21 16:54:47.468: I/dalvikvm(20335):   at java.util.concurrent.FutureTask.run(FutureTask.java:234)
01-21 16:54:47.468: I/dalvikvm(20335):   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
01-21 16:54:47.468: I/dalvikvm(20335):   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
01-21 16:54:47.468: I/dalvikvm(20335):   at java.lang.Thread.run(Thread.java:856)
4

2 に答える 2

6

私の答えには2つの(大きな)部分があります。最初の部分はあなたの質問をより直接的に対象としており、2 番目の部分は一歩下がって、初めてこれに遭遇する人々を対象とした独自のソリューションを実装する方法を私がどのように学んだかを共有します.

を使用しないでくださいbitmap.recycle()。実際に使用する必要はないからです。これにより、そのビットマップに使用されているメモリがクリアされますが、ビットマップがまだどこかで使用されているという問題が発生する可能性があります。

また、オブジェクトがビットマップにハングアップする可能性があるすべての場所で使用する必要がありますWeakReference(Tasks、ImageViews などのロード)。ドキュメントから:

弱い参照は、(外部から) 参照されなくなったらエントリを自動的に削除する必要があるマッピングに役立ちます。SoftReference と WeakReference の違いは、参照をクリアしてキューに入れるかどうかが決定される時点です。
  • SoftReference は、VM がメモリ不足になる危険性がある場合に備えて、できるだけ遅くクリアしてキューに入れる必要があります。
  • WeakReference は、弱参照であることが判明し次第、クリアしてキューに入れることができます。

理論的にはどちらも機能するはずですが、Java ファイナライザーという小さな問題があります。それらは時間内に実行されるとは限りません。残念ながら、私たちの小さな友人である Bitmap がメモリをクリアしているところです。問題のビットマップが十分にゆっくりと作成される場合、GC はおそらくSoftReferenceorWeakReferenceオブジェクトを認識してメモリからクリアするのに十分な時間がありますが、実際にはそうではありません。

要するに、ビットマップなどのファイナライザーを使用するオブジェクトを操作する場合、ガベージ コレクターを追い越すのは非常に簡単です (一部の IO クラスもそれらを使用すると思います)。AWeakReferenceは、タイミングの問題を A よりも少し助けますSoftReference。ええ、非常識なパフォーマンスのために大量の画像をメモリに保持できればいいのですが、多くの Android デバイスにはこれを行うためのメモリがありません。できるだけ早く参照をクリアしないと、この問題が発生します。

キャッシングに関する限り、私が最初に行う変更は、独自のメモリ キャッシュ クラスを捨ててLruCache、Android 互換性ライブラリにある を使用することです。キャッシュに問題があるわけではありませんが、もう 1 つの頭痛の種が取り除かれます。キャッシュは既に作成されており、維持する必要はありません。

それ以外の場合、あなたが持っているもので私が目にする最大の問題はPhotoToLoad、 ImageView への強い参照を保持していることですが、このクラス全体の多くは微調整を使用できます。

画像のダウンロード中に正しい ImageView への参照を保持するための優れた方法を説明する、短いながらも適切に書かれたブログ投稿は、Android のブログ、Multithreading for Performance にあります。ソースコードが入手可能な Google の I/O アプリでも、この種の実践が行われているのを見ることができます。これについては、第 2 部で少し詳しく説明します。

とにかく、ロードされている URL をコレクションで意図されている ImageView にマップしようとする代わりに、上記のブログ投稿で行われたことに従うことは、使用を避けながら問題の ImageView を参照するエレガントな方法です。誤って ImageView をリサイクルしました。そしてもちろん、これは ImageView がすべて弱く参照されていることを示す良い例です。つまり、ガベージ コレクターがそのメモリをより速く解放できることを意味します。

わかった。今第二部。

この問題の一般的な話を続けて、さらに長文になる前に、あなたは正しい道を進んでいると言いたいと思います。しかし、これが新しい人にも役立つことを願っていますので、ご容赦ください。

ご存知のように、これは Android で非常に一般的な問題であり、かなり長い説明が以前に取り上げられています (ファイナライザーで拳を振る)。何時間にもわたって頭を壁にぶつけ、ローダーとキャッシャーのさまざまな実装を試し、ログで「ヒープの成長/クリーンアップの競争」を際限なく観察し、メモリ使用量をプロファイリングし、さまざまな実装でオブジェクトをトレースして、目が血を流した後、いくつかのことが明らかになりました:

  1. 起動するタイミングを GC に伝えようとしていることに気付いた場合は、間違った道を進んでいます。
  2. bitmap.recycle()ImageView などの UI で使用されるビットマップを呼び出そうとすると、大変なことになります。
  3. これが頭痛の種である主な理由は、問題の解決方法に関するこのトピックに関する誤った情報が多すぎるためです。そこにあるチュートリアルや例の多くは、理論的には良さそうに見えますが、実際には絶対にゴミです (上記のすべてのプロファイリングとトレースによって確認されています)。ナビゲートするのはなんて迷路でしょう!

2 つのオプションがあります。1 つ目は、よく知られたテスト済みのライブラリを使用することです。2 つ目は、このタスクを達成するための正しい方法を学び、その過程で洞察に満ちた知識を得ることです。一部のライブラリでは、両方のオプションを実行できます。

この質問を見ると、あなたがやろうとしていることを達成するライブラリがいくつか見つかります。また、非常に役立つ学習リソースを示す優れた回答がいくつかあります。

私自身が取ったルートはより困難なものでしたが、単にソリューションを使用するだけでなく、ソリューションを理解することに夢中になっています. 同じルートをたどりたい場合 (それだけの価値はあります)、まず Google のチュートリアル「ビットマップを効率的に表示する」に従う必要があります。

それでも問題が解決しない場合、または Google 自身が実際に使用しているソリューションを調べたい場合は、I/O 2012 アプリでビットマップの読み込みとキャッシュを処理するユーティリティ クラスを確認してください。特に、次のクラスを学習してください。

もちろん、いくつかのアクティビティを調べて、これらのクラスがどのように使用されているかを確認してください。公式の Android チュートリアルと I/O 2012 アプリの間で、自分がやっていることをより具体的に当てはめ、実際に何が起こっているのかを理解するために、自分自身をうまくロールバックすることができました. 上でリンクした質問で言及したライブラリのいくつかをいつでも調べて、わずかに異なるいくつかのテイクを確認することもできます。

于 2013-01-31T07:32:12.967 に答える
0

使用後にビットマップをリサイクルします。あなたはそれを行うことができます

bitmap.recycle();
于 2013-01-21T11:07:22.717 に答える