Java.lang.OutOfMemoryError: 在ListView和延迟加载图像中,位图大小超出VM预算

5

我有一个列表视图,可能会在无限滚动时加载无限数量的项。

列表视图中的每个项都有一到两张图片,我正在使用懒加载技术进行加载。

一切正常,但当我长时间滚动列表视图时,日志窗口中出现以下错误导致应用程序崩溃:

 08-07 15:26:25.231: E/AndroidRuntime(30979): FATAL EXCEPTION: Thread-60
08-07 15:26:25.231: E/AndroidRuntime(30979): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
08-07 15:26:25.231: E/AndroidRuntime(30979):    at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
08-07 15:26:25.231: E/AndroidRuntime(30979):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:493)
08-07 15:26:25.231: E/AndroidRuntime(30979):    at com.test.android.helper.LazyImageLoader.decodeFile(LazyImageLoader.java:171)
08-07 15:26:25.231: E/AndroidRuntime(30979):    at com.test.android.helper.LazyImageLoader.getBitmap(LazyImageLoader.java:112)
08-07 15:26:25.231: E/AndroidRuntime(30979):    at com.test.android.helper.LazyImageLoader.access$2(LazyImageLoader.java:106)
08-07 15:26:25.231: E/AndroidRuntime(30979):    at com.test.android.helper.LazyImageLoader$ImageLoader.run(LazyImageLoader.java:197)

在我的懒惰图片加载器中,我将位图存储在WeakHashMap中。因此,垃圾收集器应该会收集位图,对吗?
我的懒惰图片加载器的工作方式如下:
我从适配器中使用url和imageview的引用调用displayImage()
public void displayImage(String url, ImageView imageView, int defaultImageResourceId){

        latestImageMetaData.put(imageView, url);

        if(weakhashmapcache.containsKey(url)){
            imageView.setImageBitmap(weakhashmapcache.get(url));
        }
        else{
            enqueueImage(url, imageView, defaultImageResourceId);
            imageView.setImageResource(defaultImageResourceId);
        }
    }

如果我在缓存中找到了这张图片,我会直接设置它。否则,我会使用函数enqueueImage()将其排队。

private void enqueueImage(String url, ImageView imageView, int defaultImageResourceId){ Image image = new Image(url, imageView, defaultImageResourceId); downloadqueue.add(image); // downloadQueue是一个阻塞队列,它等待添加图像 // 如果队列即将满,则删除队列中前面的元素,因为它们无论如何都不可见 Iterator iterator = downloadQueue.iterator(); while(iterator.hasNext() && downloadQueue.remainingCapacity() < 80){ downloadQueue.remove(iterator.next()); } }

And my image loader thread is this - 

class ImageLoader extends Thread {

    public void run() {
        Image firstImageInQueue;

        try {
            while((firstImageInQueue = downloadQueue.take()) != SHUTDOWN_TOKEN)
            {
                Bitmap imageBitmap = getBitmap(firstImageInQueue.url);

                if(imageBitmap != null){
                    weakhashmap.put(firstImageInQueue.url, imageBitmap);
                    BitmapDisplayer displayer = new BitmapDisplayer(imageBitmap, firstImageInQueue.imageView, firstImageInQueue.url, firstImageInQueue.defaultImageResourceId);
                    Activity activity = (Activity)firstImageInQueue.imageView.getContext();
                    activity.runOnUiThread(displayer);
                }
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            imageLoaderTerminated = true;
        }
    } 
}

getBitmap() 方法仅从URL获取图像并将其缩放解码为位图对象。 BitmapDisplayer 是一个在UI线程上设置图像到ImageView的Runnable。

您是否有什么做错了?


是的,我做了,而且有数百个类似的线程。但大多数时候,提出的答案是强制GC,这对于位图图像没有用,因为它们不存储在堆中。 - Sudarshan Bhat
你的位图有多大?如果它们比你需要的尺寸要大,使用BitmapFactory.Options将它们缩小可能是值得的。 - Alex Curran
我以前遇到过这个问题,真的很糟糕。这是4.0问题还是低于4.0的问题?其次,将图像放在SD卡中而不是从网络获取。不要将其存储在键为URL且值为图像的哈希映射中。这会消耗整个堆,如果你愿意,只需将位图的弱引用放置在其中并在需要时加载。如果可以的话,还可以将ImageView设为null。由于我在移动设备上,可能存在拼写错误。 - Jayshil Dave
显示剩余2条评论
2个回答

1

这真是一场噩梦,经过数日的研究,以下几点或许对他人有所帮助。

不要将所有位图存储在缓存中。将其在磁盘缓存和内存缓存之间交换。您存储的位图数量可以取决于通过调用

int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)) .getMemoryClass();

获得的堆限制。

我使用了LruCache而不是WeakHashMap缓存。 LruCache可在支持包中找到。很容易用LruCache替换现有的WeakHashMap实现。Android还有一个关于缓存位图的精美文档。

Jake Wharton的DiskLruCache是管理磁盘缓存的好方法。

如果您不需要它,请勿下载大型位图。尝试获取一个大小,刚好足够满足您的需求。

使用 BitmapFactory.Options,您可以在图像质量上进行一些权衡,以在内存缓存中容纳更多的图像。

如果您认为还有其他可以做的事情,请添加。


1

试试通用图像加载器吧!

这是一个开源项目,他们有关于如何使用它的博客文章。我已经在几个项目中使用过它,即使在大型长列表中也没有问题。

享受吧!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接