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

我的答案有两个(大)部分。第一部分更直接地针对您的问题,第二部分退后一步,因为我分享了我如何学会实施自己的解决方案,该解决方案更多地针对第一次遇到此问题的人。

不要使用bitmap.recycle(),因为你真的不应该这样做。虽然这会清除用于该位图的内存,但您可能会遇到仍然在某处使用位图的问题。

您还应该WeakReference在任何可能对象挂在位图上的地方使用(加载任务、ImageViews 等)。从文档中:

弱引用对于一旦不再引用(从外部)就应该自动删除其条目的映射很有用。SoftReference 和 WeakReference 之间的区别在于决定清除引用并将其入队的时间点:
  • 应尽可能晚地清除 SoftReference 并将其加入队列,也就是说,以防 VM 面临内存不足的危险。
  • 一旦已知弱引用,就可以清除 WeakReference 并将其加入队列。

理论上两者都应该有效,但我们有一个小问题:Java 终结器。它们不能保证及时运行,不幸的是,这就是我们的小朋友 Bitmap 正在清除它的内存的地方。如果有问题的位图创建得足够慢,GC 可能有足够的时间来识别我们的SoftReferenceWeakReference对象并将其从内存中清除,但实际上并非如此。

简而言之,在处理使用诸如位图之类的终结器的对象时,很容易超过垃圾收集器(我认为一些 IO 类也使用它们)。AWeakReference将比 a 更好地帮助我们解决计时问题SoftReference。是的,如果我们可以在内存中保存一堆图像以获得疯狂的性能,那就太好了,但是许多 Android 设备根本没有内存来执行此操作,而且我发现无论缓存有多大如果您不尽快清除引用,则仍然会遇到此问题。

就您的缓存而言,我要做的第一个更改是放弃您自己的内存缓存类,而只使用LruCache在 Android 兼容性库中找到的缓存类。并不是说您的缓存有问题或任何东西,而是它消除了另一个令人头疼的问题,它已经为您完成了,您不必维护它。

否则,我看到的最大问题是PhotoToLoad对 ImageView 的强引用,但是整个类中的更多内容可以使用一些调整。

可以在 Android 的博客Multithreading for Performance上找到一篇简短但写得很好的博客文章,解释了在下载图像时保存对正确 ImageView 的引用的好方法。您还可以在 Google 的 I/O 应用程序上看到这种做法,该应用程序的源代码可用。我在第二部分对此进行了一些扩展。

无论如何,与其尝试将正在加载的 URL 映射到它打算使用 Collection 的 ImageView,不如按照上面博客文章中所做的操作是一种优雅的方式来引用有问题的 ImageView,同时避免使用错误地回收了 ImageView。当然,这是一个很好的例子,说明 ImageView 是如何被弱引用的,这意味着我们的垃圾收集器可以更快地释放内存。

好的。现在是第二部分。

在我继续更笼统地讨论这个问题并变得更加冗长之前,我会说你走在正确的轨道上,我的其余答案可能涉及你已经涵盖并了解的很多领域,但我希望它也能让新人受益,所以请耐心等待。

如您所知,这是 Android 上一个非常常见的问题,之前已经介绍过相当长的解释(在终结器上摇拳)。在连续数小时将自己的头撞在墙上之后,尝试了加载器和缓存器的各种实现,无休止地观察日志中的“堆增长/清理竞赛”,并使用各种实现分析内存使用和跟踪对象,直到我的眼睛流血,我已经清楚了一些事情:

  1. 如果您发现自己试图告诉 GC 何时触发,那么您就走错了路。
  2. 如果您尝试调用bitmap.recycle()UI 中使用的位图(例如 ImageViews),您将陷入痛苦的世界。
  3. 这是如此令人头疼的一个主要原因是因为关于如何解决问题的这个主题有太多的错误信息。那里的许多教程或示例在理论上看起来不错,但在实践中绝对是垃圾(我上面提到的所有分析和跟踪都证实了这一点)。真是个迷宫啊!

你有两个选择。首先是使用一个知名且经过测试的库。二是学习完成这项任务的正确方法,并在此过程中获得一些有见地的知识。对于某些库,您可以同时执行这两个选项。

如果您查看这个问题,您会发现一些库可以完成您正在尝试做的事情。还有一些很好的答案指向非常有用的学习资源。

我自己选择的路线比较困难,但我痴迷于理解解决方案,而不仅仅是使用它们。如果你想走同样的路线(这是值得的),你应该首先遵循谷歌的教程“高效显示位图”

如果没有,或者您想研究 Google 自己在实践中使用的解决方案,请查看在其 I/O 2012 应用程序中处理位图加载和缓存的实用程序类。特别要学习以下课程:

当然,研究一些活动,看看他们如何使用这些类。在官方的 Android 教程和 I/O 2012 应用程序之间,我能够成功地推出自己的应用程序,以更具体地适应我正在做的事情并了解实际发生的情况。您也可以随时研究我在上面链接的问题中提到的一些库,以查看一些略有不同的结果。

于 2013-01-31T07:32:12.967 回答
0

使用后回收位图。你可以这样做

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