在 Glide 中加载更多图像时出现内存不足错误

Out Of Memory Error When loading more images in Glide

已编辑:

我在 manifest 中使用了 large heap true :

android:largeHeap="true"

滑行版本:

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

Device/Android版本:

Nexus 设备 6.0 版本

Every images I'm getting from Json would be 800kb to 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);

adapter_layout.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>

Logcat:

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.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.run(FifoPriorityThreadPoolExecutor.java:118)

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

原因是在滚动时,滑动会继续进行图像处理,即使相关视图已从列表中删除。在您的列表视图的 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;
        }
    }

使用 recyclerView 而不是 ListView。它可重复使用单个项目来渲染项目。我正在使用 gliderecyclerView,我正在加载包含 100 多个项目的壁纸。

在 ListView 中,每次创建视图时,如果您有 100+ 个视图,它将创建 100+ 个视图,而在 recyclerview 中,它会创建屏幕中可见项目的数量 +2。

  1. 确保 ImageView 具有 match_parent 或固定 dp 作为尺寸 wrap_content 让 Glide 加载全分辨率位图。
  2. .placeholder() 在加载大位图
  3. 时显示图像而不是空图像 space
  4. .thumbnail(float) 快速加载缩减采样版本,同时在后台加载更大的图像
  5. 也看看 Glide issues,也许你会找到有用的东西。

使用Glide并不能保证没有Out of Memory错误,您需要使用几个小步骤来降低得不到OOM's的概率。

第 1 步:了解 Glidecaching mechanism

第 2 步:我更喜欢将 thumbnails 加载到 recyclerview

Glide  
    .with( context )
    .load( UsageExampleGifAndVideos.gifUrl )
    .thumbnail( 0.1f )
    .into( imageView2 );

Remember to always request small size image if bigger or HD images are not required.

我遇到了类似的问题。我正在分享我解决它的方式。创建一个名为 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);
  • 您的图片不应太大(如果太大,请使用 .thumbnail(...f)
  • 如果您不强制将图像保存在缓存中,请使用 .skipMemoryCache(true)
  • 您可以使用.diskCacheStrategy(DiskCacheStrategy.NONE)停用磁盘缓存

这不是您问题的确切解决方案,但您在使用 Glide 加载列表中的图像时需要牢记这些事项。

问题的主要威胁部分是图像大小。你得到的图像每张几乎是 1mb!这实际上对于将它们显示到具有 300 多个项目的列表中来说太大了。所以如果你也在做服务器端,它总是建议有几种不同尺寸的图像。

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

使用 RecyclerView 代替 ListView 也是一个不错的选择。但它不会解决您在低端设备中遇到的问题。

OMM 与您无关,您可以通过编程方式解决。您需要将图像调整为较低分辨率的版本。

您也可以查看 Glide 的 caching mechanism。我建议您使用缓存策略,这样您就不必每次都从服务器加载图像。

祝你好运。

为了防止内存不足错误,可以采取预防措施以确保它不会发生。所以这个问题的答案实际上是一堆可以建议的建议。我也是。

根据 @Reaz Murshed 的建议,我还建议使用几种不同尺寸的图像。除此之外,我想补充一些可能有助于您分析和解决此问题的内容。

据我所知,OOM 总是一个使用错误,largeHeap 只会延迟它;或者,如果负载很大,那么可能是不可能的。所以我建议您按照 this link 来诊断内存泄漏。

Stack traces of OutOfMemoryErrors don't help at all for diagnosing them. It just tells you it's broken and something filled up the memory. This filling happens much before the actual exception was thrown. This also implies that usually whatever throws the OOM is not actually the culprit. The only exception to this is when the amount of wannabe allocated memory is simply too big, for example: an array to be allocated is bigger than the maximum memory, then you know that some calculation went really wrong, like a 32000x32000@4 image would take around 4GB of memory.

If you can reproduce: get a heap dump and analyze your app's usage. Normal OOM diagnostic steps:

  • Reproduce exception (wait till you see it in LogCat)

  • Take a heap dump(To analyze memory leaks)

  • Analyze it for big objects and leaks

在上面分享的 link 中,关于 how to take heap dump?issues 的其他 link 很少与此相同。

所以我建议你分析内存泄漏并采取必要的措施来防止OOM。

希望对您有所帮助。

可能可以采取不同的方法来解决这个问题。 为此,您可以使用不同的 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 methods to check memory and bitmap and github discussion

  • 我通过 删除上面的嵌套滚动视图 解决了这个问题 回收站视图。为什么发生 OutOfMemory 错误意味着,加载更多时 主页中有超过 200 张图片,它正在加载所有 200 张图片,因为使用 recyclerview 上方的嵌套滚动视图。

  • 所以我无法在 logcat 中一一检查图像视图的宽度和高度 适配器。

  • 删除嵌套滚动视图后固定内存不足error.because 回家时只会加载设备中显示的 3 张图像 activity.

  • 同时检查 this,如何使用滚动而不是嵌套滚动视图。

我遇到了同样的问题,通过在我的清单文件的应用程序标签中添加 android:largeHeap="true" 解决了这个问题,如下所示。

<manifest>
   ...
   <application
    .....
    android:largeHeap="true"
    ....
   >
   ....
   </application>
</manifest>

注意: 这应该是您最后的选择,因为不建议使用 largeHeap:true 来解决简单的 OOM 问题。