加载更多图片时Glide出现内存不足错误

20

编辑:

  • 在我的应用程序中,我在首页加载了超过300张图片。我使用glide来加载这些图片。但是我遇到了Out of Memory Error的问题。

我在清单文件中设置了大堆内存:

android:largeHeap="true"

Glide版本:

compile 'com.github.bumptech.glide:glide:3.7.0'

设备/安卓版本:

Nexus 设备 6.0 版本

我从 Json 中获取的每张图片大小都在 800kb 到 1mb 之间。

activity_layout:

<RelativeLayout
    android:id="@+id/home_layout_bottom"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/home_layout_top_recycler"
    android:layout_margin="5dp">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list_tab_home_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:scrollbars="vertical"
        android:visibility="visible" />

    <TextView
        android:id="@+id/no_user_posts_item_tv_recycler"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/rv_list_tab_home_recycler"
        android:layout_marginTop="80dp"
        android:layout_centerHorizontal="true"
        android:text="@string/txt_no_posts_available"
        android:textColor="@color/txt_common_black"
        android:textSize="@dimen/txt_size" />
</RelativeLayout>

适配器代码:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;

    final HomePostItems rowItem = getItem(position);

    LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    if (convertView == null) {

        convertView = mInflater.inflate(R.layout.lv_adapter_post_items_layout, null);

      holder = new ViewHolder();

      holder.ivPostedImage = (ImageView) convertView.findViewById(R.id.iv_posted_img);


        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

      ..................

          Glide.with(context).load(rowItem.getPosteduserpostimage())
                        .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                        .override(600, 200)
                        .into(holder.ivPostedImage);

适配器布局.xml:

<RelativeLayout
    android:id="@+id/rl_lv_user_post_adapter_img_holder_home"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:layout_marginLeft="1dp"
    android:layout_marginRight="1dp"
    android:layout_below="@+id/tv_user_posted_msg_post_items_home" >

    <ImageView
        android:id="@+id/iv_posted_img_home"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY"
        android:background="#ffffff"
        android:contentDescription="@string/cont_desc"/>
</RelativeLayout>

日志记录:

Request threw uncaught throwable
java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at java.util.concurrent.FutureTask.report(FutureTask.java:94)
at java.util.concurrent.FutureTask.get(FutureTask.java:164)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor.afterExecute(FifoPriorityThreadPoolExecutor.java:96)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1121)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decodeStream(Downsampler.java:329)
at com.bumptech.glide.load.resource.bitmap.Downsampler.downsampleWithSize(Downsampler.java:220)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decode(Downsampler.java:153)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:50)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:19)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:39)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.java:20)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeBitmapWrapper(GifBitmapWrapperResourceDecoder.java:121)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeStream(GifBitmapWrapperResourceDecoder.java:94)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:71)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:61)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.java:22)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSourceData(DecodeJob.java:190)
at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:177)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128)
at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)
at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)
at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
at java.lang.Thread.run(Thread.java:818) 
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)

我不知道如何解决这个OOM问题。如果您已经熟悉此问题,请分享您的建议。


你把旧铃声保存在哪里? - Jas
@invisbo 在滚动图片后,我遇到了这个问题。 - Stephen
@Kaushik 你可以在这里查看:(https://www.pastiebin.com/57ea53a5c2eed) - Stephen
@Kaushik请检查这个 - Stephen
@Naruto getView的代码没问题 - Kaushik
显示剩余7条评论
11个回答

19
  • 我通过移除放在recyclerview之上的嵌套滚动视图解决了这个问题。为什么会出现OutOfMemory错误呢?因为在主页加载超过200张图片时,由于使用了嵌套滚动视图,它会加载所有这200张图片。

  • 所以我不能在适配器中逐个检查logcat图像视图的宽度和高度。

  • 移除嵌套滚动视图后修复了OutOfMemory错误,因为当进入主页活动时,它只会加载设备上显示的3张图像。

  • 还要检查这里,如何使用滚动代替嵌套滚动视图。


13

这并不是你问题的确切解决方案,但在使用Glide加载列表中的图像时,需要牢记以下几点。

问题的主要威胁是图像大小。 你获取的图像每个文件大小几乎为1mb!对于显示300多个项的列表来说,这实际上太大了。 因此,如果你也在服务器端进行操作,建议始终使用多种不同大小的图像。

例如,在显示好友列表以及其个人资料图片时,我建议你首先从服务器获取整个列表。 然后获取所有个人资料图片并将它们本地存储。 然后填充 ListView。 最重要的部分是在将用户的个人资料图片上传到服务器后,服务器需要保留它的多个尺寸版本,例如低、中和高分辨率版本。以便在为ListView提供个人资料图片URL时,服务器可能会提供低分辨率的图像,因为它们最有可能用于缩略图。

使用 RecyclerView 而不是 ListView 也是一个明智的选择。 但是,在低端设备上仍无法解决你面临的问题。

OMM与你可以程序上解决的问题无关。 你需要将你的图像调整为较低分辨率版本。

你可以查看Glide的缓存机制。建议你使用缓存策略,以便不必每次从服务器加载图像。

祝你好运。


8
使用Glide并不能保证没有内存溢出错误,您需要采用几个小步骤来降低不出现OOM的概率。 步骤1:了解Glide中的缓存机制步骤2:我更喜欢将缩略图加载到RecyclerView中。
Glide  
    .with( context )
    .load( UsageExampleGifAndVideos.gifUrl )
    .thumbnail( 0.1f )
    .into( imageView2 );

记得总是请求小尺寸的图片,如果不需要更大或高清晰度的图片。

7
  1. 确保ImageView的尺寸是match_parent或固定dp,wrap_content会导致Glide加载全分辨率位图.
  2. .placeholder()可以在大位图加载期间显示一个图像而不是空白区域。
  3. .thumbnail(float)会加载一个快速降采样的版本,同时大图像在后台加载。
  4. 另外,请查看Glide issues,也许您能找到有用的内容。

有关更多信息,请访问https://dev59.com/sIvda4cB1Zd3GeqPdsAT - Sreehari

5
使用recyclerView代替ListView。它可以重复使用单个项目来渲染项目。我正在使用gliderecyclerView,其中我加载了100多个壁纸。
在ListView中,每次创建视图时,如果您有100多个视图,它将创建100多个视图,而在recyclerview中,它只会创建屏幕上可见的项加2个。

一个ListView也不会导致任何OOME。 - Sakchham
5
ListView不会创建100多个视图,而是重用旧的视图——convertView。因此不存在这种情况。 @Naruto,你测试设备上的Android版本是多少? - Roman_D
好的,那么请检查图像大小(原始位图大小,而不是JPEG大小),它有多大?即使您设置了 skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE),OOM是否仍会发生? - Roman_D

3
原因是在滚动时,Glide 保持对图像进行处理,即使相关视图已从列表中移除。将以下代码添加到您的 ListView 的 onScrollStateChanged 中即可解决问题。
if (view.getContext() != null) {
        switch (scrollState) {
            case SCROLL_STATE_IDLE:
                Glide.with(view.getContext()).resumeRequests();
                break;
            case SCROLL_STATE_TOUCH_SCROLL:
            case SCROLL_STATE_FLING:
                Glide.with(view.getContext()).pauseRequests();
                break;
        }
    }

3

我曾面临类似的问题。现在分享我解决它的方法。创建一个名为drawable-nodpi的文件夹,将你的golive_load_imagegolive_cancel_im‌​age文件放入该文件夹中,并从其他位置(如drawable-ldpidrawable-hdpi等)删除这两个图像文件(如果你有的话)。然后添加skipMemoryCache(true)

     Glide.with(context).load(rowItem.getPosteduserpostimage())
                            .skipMemoryCache( true )
                            .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                            .override(600, 200)
                            .into(holder.ivPostedImage);

4
根据 Glide 官方文档 https://bumptech.github.io/glide/doc/caching.html,不建议跳过内存缓存 --> 一般来说,应尽量避免跳过缓存。从缓存加载图像比检索、解码和转换以创建新缩略图要快得多。 - Carlos Daniel

2
  • 你的图片不应该太大(如果太大,可以使用.thumbnail(...f))。
  • 如果你不需要强制保留图像缓存,请使用.skipMemoryCache(true)
  • 你可以使用.diskCacheStrategy(DiskCacheStrategy.NONE)来禁用磁盘缓存。

2
为了防止内存溢出错误,可以采取预防措施确保其不会发生。因此,这个问题的答案实际上是一堆建议。就像我一样。
正如@Reaz Murshed所建议的那样,我也建议将图像保存在几种不同的尺寸中。除此之外,我还想添加一些可能有助于分析和解决此问题的内容。
据我所记,OOM始终是使用错误,largeHeap只会延迟它;或者如果是大负载,则可能无法实现。因此,我建议您按照this链接诊断内存泄漏。

OutOfMemoryErrors的堆栈跟踪对于诊断它们没有任何帮助。它只告诉你它已经崩溃了,内存被填满了。这个填充发生在实际异常抛出之前很久。这也意味着通常引发OOM的东西并不是罪魁祸首。唯一的例外是当想要分配的内存量太大时,例如:要分配的数组比最大内存还要大,那么你就知道某些计算出了问题,比如一个32000x32000@4的图像将需要约4GB的内存。

如果您可以重现该问题:获取堆转储并分析应用程序的使用情况。普通的OOM诊断步骤:

  • 重现异常(等待在LogCat中看到它)

  • 获取堆转储(以分析内存泄漏)

  • 分析其中的大对象和泄漏

在上面分享的链接中,有几个关于如何获取堆转储的链接和与此相同的问题
因此,我建议您分析内存泄漏并采取必要的措施以防止OOM。
希望这可以帮助您。

1
也许可以采用不同的方法来解决这个问题。为了实现这一点,您可以使用不同的ImageAdapter
Glide.with(mActivity).loadFromMediaStore(_imageInfo.getmUri())

这不会崩溃,使用MediaStoreThumbFetcher

要更好地控制加载,请按以下步骤使用Glide v4:

// usage:
Glide.with(mActivity).load(_imageInfo)....

// in GlideModule.registerComponents
registry.prepend(ImageInfo.class, ImageInfo.class, new UnitModelLoader.Factory<ImageInfo>());
registry.prepend(ImageInfo.class, Bitmap.class, new ImageInfoBitmapDecoder(context));

class ImageInfoBitmapDecoder implements ResourceDecoder<ImageInfo, Bitmap> {
    private final ContentResolver contentResolver;
    private final BitmapPool pool;
    public ImageInfoBitmapDecoder(Context context) {
        this.contentResolver = context.getContentResolver();
        this.pool = Glide.get(context).getBitmapPool();
    }
    @Override public boolean handles(ImageInfo source, Options options) { return true; }
    @Override public @Nullable Resource<Bitmap> decode(ImageInfo source, int width, int height, Options options) {
        Bitmap thumb = Thumbnails.getThumbnail(contentResolver, source.getmId(), Thumbnails.MINI_KIND, null);
        return BitmapResource.obtain(thumb, pool);
    }
}

使用以下API,我们可以找出剩余的可用内存和位图的大小。
如果需要,您可以进行预检查以检查可用内存和位图详细信息。
检查剩余的可用内存量。
public static final float BYTES_IN_MB = 1024.0f * 1024.0f;

    public static float megabytesFree() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesUsed = rt.totalMemory();
        final float mbUsed = bytesUsed/BYTES_IN_MB;
        final float mbFree = megabytesAvailable() - mbUsed;
        return mbFree;
    }

    public static float megabytesAvailable() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesAvailable = rt.maxMemory();
        return bytesAvailable/BYTES_IN_MB;
}

检查我们想要加载的位图大小。
private void readBitmapInfo() {
        final Resources res = getActivity().getResources();
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, R.drawable.brasil, options);
        final float imageHeight = options.outHeight;
        final float imageWidth = options.outWidth;
        final String imageMimeType = options.outMimeType;
        Log.d(TAG, "w,h, type:"+imageWidth+", "+imageHeight+", "+imageMimeType);
        Log.d(TAG, "estimated memory required in MB: "+imageWidth * imageHeight * BYTES_PER_PX/MemUtils.BYTES_IN_MB);
}

更多细节请查看 Java检查内存和位图的方法GitHub讨论


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