Android-universal-image-loader: Imagen parpadeando en notifyDataSetChanged ()

Creado en 20 ago. 2013  ·  36Comentarios  ·  Fuente: nostra13/Android-Universal-Image-Loader

Problemas causados ​​por este error:

  1. parpadeo de la imagen
  2. si carga la cuadrícula de imágenes, desplácese hacia abajo y luego hacia arriba, las imágenes se vuelven a cargar
  3. el primer lote de imágenes consume el doble de memoria.

Parece que hay un error aquí:

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

La primera vez que no se crean vistas, genera una clave de caché de imagen con screenWidth x screenHeight.
Y cuando las vistas ya están creadas, genera una nueva clave de caché de imagen viewWidth x viewHeight.

Dado que las claves son diferentes en ambas ocasiones, las imágenes se consideran diferentes, por lo que carga la misma imagen dos veces en la memoria.

Entonces, si tenemos una cuadrícula con 20 imágenes, obtendremos:

  1. caché con 40 imágenes
  2. parpadeo de la cuadrícula o recarga de la imagen al desplazarse

Se puede reproducir muy fácilmente con muestras que vienen con UIL:

  1. llame a notifyDataSetChanged una vez más.
    o
  2. simplemente desplácese hacia abajo y luego hacia arriba.

Si alguien quiere una solución rápida, reemplace el generador de claves de caché con uri estático:

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

Comentario más útil

Resolví este "error" (definitivamente no lo es) con un enfoque mucho más simple, sin necesidad de ayudantes o soluciones personalizados, solo use la etiqueta de View y el patrón de ViewHolder correctamente:

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

Sí, son muchas etiquetas, pero esto evitará que sus ImageViews se vuelvan a cargar ante cualquier cambio sutil
a un estado ListView / GridView.

PS Tome el evento de apertura / cierre de NavigationDrawer como un ejemplo de un "cambio sutil" - esto será seguido por notifyDataSetChanged() incluso si no se produjo ningún cambio en los datos subyacentes del adaptador.

Todos 36 comentarios

La primera vez que no se crean vistas, genera una clave de caché de imagen con screenWidth x screenHeight.
Y cuando las vistas ya están creadas, genera una nueva clave de caché de imagen viewWidth x viewHeight.

Estás bien. La vista por primera vez aún no está dibujada y se desconoce su tamaño. Entonces, UIL toma las dimensiones de la pantalla. Y las vistas por segunda vez ya tienen sus tamaños y cargas UIL en la memoria, otros mapas de bits que son más pequeños en común. Entonces, UIL intenta ahorrar memoria con eso. No consumir.
En realidad, no hay ningún error con las claves de caché. Pero aún no he encontrado una solución para este "error".

Gracias por la respuesta.

El error es que almacena en caché la misma imagen dos veces, debido a una clave incorrecta, y esto no debería suceder.
Creo que es un error bastante grave, que existe desde hace más de un año.

Como resultado, tenemos problemas de rendimiento:

  • más memoria usada
  • trabajo adicional para eliminar las imágenes no utilizadas
  • tartamudeando en el desplazamiento
  • etc ...

Por cierto, ¿es posible configurar UIL para mostrar la imagen "tal cual"?

Este "error" se introdujo el 1 de marzo aquí - https://github.com/nostra13/Android-Universal-Image-Loader/commit/50751a0 - como mejora. Esta fue una solicitud de extracción y la acepté.
view.getWidth() y view.getHeight() (tamaño de vista real) no se consideraron antes de eso. Entonces, teniendo en cuenta estos valores, este "error" ocurre con más frecuencia (en realidad, también podría ocurrir antes, pero muy raramente).

Para evitar el parpadeo y mejorar la definición del tamaño de la vista, debe configurar layout_width parámetros layout_height si es posible. De lo contrario, debe configurar maxWidth y maxHeight , aproximadamente.

De todos modos, si desea evitar el almacenamiento en caché dos veces, debe eliminarlo considerando view.getWidth() y view.getHeight() de UIL. En realidad, puede hacerlo anulando ImageViewAware (métodos getWidth() y getHeight() ) desde 1.9.0.

Hola tios. Resolví este error con una solución alternativa en nuestra aplicación (también estoy pensando en hacer una solicitud de extracción con esta solución):

guardamos la URL cargada en cada vista de imagen. De esta forma evitamos iniciar todo el proceso si la url a cargar es la misma. esto evitó el problema del parpadeo y ayudó a ahorrar algunos ciclos de CPU en el hilo principal (cada ahorro es bueno).

Creo que este problema se puede resolver de la siguiente manera (1.9.0):

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

Pero todavía no lo probé.

Ok, investigué esta pregunta y sé el motivo del parpadeo y algunas soluciones para evitarlo.

Este problema es real si sus ImageViews no tienen un tamaño definido concreto ( android:layout_width o android:layout_height no se establece con valor en dips).

La razón del parpadeo es que ImageLoader define el tamaño real de ImageView de forma predeterminada. Cuando muestra imágenes por primera vez, los ImageViews aún no están dibujados, por lo que no tienen tamaño. Entonces, ImageLoader crea mapas de bits según el tamaño de la pantalla del dispositivo. Luego ocurre la reutilización de la vista e ImageView ya se dibujó y tiene un tamaño definido. Entonces ImageLoader vuelve a decodificar la imagen para crear un mapa de bits de acuerdo con este tamaño. El nuevo mapa de bits es más pequeño que el anterior en común, por lo que ahorra memoria. Pero la creación de un nuevo mapa de bits lleva algo de tiempo para que pueda ver el parpadeo.

Hay 3 formas de evitar este problema:
1) Establezca los parámetros android:layout_width y android:layout_height para ImageViews en dips ('wrap_content' y 'match_parent' no son aceptables)
2) Llame a ImageLoader después de que se dibujó ImageView (en imageView.post(...) :

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

3) Pase ImageViewAware (en lugar de ImageView ) que no considera el tamaño de vista real:
En lugar de:

imageLoader.displayImage(imageUri, imageView);

haz lo siguiente:

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

De esta forma evita la economía de memoria porque ImageLoader mantiene los mapas de bits más grandes de lo que deberían ser en común.

Estas son soluciones por ahora, pero pensaré en mejorar UIL para manejar este caso.

Sí, es exactamente lo que pasa.

ImageView usado en cuadrículas o listas casi nunca tiene un tamaño específico.
Entonces es un uso más común de lo que cree.
También puede ver este error en la aplicación de muestras UIL.

¿Qué pasa si no me importan los tamaños y quiero mantener las imágenes como están, por qué se fuerza el cambio de tamaño y las claves contienen el tamaño de la imagen en las claves de caché? ¿No debería cambiar el tamaño como parte de la configuración o algo así?

Entonces es un uso más común de lo que cree.

No creo que se use con poca frecuencia. Propuse soluciones.

¿Qué pasa si no me importan los tamaños y quiero mantener las imágenes como están, por qué se fuerza el cambio de tamaño y las claves contienen el tamaño de la imagen en las claves de caché? ¿No debería cambiar el tamaño como parte de la configuración o algo así?

Existe la opción DisplayImageOptions.imageScaleType(...) para eso.

Gracias por responder.

No creo que se use con poca frecuencia. Propuse soluciones.

Común :) La muestra de la cuadrícula tiene este error. Lista, si necesita hacer que el elemento no se fije por ancho o alto.

Existe la opción DisplayImageOptions.imageScaleType (...) para eso.

Tal vez me equivoque, pero esto afectará solo al mapa de bits y no a las claves de caché. El parpadeo seguirá ocurriendo y los mapas de bits se duplicarán. Si quiero almacenar en caché mapas de bits originales, ¿por qué las claves de caché son diferentes para diferentes vistas, qué sentido tiene si el mapa de bits es el mismo?

Otro caso de uso:
tenemos 2 vistas que son quizás unos pocos píxeles diferentes, por lo que ahora creará 2 mapas de bits y consumirá el doble de memoria de lo que realmente necesita.

El principal problema es que la versión con errores funciona por defecto. Y necesita hacer un trabajo adicional para solucionarlo.
Es por eso que tantas aplicaciones que usan UIL pueden reconocerse desde la primera verificación: debido al parpadeo en el primer desplazamiento.

También me he enfrentado al mismo problema, por favor ayúdenme. La solución anterior no funciona para mí.

¿Alguien?

La solución anterior no me funciona. Apliqué una solución alternativa para resolver el problema:

Guardo la URL cargada en una etiqueta en la vista de imagen. antes de comenzar la carga con el cargador de imágenes, verifico si la vista de imagen ya muestra la misma 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();

Encontré una manera no muy buena de resolver este problema:
cacheInMemory(true)

Chicos, el problema todavía existe.

Abra su aplicación de muestra -> GridView, espere a que se cargue, desplácese hacia abajo, desplácese hacia arriba.

Puede confirmar. El problema sigue ahí. usando una actividad con listivew y comenzando una nueva actividad. volviendo a la actividad con la vista de lista -> Las imágenes parpadean.
Ahora estoy usando Picasso, que está manejando esto muy bien :)

¡Hola!

Solucioné este problema con helper-class (animación clara de ImageView y configurando Bitmap desde la memoria caché manualmente):

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

Mi estrategia de carga es:

  1. Restablecer ImageView antes de cargar ( resetViewBeforeLoading(true) );
  2. Si tengo Bitmap para el elemento en la memoria caché, se configurará sin desvanecimiento;
  3. Si no tengo Bitmap para el elemento en la memoria caché, se descargará y configurará en ImageView con fade.
    Es por eso que ese código es la solución para mí. Mi 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();

Utilizo FadeInBitmapDisplayer en mi DisplayImageOptions y creo que ImageLoader no borran Animation después de configurar Bitmap . Cuando llama Adapter.notifyDataSetChanged() , AdapterView llama View.requestLayout() eso invalida a sus hijos y reinicia Animation .

Tipo !!! Para una galería, con imágenes de altura variable ...

Realmente deberías OBTENER EL ANCHO Y ALTO DEL SERVIDOR. De hecho, ¡tienes que almacenarlos en el servidor cuando guardas imágenes!

Luego use la proporción y establezca el tamaño absoluto. ENTONCES cargue la imagen usando el magnífico AUIL.

@carlonzo Cómo resolvió el problema que probé, todo lo mencionado sigue parpadeando.

Este es mi código de imagen de visualización.

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 probé el mismo código que el tuyo no funciona en imágenes de desplazamiento flickr
Por favor ayuda

Es una solución para detener el parpadeo;)

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

    ...

}

Parece que este problema todavía existe con la última versión de Android Lollipop. Estoy usando UIL v1.9.3 y el parpadeo de la imagen ocurre solo en dispositivos Lollipop

Resolví este "error" (definitivamente no lo es) con un enfoque mucho más simple, sin necesidad de ayudantes o soluciones personalizados, solo use la etiqueta de View y el patrón de ViewHolder correctamente:

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

Sí, son muchas etiquetas, pero esto evitará que sus ImageViews se vuelvan a cargar ante cualquier cambio sutil
a un estado ListView / GridView.

PS Tome el evento de apertura / cierre de NavigationDrawer como un ejemplo de un "cambio sutil" - esto será seguido por notifyDataSetChanged() incluso si no se produjo ningún cambio en los datos subyacentes del adaptador.

gracias ...

ImageAware imageAware = nuevo ImageViewAware (imageView, falso)
imageLoader.displayImage (imageUri, imageAware);

Gracias su trabajo para mi

tipo. Esta biblioteca ha sido genial. pero ahora hay bibliotecas más evolucionadas. por favor revise Picasso, Glide o Fresco.

¿Existe alguna solución o solución? He probado las soluciones anteriores, como establecer ImageView en una etiqueta.
Existe el mismo problema al usar RecyclerView & notifidatasetchanged ();
@ nostra13

EDITAR:
Es mi culpa, debería llamar a notifieddatainsert en su lugar, ¡luego funcionó bien!

gracias @ousenko

gracias @ousenko funciona muy bien)

en RecyclerView intente usar: notifyItemChanged (posición);
en mi código funciona.

O:

intente obtener mapa de bits por método:

mapa de bits estático privado getCachedBitmap (URL de cadena) {
Listab = MemoryCacheUtils.findCachedBitmapsForImageUri (url, ImageLoader.getInstance (). getMemoryCache ());
if (b! = null && b.size ()> 0)
return b.get (0);
devolver nulo;
}

luego en código:

Mapa de bits cachedThumbnail = getCachedBitmap (url);
if (cachedThumbnail! = null) {
image.setImageBitmap (cachedThumbnail);
regreso;
}
ImageLoader.getInstance (). DisplayImage (...);

Hola amigo,
¿Se solucionó este problema?

Hola @SagarPanwala, uso v1.9.5 "error" todavía existe, puedes usar la solución alternativa mencionada por Ousenko

gracias @ousenko es una gran respuesta, tal vez me cambie a Fresco.

¿Alguien puede decirme cómo usar esto en ViewPager que se usa como un elemento ListView? Estoy enfrentando problemas con eso.

Lo mismo para mi.

y creo que es importante verificar la configuración de las opciones de inicio.

Gracias por tu solucion. jajaja :)) @ousenko

Gracias @ousenko

¿Fue útil esta página
0 / 5 - 0 calificaciones