使用通用图像加载器的视图分页器内存不足错误

11

我不确定是否应该使用带有Universal Image Loader的ViewPager作为类似画廊的界面的替代品,因为我在从SD卡加载图像并在全屏模式下查看它们时遇到了内存溢出错误。无论数量是多少,在GridView中运行得非常好,但是在ViewPager中查看图像时,每个位图会占用大量内存,并且在大约10张以上的图像后,会出现内存溢出错误。

我已经看过这里发布的几乎所有与使用Universal Image Loader时出现内存溢出错误相关的问题,每个问题都有配置错误作为原因。

我不知道是否使用了错误的配置或其他问题,但我已经浪费了很多时间,有点困惑,任何帮助和建议将不胜感激。

ImageLoader的配置:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
            .memoryCache(new WeakMemoryCache())
            .denyCacheImageMultipleSizesInMemory()
            .discCacheFileNameGenerator(new Md5FileNameGenerator())
            .imageDownloader(new ExtendedImageDownloader(getApplicationContext()))
            .tasksProcessingOrder(QueueProcessingType.LIFO)
//          .enableLogging() // Not necessary in common
            .build();

显示图像选项包括:

options = new DisplayImageOptions.Builder()
            .showImageForEmptyUri(R.drawable.image_for_empty_url)
            .resetViewBeforeLoading()
            .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
            .bitmapConfig(Bitmap.Config.RGB_565)
            .displayer(new FadeInBitmapDisplayer(300))
            .build();

我正在使用库提供的示例项目,但是那些设置也不起作用,它会在一段时间后崩溃。我的猜测是有一个特定的回调函数,我必须从那些不可见的视图中回收位图。

编辑:我知道这是内存泄漏,那些不可见的视图会在应该销毁时被销毁,但内存没有如预期释放。下面是destroyItem回调的实现,我遵循了不同问题中给出的提示,但仍然找不到内存泄漏。

@Override
        public void destroyItem(View container, int position, Object object) {
//          ((ViewPager) container).removeView((View) object);
            Log.d("DESTROY", "destroying view at position " + position);
            View view = (View)object;
            ((ViewPager) container).removeView(view);
            view = null;
        }
7个回答

5

这可能不是最佳实现方式,但对我很有效。仅删除ImageViews是不够的,因此我决定在“destroyItem”中回收位图:

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    View view = (View) object;
    ImageView imageView = (ImageView) view.findViewById(R.id.image);
    if (imageView != null) {
        Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
        bitmap.recycle();
        bitmap = null;
    }
    ((ViewPager) container).removeView(view);
    view = null;
}

虽然我希望GC会处理它们,但是当您离开活动时,这不会清除最后3个活动页面。


请注意,我没有使用“ImageLoader.cacheMemory”。如果这是一个问题,请考虑一下。 - txuslee
这段编程代码的翻译如下: @Override public void destroyItem(View collection, int position, Object view) { ((ViewPager) collection).removeView((View) view); } - Sazzad Hissain Khan
应该可以,但还不够。回收位图解决了我的崩溃问题,尽管我认为这不是一个非常正统的方法。 - txuslee

4

尝试应用以下建议:

  1. 使用 ImageScaleType.EXACTLY
  2. 启用磁盘缓存(在显示选项中)。
  3. 最后尝试使用 .discCacheExtraOptions(maxImageWidthForDiscCache,maxImageHeightForDiscCache,CompressFormat.PNG,0);

1
正如我之前提到的,我使用了在Github问题链接中提到的实现方式使其工作。这导致现在出现了另一个bug,每当我删除一张图片时,就会抛出异常(Illegal State Exception,ImageView no longer exists,should not use this PhotoViewAttacher anymore)。 奇怪的是,在getItemPosition中我返回的是POSITION_NONE,虽然这不是最好的实现方式,但对我的情况来说足够好了,你有什么想法吗? - Faraz Hassan
请查看附带的补丁,以获取关于您的新异常情况的信息:https://github.com/chrisbanes/PhotoView/pull/34 - shem
不行,没用,还是崩溃了。当页面改变时,出现相同的异常。 - Faraz Hassan
无论配置如何,使用ViewPager Imageloader仍然会导致OOM。我只加载存储在SD卡上的每个小于100Ko的JPG文件。 - Damien Locque
1
尝试在DisplayImageOptions中禁用内存缓存。 - nostra13

2

我发表此篇文章是因为在搜索 UIL 和 OOP 时,这个问题会出现在 Google 上。无论配置如何,我都遇到了 OOP 问题,但解决所有问题的方法是使用来自示例项目的两个类RecyclingImageViewRecyclingBitmapDrawable


1
我也使用了同样的库,并且遇到了相同的错误。解决方法是,我创建了一个SparseArray来保存PhotoView实例。然后像这样使用它:
 private SparseArray<PhotoView> photoViewHolder;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
       ...

       photoViewHolder = new SparseArray<PhotoView>();
       ...
 }

private class GalleryPagerAdapter extends PagerAdapter {

@Override
public View instantiateItem(ViewGroup container, int position) { 

        PhotoView photoView = new PhotoView(container.getContext());

        ImageHolder holder = new ImageHolder();
        holder.position = position;
        holder.loaded = false;

        photoView.setTag(holder);
        photoViewHolder.put(position, photoView);

                    // I used LazyList loading
        loader.DisplayImage(items.get(position), photoView);

        // Now just add PhotoView to ViewPager and return it
        container.addView(photoView, LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);

        return photoView;
    }

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    container.removeView((View) object);
    photoViewHolder.remove(position);
}

@Override
public boolean isViewFromObject(View view, Object object) {
    return view == object;
}

}

并处理viewPager的监听器:

   pager.setOnPageChangeListener(new OnPageChangeListener() { 

    @Override
    public void onPageScrollStateChanged(int position) { 

    } 

    @Override
    public void onPageScrolled(int position, float arg1, int arg2) { 

    } 

    @Override
    public void onPageSelected(int position) { 
        if(photoViewHolder.get(position) != null) {
            ImageHolder holder = (ImageHolder)photoViewHolder.get(position).getTag();
            // Do something...
        }
    } 
});

希望这有所帮助...

1
不是真的:/ 问题仍然存在:S - Faraz Hassan
所有这些你有什么想法吗?我卡在这里了 :/ - Faraz Hassan
我还没有以这种方式使用过那个库,所以我不确定出了什么问题。但是我会尽快在有空的时候检查你的代码 :) - yahya
哦,我刚意识到你可能在处理错误的问题。我之前在另一个答案的评论中提到,当我使用这个补丁时,原始问题已经被解决了:https://github.com/chrisbanes/PhotoView/pull/34但是这个补丁增加了删除功能的一个bug。现在手头的问题是,当从图库中删除一张图片时,它会从SD卡中移除,但视图没有被销毁并仍然存在。如果我强制使用POSITION_NONE来初始化所有视图,它会出现错误。希望这能澄清一下,如果你还在处理内存不足的问题。 - Faraz Hassan
现在我明白了,我之前是这么想的,“不应该有什么问题...嗯...”,所以你的问题完全不同。好吧...那我就毫无头绪了,虽然很抱歉。 - yahya
显示剩余3条评论

0

当我使用以下代码将Uri设置为ImageView时,遇到了问题:iv.setImageURI(Uri.fromFile(imgFile)); 我在使用Universal Image Loader时也遇到了同样的问题,甚至寻找了其他的图片加载器,发现了另一个好用的叫做“Picasso”,但它也有同样的问题。

所以对我有效的方法是使用GestureImageView,并通过XML将gesture-image:recycle设置为true,并使用以下代码加载图片:

            Drawable yourDrawable = null;

            try {
                InputStream inputStream = getActivity().getContentResolver().openInputStream(Uri.fromFile(img));
                yourDrawable = Drawable.createFromStream(inputStream, Uri.fromFile(img).toString() );
                inputStream.close();
            } catch (FileNotFoundException e) {
                yourDrawable = getResources().getDrawable(R.drawable.ic_launcher);
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (yourDrawable != null)
                iv.setImageDrawable(yourDrawable);

程序崩溃并出现OOM错误的原因是当图像不再显示在屏幕上时,位图没有被回收,从而导致内存泄漏。

如果有其他方法可以在普通的ImageView中回收位图,那将是更好的解决方案。

希望我能帮到你。


0

我使用了kutothe在github的issues页面上的实现。


很遗憾,链接已经失效了。 - Youngjae

-1

我知道现在已经很晚了,但是也许我的答案能够帮助到其他人节省时间。在尝试了几个小时并且查看了 Stack Overflow 上的几乎所有答案后,我最终通过使用 Fresco 图片库解决了这个问题。Fresco 是 Facebook 编写的一个库,其主要目标是以高效的方式使用内存。它非常棒,我的 Out Of Memory 错误也消失了。我强烈推荐使用它。

http://frescolib.org/


答案与所提出的问题无关。确实,这是一个可能有用但不能解决此处提到的问题的替代库。 - Faraz Hassan
我知道,但我写这篇文章是因为我曾经遇到过与UIL和View Pager完全相同的问题。我花了太多时间来解决这个问题,但内存溢出错误仍然发生。在我实施Fresco库之后(大约花费了我10分钟),这个问题再也没有发生过。添加这个库对我的项目没有负面影响——你需要做的最糟糕的事情就是用Fresco的自定义图像视图替换ImageView,而且它仍然很容易。我用Fresco替换了UIL,没有任何问题。它易于使用,似乎更加友好于内存。这就是为什么我如此推荐它,只需测试一下 :) - Jesus Christ

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