Android-universal-image-loader: Изображение мерцает при notifyDataSetChanged ()

Созданный на 20 авг. 2013  ·  36Комментарии  ·  Источник: nostra13/Android-Universal-Image-Loader

Проблемы, вызванные этой ошибкой:

  1. изображение мерцает
  2. если вы загружаете сетку изображений, прокрутите вниз, затем прокрутите вверх, изображения снова перезагружаются
  3. первая партия изображений занимает ВДВОЕ больше памяти.

Похоже, здесь есть ошибка:

/* com.nostra13.universalimageloader.core.ImageLoader:193 */
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, configuration.maxImageWidthForMemoryCache, configuration.maxImageHeightForMemoryCache);
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);

В первый раз, когда представления не создаются, он генерирует ключ кэша изображений с screenWidth x screenHeight.
И когда представления уже созданы, он генерирует новый ключ кэша изображений viewWidth x viewHeight.

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

Итак, если у нас есть сетка с 20 изображениями, мы получим:

  1. кеш на 40 изображений
  2. мерцание сетки или перезагрузка изображения при прокрутке

Его очень легко воспроизвести с помощью сэмплов, поставляемых с UIL:

  1. вызовите notifyDataSetChanged еще раз.
    или же
  2. просто прокрутите вниз, затем вверх.

Если кому-то нужно быстрое исправление, замените генератор ключей кеша статическим uri:

/* com.nostra13.universalimageloader.core.ImageLoader:194 */
String memoryCacheKey = uri; // MemoryCacheUtil.generateKey(uri, targetSize);

Самый полезный комментарий

Я решил эту «ошибку» (это определенно не так) с помощью гораздо более простого подхода - нет необходимости в специальных помощниках или решениях, просто правильно используйте тег View и шаблон ViewHolder:

    <strong i="6">@Override</strong>
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.row_layout, parent, false);
            viewHolder = new ViewHolder();

            viewHolder.title = (TextView) convertView.findViewById(R.id.row_layout_title);
            viewHolder.image = (ImageView) convertView.findViewById(R.id.row_layout_imgView);

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

        Item item = someAdapterArray.get(position);

        viewHolder.title.setText(item.getTitle());

        //lets prevent "blinking" by "saving URL tag" hack

        if (viewHolder.image.getTag() == null ||
                !viewHolder.image.getTag().equals(item.getImageUrl())) {

            //we only load image if prev. URL and current URL do not match, or tag is null

            ImageLoader.getInstance().displayImage(item.getImageUrl(), viewHolder.image, new SimpleImageLoadingListener());
            viewHolder.image.setTag(item.getImageUrl());
        }

        return convertView;
    }

Да, это много тегов, но это предотвратит перезагрузку ваших ImageViews при любых незначительных изменениях.
в состояние ListView / GridView.

PS Возьмите событие открытия / закрытия NavigationDrawer в качестве примера такого «тонкого изменения» - за ним будет следовать notifyDataSetChanged() даже если не произошло никаких изменений в базовых данных адаптера.

Все 36 Комментарий

В первый раз, когда представления не создаются, он генерирует ключ кэша изображений с screenWidth x screenHeight.
И когда представления уже созданы, он генерирует новый ключ кэша изображений viewWidth x viewHeight.

Ты прав. Первый вид еще не отрисован и его размер неизвестен. Итак, UIL принимает размеры экрана. И во второй раз представления уже имеют свои размеры, а UIL загружает в память другие растровые изображения, которые обычно меньше. Таким образом, UIL пытается сэкономить память. Не употребляйте.
На самом деле ошибки с ключами кеша нет. Но я пока не нашел решения этой «ошибки».

Спасибо за ответ.

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

В результате возникают проблемы с производительностью:

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

Кстати, можно ли настроить UIL для отображения изображения «как есть»?

Эта «ошибка» была представлена ​​1 марта здесь - https://github.com/nostra13/Android-Universal-Image-Loader/commit/50751a0 - в качестве улучшения. Это был запрос на перенос, и я его принял.
view.getWidth() и view.getHeight() (фактический размер просмотра) до этого не учитывались. Итак, учитывая эти значения, эта «ошибка» происходит чаще (на самом деле это могло случиться и раньше, но очень редко).

Чтобы предотвратить мерцание и улучшить определение размера представления, вы должны установить параметры layout_width и layout_height если это возможно. В противном случае вам следует установить maxWidth и maxHeight приблизительно.

В любом случае, если вы хотите предотвратить двойное кеширование, вы должны удалить с учетом view.getWidth() и view.getHeight() из UIL. Фактически вы можете сделать это, переопределив ImageViewAware (методы getWidth() и getHeight() ) начиная с 1.9.0.

Привет, ребята. Я решил эту ошибку с помощью обходного пути в нашем приложении (я также думаю о том, чтобы сделать запрос на перенос с этим исправлением):

мы сохраняем URL-адрес, загруженный в каждый просмотр изображения. таким образом мы избегаем запуска всего процесса, если загружаемый URL-адрес совпадает. это предотвратило проблему мерцания и помогло сэкономить несколько циклов процессора в основном потоке (каждая экономия хороша).

Думаю, эту проблему можно решить следующим образом (1.9.0):

ImageAware imageAware = new ImageViewAware(imageView, false);
imageLoader.displayImage(imageUri, imageAware);

Но пока не тестировал.

Хорошо, я изучил этот вопрос и знаю причину мерцания и некоторые способы его избежать.

Эта проблема актуальна, если ваши ImageView не имеют конкретного определенного размера ( android:layout_width или android:layout_height не устанавливаются со значением в падениях).

Причина мерцания в том, что ImageLoader по умолчанию определяет фактический размер ImageView. Когда вы показываете изображения в первый раз, ImageView еще не отрисовываются, поэтому они не имеют размера. Таким образом, ImageLoader создает растровые изображения в соответствии с размером экрана устройства. Затем происходит повторное использование представления, и ImageView уже нарисован и имеет определенный размер. Поэтому ImageLoader повторно декодирует изображение для создания растрового изображения в соответствии с этим размером. Новое растровое изображение меньше обычного, поэтому экономит память. Но создание нового растрового изображения занимает некоторое время, поэтому вы можете увидеть мерцание.

Есть 3 способа избежать этой проблемы:
1) Задайте параметры android:layout_width и android:layout_height для ImageView в виде провалов ('wrap_content' и 'match_parent' недопустимы)
2) Вызов ImageLoader после отрисовки ImageView (в imageView.post(...) :

imageView.post(new Runnable() {
        <strong i="17">@Override</strong>
        public void run() {
            imageLoader.displayImage(imageUri, imageView);
        }
    });

3) Передайте ImageViewAware (вместо ImageView ), который не учитывает фактический размер представления:
Вместо:

imageLoader.displayImage(imageUri, imageView);

сделать следующее:

ImageAware imageAware = new ImageViewAware(imageView, false)
imageLoader.displayImage(imageUri, imageAware);

Таким образом предотвращается экономия памяти, поскольку ImageLoader сохраняет растровые изображения большего размера, чем они должны быть обычно.

На данный момент это решения, но я подумаю об улучшении UIL, чтобы справиться с этим случаем.

Да, именно так и происходит.

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

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

Так что это более распространенное использование, чем вы думаете.

Я не думаю, что это используется редко. Я предложил решения.

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

Для этого есть вариант DisplayImageOptions.imageScaleType(...) .

Спасибо за ответ.

Я не думаю, что это используется редко. Я предложил решения.

Обычное :) В образце сетки есть этот баг. Список будет, если вам нужно сделать так, чтобы элемент не фиксировался по ширине или высоте.

Для этого есть опция DisplayImageOptions.imageScaleType (...).

Возможно, я ошибаюсь, но это повлияет только на растровое изображение, а не на ключи кеша. Мерцание по-прежнему будет происходить, а растровые изображения будут дублироваться. Если я хочу кэшировать исходные растровые изображения, почему ключи кеша различаются для разных представлений, какой смысл, если растровые изображения одинаковы?

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

Основная проблема в том, что глючная версия работает по умолчанию. И вам нужно проделать дополнительную работу, чтобы это исправить.
Вот почему так много приложений, использующих UIL, можно распознать с первой проверки: из-за мерцания при первой прокрутке.

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

Кто-нибудь?

Вышеупомянутое решение не работает для меня. Я применил обходной путь, поэтому решите проблему:

Я сохраняю загруженный URL-адрес в теге в просмотре изображений. Прежде чем начать загрузку с помощью загрузчика изображений, я проверяю, отображается ли уже в представлении изображения тот же URL-адрес.

    DisplayImageOptions options = new DisplayImageOptions.Builder()
            .showImageOnFail(R.drawable.avatar).displayer(new RoundedBitmapDisplayer(100))
            .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
            .cacheInMemory(true)
            .cacheOnDisc(true).bitmapConfig(Bitmap.Config.RGB_565).build();

Я нашел не очень удачный способ решить эту проблему:
cacheInMemory(true)

Ребята, проблема все еще существует.

Откройте пример приложения -> GridView, дождитесь загрузки, прокрутите вниз, прокрутите вверх.

Могу подтвердить. Проблема все еще существует. использование действия с listivew и запуск нового действия. возврат к действию со списком -> изображения мерцают.
Теперь я использую Picasso, который очень хорошо справляется с этим :)

Привет!

Я исправил эту проблему с помощью вспомогательного класса (очистить анимацию ImageView и вручную установить Bitmap из кеша памяти):

    protected static void loadImageByPath(DisplayImageOptions options, String path, ImageViewAware image, ImageLoadingListener listener) {
        ImageLoader.getInstance().cancelDisplayTask(image);
        image.getWrappedView().clearAnimation();
        image.setImageDrawable(null);

        final ImageSize size = ImageSizeUtils.defineTargetSizeForView(image, null);
        final String cacheKey = MemoryCacheUtil.generateKey(path, size);
        final List<Bitmap> cachedBitmaps = MemoryCacheUtil.findCachedBitmapsForImageUri(cacheKey, ImageLoader.getInstance().getMemoryCache());

        if (cachedBitmaps.size() > 0) {
            final Bitmap bitmap = cachedBitmaps.get(0); 

            // Yep, sometimes it is null
            if (bitmap != null) {
                if (listener != null) {
                    listener.onLoadingComplete(path, image.getWrappedView(), bitmap);
                }

                image.setImageBitmap(bitmap);

                return;
            }
        }

        ImageLoader.getInstance().displayImage(path, image, options, listener);
    }

Моя стратегия загрузки:

  1. Сбросить ImageView перед загрузкой ( resetViewBeforeLoading(true) );
  2. Если у меня есть Bitmap для элемента в кеше памяти - он будет установлен без затухания;
  3. Если у меня нет Bitmap для элемента в кеше памяти - он будет загружен и установлен в ImageView с исчезновением.
    Вот почему этот код - решение для меня. Мои DisplayImageOptions :
final DisplayImageOptions fadeDisplayOptionsForList = new DisplayImageOptions.Builder()
    .resetViewBeforeLoading(true)
    .showImageOnLoading(R.color.color_image_loading)
    .showImageForEmptyUri(R.drawable.image_catalog_error)
    .showImageOnFail(R.drawable.image_catalog_error)
    .cacheInMemory(true)
    .cacheOnDisc(true)
    .postProcessor(null)
    .imageScaleType(ImageScaleType.EXACTLY)
    .bitmapConfig(Bitmap.Config.RGB_565)
    .displayer(new FadeInBitmapDisplayer(200))
    .build();

Я использую FadeInBitmapDisplayer в моем DisplayImageOptions и думаю, что ImageLoader не очищает Animation после установки Bitmap . Когда вы вызываете Adapter.notifyDataSetChanged() , AdapterView вызывает View.requestLayout() что делает его потомков недействительным и перезапускает Animation .

Ребята !!! Для галереи с изображениями переменной высоты ....

На самом деле вы должны ПОЛУЧИТЬ ШИРИНУ И ВЫСОТУ С СЕРВЕРА. Действительно, вы должны хранить их на сервере, когда сохраняете изображения!

Затем используйте соотношение и установите абсолютный размер. ЗАТЕМ загрузите изображение с помощью великолепного AUIL.

@carlonzo Как вы решили проблему, я пробовал все упомянутое, все еще мерцает.

Это мой код отображаемого изображения.

public void displayImage(Drawable fallback, ImageView imageView,
        String imageURL) {
    ImageLoader imageLoader = ImageLoader.getInstance();
    ImageViewAware aware = new ImageViewAware(imageView, false);

    ImageLoader.getInstance().cancelDisplayTask(aware);
    aware.getWrappedView().clearAnimation();
    aware.setImageDrawable(null);

    DisplayImageOptions options = new DisplayImageOptions.Builder()
            .resetViewBeforeLoading(true).cacheOnDisk(true)
            .postProcessor(null).delayBeforeLoading(0).cacheInMemory(true)
            .showImageForEmptyUri(fallback).showImageOnFail(fallback)
            .showImageOnLoading(fallback)
            .bitmapConfig(Bitmap.Config.RGB_565)
            .displayer(new FadeInBitmapDisplayer(150))
            .imageScaleType(ImageScaleType.EXACTLY).build();
    final ImageSize size = ImageSizeUtils.defineTargetSizeForView(aware,
            new ImageSize(480, 800));
    final String cacheKey = MemoryCacheUtils.generateKey(imageURL, size);
    List<Bitmap> bitMaps = MemoryCacheUtils.findCachedBitmapsForImageUri(
            cacheKey, imageLoader.getMemoryCache());
    int length = bitMaps.size();
    if (bitMaps != null && length > 0) {

        if (bitMaps != null && length > 0 && bitMaps.get(0) != null) {
            if (lister != null) {
                lister.onLoadingComplete(imageURL, aware.getWrappedView(),
                        bitMaps.get(0));
            }
            imageView.setImageBitmap(bitMaps.get(0));
        } else {
            imageLoader.displayImage(imageURL, aware, options);

        }
    } else {
        imageLoader.displayImage(imageURL, aware, options);

    }
}

@nfirex Я пробовал тот же код, что и ваш, не работает с прокручиваемыми изображениями flickr
Пожалуйста помоги

Это исправление, чтобы остановить мерцание;)

<strong i="6">@Override</strong>
public View getView(int position, View convertView, ViewGroup parent) {

    ...

    // Set image
    String imgUrl = getItem(position);
    String tag = null;
    Object obj = holder.image.getTag();
    if (obj != null) {
        tag = obj.toString();
    }

   // Check if the same url  
    if (!TextUtils.equals(imgUrl, tag)) {
        mImageLoader.cancelDisplayTask(holder.image);
        mImageLoader.displayImage(imgUrl, holder.image, mDiOptions);
        holder.image.setTag(imgUrl);
    }

    ...

}

Похоже, эта проблема все еще существует с последней версией Android Lollipop. Я использую UIL v1.9.3, и мерцание изображения происходит только на устройствах Lollipop.

Я решил эту «ошибку» (это определенно не так) с помощью гораздо более простого подхода - нет необходимости в специальных помощниках или решениях, просто правильно используйте тег View и шаблон ViewHolder:

    <strong i="6">@Override</strong>
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.row_layout, parent, false);
            viewHolder = new ViewHolder();

            viewHolder.title = (TextView) convertView.findViewById(R.id.row_layout_title);
            viewHolder.image = (ImageView) convertView.findViewById(R.id.row_layout_imgView);

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

        Item item = someAdapterArray.get(position);

        viewHolder.title.setText(item.getTitle());

        //lets prevent "blinking" by "saving URL tag" hack

        if (viewHolder.image.getTag() == null ||
                !viewHolder.image.getTag().equals(item.getImageUrl())) {

            //we only load image if prev. URL and current URL do not match, or tag is null

            ImageLoader.getInstance().displayImage(item.getImageUrl(), viewHolder.image, new SimpleImageLoadingListener());
            viewHolder.image.setTag(item.getImageUrl());
        }

        return convertView;
    }

Да, это много тегов, но это предотвратит перезагрузку ваших ImageViews при любых незначительных изменениях.
в состояние ListView / GridView.

PS Возьмите событие открытия / закрытия NavigationDrawer в качестве примера такого «тонкого изменения» - за ним будет следовать notifyDataSetChanged() даже если не произошло никаких изменений в базовых данных адаптера.

Спасибо ...

ImageAware imageAware = новый ImageViewAware (imageView, ложь)
imageLoader.displayImage (imageUri, imageAware);

Спасибо за работу для меня

ребята. Эта библиотека была отличной. но теперь есть более развитые библиотеки. пожалуйста, проверьте Picasso, Glide или Fresco.

Есть ли исправление или решение? Я пробовал предыдущие решения, такие как установка тега ImageView.
Та же проблема возникает при использовании RecyclerView & notifidatasetchanged ();
@ nostra13

РЕДАКТИРОВАТЬ:
Это моя вина, вместо этого я должен был позвонить в notificationdatainsert, тогда все заработало!

спасибо @ousenko

спасибо @ousenko отлично работает)

в recyclerView попробуйте использовать: notifyItemChanged (position);
в моем коде работает.

ИЛИ ЖЕ:

попробуйте получить растровое изображение по методу:

частный статический Bitmap getCachedBitmap (String url) {
Списокb = MemoryCacheUtils.findCachedBitmapsForImageUri (url, ImageLoader.getInstance (). getMemoryCache ());
если (b! = null && b.size ()> 0)
возврат b.get (0);
return null;
}

затем в коде:

Растровое изображение cachedThumbnail = getCachedBitmap (url);
if (cachedThumbnail! = null) {
image.setImageBitmap (cachedThumbnail);
возвращаться;
}
ImageLoader.getInstance (). DisplayImage (...);

Привет, друг,
Эта проблема исправлена?

Привет @SagarPanwala, я использую v1.9.5 "ошибка" все еще существует, вы можете использовать упомянутый обходной путь ousenko

спасибо @ousenko , это отличный ответ, может я

Может ли кто-нибудь сказать мне, как использовать это в ViewPager, который используется как элемент ListView? У меня проблемы с этим.

То же самое для меня.

и я думаю, что важно проверить конфигурацию для параметров инициализации.

Спасибо за ваше решение. лол :)) @ousenko

Спасибо @ousenko

Была ли эта страница полезной?
0 / 5 - 0 рейтинги