Ошибка памяти при загрузке большего количества изображений в Glide

Отредактировано:

  • В моем приложении загружается более 300 изображений на главной странице. Я использовал glide для загрузки изображений. Я получаю Out of Memory Error.

Я использовал большую кучу true в manifest:

android:largeHeap="true"

Версия Glide:

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

Версия устройства /Android:

Версия 6.0 для Nexus Device

Каждое изображение, которое я получаю от Json, будет от 800 КБ до 1 МБ.

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$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. Поделитесь своим предложением, если вы уже знакомы с этой проблемой.

Ответ 1

  • Я решил эту проблему с помощью удаления вложенного представления прокрутки, помещенного выше recyclerview. Почему произошла ошибка OutOfMemory, при загрузке больше чем 200 изображений на домашней странице, загружает все 200 изображений из-за использования вложенное представление прокрутки выше recyclerview.

  • Таким образом, я не могу проверить ширину и высоту изображения в режиме logcat один за другим в адаптер.

  • После удаления вложенного просмотра прокрутки исправлена ​​ошибка с памятью. будет загружать только 3 изображения, отображаемые в устройстве при приходе на родину деятельность.

  • Также проверьте this, как использовать прокрутку вместо вложенного представления прокрутки.

Ответ 2

Это не точное решение вашей проблемы, но вы должны помнить об этом при загрузке изображений в списке с помощью Glide.

Основной угрожающей частью вашей проблемы является размер изображения. Изображение, которое вы получаете, составляет почти 1 мб каждый! Это на самом деле слишком велико для отображения их в список, содержащий более 300 предметов. Поэтому, если вы тоже делаете серверную сторону, всегда рекомендуется иметь изображения разных размеров.

Например, если вы показываете список друзей вместе со своими изображениями профиля, я предлагаю вам сначала получить весь список с сервера. Затем загрузите все изображения профиля и сохраните их локально. Затем залейте ListView. И самая важная часть - при загрузке изображения профиля пользователя на сервер, после его загрузки серверу необходимо сохранить несколько его размеров, например. низкое, среднее и высокое разрешение. Чтобы при показе URL-адреса профиля для ListView сервер мог предоставить изображения с низким разрешением, поскольку они будут использоваться, скорее всего, для эскизов.

Использование RecyclerView вместо ListView - хороший вызов. Но это не решит проблему, возникшую у вас здесь, когда вы находитесь в устройстве с низким уровнем обслуживания.

OMM не имеет никакого отношения к тому, что вы можете решить программно. Вам нужно изменить размер изображения до более низкой версии.

Вы также можете проверить механизм кэширования Glide . Я бы предложил вам использовать стратегию кэширования, чтобы каждый раз, когда вам не нужно было загружать изображение с сервера.

Удачи.

Ответ 3

Используйте recyclerView вместо ListView. Это многоразовый элемент для рендеринга элементов. Я использую glide с recyclerView, где я загружаю обои с более чем 100 элементами.

В ListView каждый раз, когда вы создаете представление, если у вас есть более 100 просмотров, он будет создавать более 100 представлений, где, как и в recyclerview, он создает количество видимых элементов на экране +2.

Ответ 4

Использование Glide не гарантирует отсутствие ошибок Out of Memory, вам нужно использовать несколько небольших шагов, чтобы уменьшить вероятность не получить OOM's.

Шаг 1: Поймите механизм кеширования в Glide

Шаг 2: Я предпочитаю загружать thumbnails в recyclerview

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

Не забудьте всегда запрашивать изображение небольшого размера, если больше или HD-изображения не требуется.

Ответ 5

  • Убедитесь, что ImageView имеет match_parent или fixed dp в качестве размеров wrap_content делает полное скольжение полной загрузки растровых изображений.
  • .placeholder() показывает изображение вместо пустого пространства при загрузке большого растрового изображения
  • .thumbnail(float) быстро загружает понижающую дискретизацию, в то время как большее изображение загружается в фоновом режиме.
  • Также посмотрите Glide issues, возможно, вы найдете что-то полезное.

Ответ 6

Я столкнулся с подобной проблемой. Я разделяю то, как я это решил. Создайте папку с именем drawable-nodpi, поместите файлы golive_load_image и golive_cancel_im‌​age в эту папку и удалите эти два файла изображения из другого места, например drawable-ldpi, drawable-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);

Ответ 7

  • Ваши изображения не должны быть слишком большими (если они есть, используйте .thumbnail(...f))
  • используйте .skipMemoryCache(true), если вы не хотите хранить изображения в кеше
  • вы можете использовать .diskCacheStrategy(DiskCacheStrategy.NONE) для деактивации кеша диска

Ответ 8

Чтобы предотвратить ошибку "Из памяти", вы можете просто принять меры предосторожности, чтобы убедиться, что этого не произойдет. Таким образом, ответ на этот вопрос на самом деле представляет собой набор предложений, которые можно предложить. Я тоже.

Как было предложено @Reaz Murshed, я также рекомендую иметь изображения в разных размерах. Помимо этого, я хотел бы добавить еще несколько вещей, которые могут помочь вам проанализировать эту проблему и решить ее.

Насколько я помню, OOM всегда была ошибкой использования, largeHeap просто задерживает его; или если это большая нагрузка, то, возможно, это невозможно. Поэтому я предлагаю вам следовать этой ссылке для диагностики утечек памяти.

Трассировки стека OutOfMemoryErrors вообще не помогают диагностировать их. Это просто говорит вам об этом, и что-то пополнило Память. Это заполнение происходит задолго до того, как фактическое исключение было выброшены. Это также подразумевает, что, как правило, независимо от того, что выбрал OOM, на самом деле преступник. Единственным исключением является то, когда количество выделенная память wannabe просто слишком большая, например: массив выделяется больше, чем максимальная память, тогда вы знаете, что некоторые вычисления пошли очень неправильно, например, изображение 32000x32000 @4 возьмите около 4 ГБ памяти.

Если вы можете воспроизвести: получите кучу кучи и проанализируйте использование своего приложения. Обычные этапы диагностики OOM:

  • Воспроизводить исключение (подождите, пока вы не увидите его в LogCat)

  • Возьмите кучу кучи (для анализа утечек памяти)

  • Проанализируйте его для больших объектов и утечек

В приведенной выше ссылке есть несколько других ссылок относительно how to take heap dump? и issues, которые идентичны этому.

Поэтому я предлагаю вам проанализировать утечки памяти и предпринять необходимые шаги для предотвращения OOM.

Надеюсь, это поможет вам.

Ответ 9

Возможно, для решения этого может быть использован другой подход. Для этого вы можете использовать другое 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

Ответ 10

Причина в том, что при прокрутке, скольжение продолжает делать процесс изображения, даже связанные виды удаляются из списка. Добавьте этот код в свой список viewScrollStateChanged.

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;
        }
    }