¿Cuándo debo reciclar un mapa de bits usando LRUCache?

Estoy utilizando un LRUCache para almacenar mapas de bits en caché que se almacenan en el sistema de archivos. Hice la caché basándome en los ejemplos aquí: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

El problema es que veo que OutOfMemory se bloquea con frecuencia mientras se usa la aplicación. Creo que cuando el LRUCache desaloja una imagen para dejar espacio a otra, la memoria no está siendo liberada.

He añadido una llamada a Bitmap.recycle () cuando una imagen es desalojada:

  // use 1/8 of the available memory for this memory cache final int cacheSize = 1024 * 1024 * memClass / 8; mImageCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) { oldBitmap.recycle(); oldBitmap = null; } }; 

Esto soluciona los fallos, sin embargo, también resulta en imágenes que a veces no aparecen en la aplicación (sólo un espacio negro donde debería estar la imagen). Cada vez que ocurre veo este mensaje en mi Logcat: Cannot generate texture from bitmap .

Una búsqueda rápida de google revela que esto está sucediendo porque la imagen que está mostrando se ha reciclado.

Entonces, ¿Que esta pasando aquí? ¿Por qué las imágenes recicladas están todavía en el LRUCache si solo las reciclo después de haberlas eliminado? ¿Cuál es la alternativa para implementar un caché? Los documentos de Android indican claramente que LRUCache es el camino a seguir, pero no mencionan la necesidad de reciclar mapas de bits o cómo hacerlo.

RESOLUCIÓN: En caso de que sea útil para cualquier otra persona, la solución a este problema sugerida por la respuesta aceptada es NO hacer lo que hice en el ejemplo de código anterior (no recicle los mapas de bits en la llamada entryRemoved() ).

En su lugar, cuando haya terminado con un ImageView (como onPause() en una actividad, o cuando una vista se recicla en un adaptador) compruebe si el mapa de bits sigue en la caché (he añadido un método isImageInCache() a mi caché Clase) y, si no es así, recicle el mapa de bits. De lo contrario, déjalo en paz. Esto solucionó mis excepciones de OutOfMemory y evitó los mapas de bits de reciclaje que todavía se estaban utilizando.

Creo que cuando el LRUCache desaloja una imagen para dejar espacio a otra, la memoria no está siendo liberada.

No lo será, hasta que el Bitmap se recicle o recolección de basura.

Una búsqueda rápida de google revela que esto está sucediendo porque la imagen que está mostrando se ha reciclado.

Por eso no deberías reciclar allí.

¿Por qué las imágenes recicladas están todavía en el LRUCache si solo las reciclo después de haberlas eliminado?

Presumiblemente, no están en el LRUCache . Están en un ImageView o algo más que sigue utilizando el Bitmap .

¿Cuál es la alternativa para implementar un caché?

Por razones de argumento, supongamos que está utilizando los objetos Bitmap en los widgets ImageView , como en filas de un ListView .

Cuando haya terminado con un Bitmap (por ejemplo, la fila en un ListView se recicla), comprueba si aún está en la caché. Si lo es, lo dejas solo. Si no lo es, lo recycle() .

El caché es simplemente hacerle saber qué objetos de Bitmap vale la pena aferrarse. La caché no tiene forma de saber si el Bitmap se sigue utilizando en algún lugar.

BTW, si está en el nivel de API 11+, considere el uso de inBitmap . OutOMemoryErrors se activan cuando no se puede cumplir una asignación. Lo último que he comprobado, Android no tiene un recolector de recopilación de compactación, por lo que puede obtener un OutOfMemoryError debido a la fragmentación (desea asignar algo más grande que el mayor bloque disponible).

Frente a la misma y gracias a @ CommonsWare para la discusión. Publicar la solución completa aquí, por lo que ayuda a más personas que vienen aquí por el mismo problema. Se agradecen las ediciones y los comentarios. Aclamaciones

  When should I recycle a bitmap using LRUCache? 
  • Precisamente cuando su mapa de bits no está en la memoria caché y ni obtener referencia de cualquier ImageView.

  • Para mantener el recuento de referencia de mapa de bits tenemos que extender la clase BitmapDrawable y agregar atributos de referencia a ellos.

  • Esta muestra de Android tiene la respuesta a ella exactamente. MostrandoBitmaps.zip

Vamos a llegar al detalle y el código a continuación.

 (don't recycle the bitmaps in the entryRemoved() call). 

No exactamente.

  • En entryRemoved delegate compruebe si aún se hace referencia a Bitmap desde cualquier ImageView. Si no. Recíclela allí mismo.

  • Y viceversa, que se menciona en la respuesta aceptada que cuando la vista está a punto de ser reutilizado o obtener dumping comprobar su mapa de bits (bitmap anterior si la vista se está reutilizando) está en la caché. Si está ahí, déjalo solo, recíclela.

  • La clave aquí es que tenemos que hacer un cheque en ambos lugares si podemos reciclar bitmap o no.

Voy a explicar mi caso específico en el que estoy usando LruCache para mantener los mapas de bits para mí. Y mostrarlos en ListView. Y llamar a reciclar en mapas de bits cuando ya no están en uso.

RecyclingBitmapDrawable.java y RecyclingImageView.java de la muestra mencionada anteriormente son las piezas fundamentales que necesitamos aquí. Están manejando las cosas maravillosamente. Sus métodos setIsCached y setIsDisplayed están haciendo lo que necesitamos.

El código se puede encontrar en el enlace de muestra mencionado anteriormente. Pero también publicar el código completo de archivo en la parte inferior de la respuesta en caso de que en el futuro el enlace va hacia abajo o cambiado. Hizo una pequeña modificación de sobreescribir setImageResource también para comprobar el estado del mapa de bits anterior.

— Aquí va el código para usted —

Por lo tanto, su administrador de LruCache debe ser algo como esto.

LruCacheManager.java

 package com.example.cache; import android.os.Build; import android.support.v4.util.LruCache; public class LruCacheManager { private LruCache<String, RecyclingBitmapDrawable> mMemoryCache; private static LruCacheManager instance; public static LruCacheManager getInstance() { if(instance == null) { instance = new LruCacheManager(); instance.init(); } return instance; } private void init() { // We are declaring a cache of 6Mb for our use. // You need to calculate this on the basis of your need mMemoryCache = new LruCache<String, RecyclingBitmapDrawable>(6 * 1024 * 1024) { @Override protected int sizeOf(String key, RecyclingBitmapDrawable bitmapDrawable) { // The cache size will be measured in kilobytes rather than // number of items. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { return bitmapDrawable.getBitmap().getByteCount() ; } else { return bitmapDrawable.getBitmap().getRowBytes() * bitmapDrawable.getBitmap().getHeight(); } } @Override protected void entryRemoved(boolean evicted, String key, RecyclingBitmapDrawable oldValue, RecyclingBitmapDrawable newValue) { super.entryRemoved(evicted, key, oldValue, newValue); oldValue.setIsCached(false); } }; } public void addBitmapToMemoryCache(String key, RecyclingBitmapDrawable bitmapDrawable) { if (getBitmapFromMemCache(key) == null) { // The removed entry is a recycling drawable, so notify it // that it has been added into the memory cache bitmapDrawable.setIsCached(true); mMemoryCache.put(key, bitmapDrawable); } } public RecyclingBitmapDrawable getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } public void clear() { mMemoryCache.evictAll(); } } 

Y su getView () de ListView / GridView adaptador debe verse normal como de costumbre. Como cuando está configurando una nueva imagen en ImageView utilizando el método setImageDrawable. Su internamente comprobar el recuento de referencia en mapa de bits anterior y llamará reciclar en él internamente si no en lrucache.

 @Override public View getView(int position, View convertView, ViewGroup parent) { RecyclingImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new RecyclingImageView(getActivity()); imageView.setLayoutParams(new GridView.LayoutParams( GridView.LayoutParams.WRAP_CONTENT, GridView.LayoutParams.WRAP_CONTENT)); imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); imageView.setPadding(5, 5, 5, 5); } else { imageView = (RecyclingImageView) convertView; } MyDataObject dataItem = (MyDataObject) getItem(position); RecyclingBitmapDrawable image = lruCacheManager.getBitmapFromMemCache(dataItem.getId()); if(image != null) { // This internally is checking reference count on previous bitmap it used. imageView.setImageDrawable(image); } else { // You have to implement this method as per your code structure. // But it basically doing is preparing bitmap in the background // and adding that to LruCache. // Also it is setting the empty view till bitmap gets loaded. // once loaded it just need to call notifyDataSetChanged of adapter. loadImage(dataItem.getId(), R.drawable.empty_view); } return imageView; } 

Aquí está su RecyclingImageView.java

 /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.cache; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.widget.ImageView; /** * Sub-class of ImageView which automatically notifies the drawable when it is * being displayed. */ public class RecyclingImageView extends ImageView { public RecyclingImageView(Context context) { super(context); } public RecyclingImageView(Context context, AttributeSet attrs) { super(context, attrs); } /** * @see android.widget.ImageView#onDetachedFromWindow() */ @Override protected void onDetachedFromWindow() { // This has been detached from Window, so clear the drawable setImageDrawable(null); super.onDetachedFromWindow(); } /** * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable) */ @Override public void setImageDrawable(Drawable drawable) { // Keep hold of previous Drawable final Drawable previousDrawable = getDrawable(); // Call super to set new Drawable super.setImageDrawable(drawable); // Notify new Drawable that it is being displayed notifyDrawable(drawable, true); // Notify old Drawable so it is no longer being displayed notifyDrawable(previousDrawable, false); } /** * @see android.widget.ImageView#setImageResource(android.graphics.drawable.Drawable) */ @Override public void setImageResource(int resId) { // Keep hold of previous Drawable final Drawable previousDrawable = getDrawable(); // Call super to set new Drawable super.setImageResource(resId); // Notify old Drawable so it is no longer being displayed notifyDrawable(previousDrawable, false); } /** * Notifies the drawable that it's displayed state has changed. * * @param drawable * @param isDisplayed */ private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) { if (drawable instanceof RecyclingBitmapDrawable) { // The drawable is a CountingBitmapDrawable, so notify it ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed); } else if (drawable instanceof LayerDrawable) { // The drawable is a LayerDrawable, so recurse on each layer LayerDrawable layerDrawable = (LayerDrawable) drawable; for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) { notifyDrawable(layerDrawable.getDrawable(i), isDisplayed); } } } } 

Aquí tienes tu RecyclingBitmapDrawable.java

 /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.cache; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.util.Log; /** * A BitmapDrawable that keeps track of whether it is being displayed or cached. * When the drawable is no longer being displayed or cached, * {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap. */ public class RecyclingBitmapDrawable extends BitmapDrawable { static final String TAG = "CountingBitmapDrawable"; private int mCacheRefCount = 0; private int mDisplayRefCount = 0; private boolean mHasBeenDisplayed; public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) { super(res, bitmap); } /** * Notify the drawable that the displayed state has changed. Internally a * count is kept so that the drawable knows when it is no longer being * displayed. * * @param isDisplayed - Whether the drawable is being displayed or not */ public void setIsDisplayed(boolean isDisplayed) { //BEGIN_INCLUDE(set_is_displayed) synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called checkState(); //END_INCLUDE(set_is_displayed) } /** * Notify the drawable that the cache state has changed. Internally a count * is kept so that the drawable knows when it is no longer being cached. * * @param isCached - Whether the drawable is being cached or not */ public void setIsCached(boolean isCached) { //BEGIN_INCLUDE(set_is_cached) synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called checkState(); //END_INCLUDE(set_is_cached) } private synchronized void checkState() { //BEGIN_INCLUDE(check_state) // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { Log.d(TAG, "No longer being used or cached so recycling. " + toString()); getBitmap().recycle(); } //END_INCLUDE(check_state) } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); } } 
  • Cómo solucionar problemas de java.lang.OutOfMemoryError en Android
  • Obtener Bitmap de ImageView cargado con Picasso
  • Mostrar imágenes desde la carpeta Desplegable con Universal Image Loader
  • ¿Cómo puedo rotar un mapa de bits en Android?
  • Cómo obtener todo el mapa de bits adjunto a un ImageView?
  • Cargar imagen de url en la notificación de Android
  • Error de memoria al cargar mapas de bits
  • ¿Es necesario llamar a Bitmap.recycle () después de usarlo (en Android)?
  • Fuera de memoria en una asignación 9830416-byte con mapa de bits
  • Crear dibujable desde mapa de bits
  • ¿Cómo hago que mi ImageView tenga un tamaño fijo independientemente del tamaño del mapa de bits
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.