Bitmap Cache (SoftReference, Hard) en Lazy List no parece funcionar correctamente – Android

He leído varios temas sobre la carga de la lista perezosa en stackoverflow y estoy tratando de entender cómo trabajar en los diferentes niveles de caché en android.

Como se mencionó aquí:

Carga perezosa de imágenes en ListView

Usé el

Multithreading For Performance , un tutorial de Gilles Debunne.

ejemplo. Lo modifiqué con el fin de trabajar con la forma correcta y también trabajar con 1.6. Aquí está el código:

/* * Copyright (C) 2010 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. */ public class ImageDownloader { private static final String TAG = "ImageDownloader"; private static final int HARD_CACHE_CAPACITY = 40; private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2); /* Download the specified image from the Internet and binds it to the provided ImageView. The * binding is immediate if the image is found in the cache and will be done asynchronously * otherwise. A null bitmap will be associated to the ImageView if an error occurs. * * @param url The URL of the image to download. * @param imageView The ImageView to bind the downloaded image to. */ public void download(String url, ImageView imageView) { Log.d(TAG, "download(String url, ImageView imageView)"); resetPurgeTimer(); Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { Log.d(TAG, "(bitmap == null)"); forceDownload(url, imageView); } else { Log.d(TAG, "(bitmap != null) "); cancelPotentialDownload(url, imageView); imageView.setImageBitmap(bitmap); } } /* * Same as download but the image is always downloaded and the cache is not used. * Kept private at the moment as its interest is not clear. private void forceDownload(String url, ImageView view) { forceDownload(url, view, null); } */ /** * Same as download but the image is always downloaded and the cache is not used. * Kept private at the moment as its interest is not clear. */ private void forceDownload(String url, ImageView imageView) { Log.d(TAG, "forceDownload(String url, ImageView imageView)"); // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys. if (url == null) { Log.d(TAG, "(url == null)"); imageView.setImageDrawable(null); return; } Bitmap bitmap = null; BitmapDownloaderTask task = null; if (cancelPotentialDownload(url, imageView)) { Log.d(TAG, "(cancelPotentialDownload(url, imageView))"); task = new BitmapDownloaderTask(imageView); DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); imageView.setImageDrawable(downloadedDrawable); imageView.setMinimumHeight(156); task.execute(url); } } /** * Returns true if the current download has been canceled or if there was no download in * progress on this image view. * Returns false if the download in progress deals with the same url. The download is not * stopped in that case. */ private static boolean cancelPotentialDownload(String url, ImageView imageView) { Log.d(TAG, "---cancelPotentialDownload(String url, ImageView imageView)----)"); BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); if (bitmapDownloaderTask != null) { Log.d(TAG, "(bitmapDownloaderTask != null)"); String bitmapUrl = bitmapDownloaderTask.url; if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { Log.d(TAG, "(((bitmapUrl == null) || (!bitmapUrl.equals(url)))"); bitmapDownloaderTask.cancel(true); } else { Log.d(TAG, "The same URL is already being downloaded."); // The same URL is already being downloaded. return false; } } return true; } /** * @param imageView Any imageView * @return Retrieve the currently active download task (if any) associated with this imageView. * null if there is no such task. */ private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { Log.d(TAG, "getBitmapDownloaderTask(ImageView imageView)"); if (imageView != null) { Log.d(TAG, "(imageView != null) )"); Drawable drawable = imageView.getDrawable(); if (drawable instanceof DownloadedDrawable) { Log.d(TAG, "(drawable instanceof DownloadedDrawable) "); DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; return downloadedDrawable.getBitmapDownloaderTask(); } } return null; } Bitmap downloadBitmap(String stringUrl) { Log.d(TAG, "(downloadBitmap(String stringUrl)"); URL url = null; HttpURLConnection connection = null; InputStream inputStream = null; try { url = new URL(stringUrl); connection = (HttpURLConnection) url.openConnection(); connection.setUseCaches(true); inputStream = connection.getInputStream(); return BitmapFactory.decodeStream(new FlushedInputStream(inputStream)); /* BitmapFactory.Options bfOptions=new BitmapFactory.Options(); bfOptions.inDither=false; //Disable Dithering mode bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future bfOptions.inTempStorage=new byte[32 * 1024]; Bitmap b=BitmapFactory.decodeStream(new FlushedInputStream(inputStream), null,bfOptions ); return b; */ } catch (IOException e) { //getRequest.abort(); Log.w(TAG, "I/O error while retrieving bitmap from " + url, e); } catch (IllegalStateException e) { // getRequest.abort(); Log.w(TAG, "Incorrect URL: " + url); } catch (Exception e) { //getRequest.abort(); Log.w(TAG, "Error while retrieving bitmap from " + url, e); } finally { if (connection != null) { connection.disconnect(); } } return null; } /* * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. */ static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int b = read(); if (b < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } } /** * The actual AsyncTask that will asynchronously download the image. */ class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private String url; private final WeakReference<ImageView> imageViewReference; public BitmapDownloaderTask(ImageView imageView) { Log.w(TAG, "BitmapDownloaderTask(ImageView imageView)"); imageViewReference = new WeakReference<ImageView>(imageView); } /** * Actual download method. */ @Override protected Bitmap doInBackground(String... params) { Log.w(TAG, "doInBackground"); url = params[0]; return downloadBitmap(url); } /** * Once the image is downloaded, associates it to the imageView */ @Override protected void onPostExecute(Bitmap bitmap) { Log.w(TAG, "onPostExecute"); if (isCancelled()) { bitmap = null; } addBitmapToCache(url, bitmap); if (imageViewReference != null) { Log.w(TAG, "(imageViewReference != null)"); ImageView imageView = imageViewReference.get(); BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); // Change bitmap only if this process is still associated with it // Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode) if ((this == bitmapDownloaderTask)) { Log.w(TAG, " if ((this == bitmapDownloaderTask)"); imageView.setImageBitmap(bitmap); } } } } /** * A fake Drawable that will be attached to the imageView while the download is in progress. * * <p>Contains a reference to the actual download task, so that a download task can be stopped * if a new binding is required, and makes sure that only the last started download process can * bind its result, independently of the download finish order.</p> */ static class DownloadedDrawable extends ColorDrawable { private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { super(Color.BLACK); Log.w(TAG, "DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) "); bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); } public BitmapDownloaderTask getBitmapDownloaderTask() { Log.w(TAG, "BitmapDownloaderTask getBitmapDownloaderTask() "); return bitmapDownloaderTaskReference.get(); } } /* public void setMode(Mode mode) { this.mode = mode; clearCache(); } */ /* * Cache-related fields and methods. * * We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the * Garbage Collector. */ // Hard cache, with a fixed maximum capacity and a life duration private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) // private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>() { // private static final long serialVersionUID = -695136752926135717L; @Override protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) { Log.w(TAG, "removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) "); Log.w(TAG, "size() : " + size()); Log.w(TAG, "HARD_CACHE_CAPACITY : " + HARD_CACHE_CAPACITY); if (size() > HARD_CACHE_CAPACITY) { Log.w(TAG, "(size() > HARD_CACHE_CAPACITY) "); Log.e(TAG, "sSoftBitmapCache --> sSoftBitmapCache.put "); // Entries push-out of hard reference cache are transferred to soft reference cache sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); return true; } else return false; } }; //HARD_CACHE_CAPACITY / 2 // Soft cache for bitmaps kicked out of hard cache // private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(); private final Handler purgeHandler = new Handler(); private final Runnable purger = new Runnable() { public void run() { Log.e(TAG, "purger run"); // clearCache(); } }; /** * Adds this bitmap to the cache. * @param bitmap The newly downloaded bitmap. */ private void addBitmapToCache(String url, Bitmap bitmap) { Log.w(TAG, "--------addBitmapToCache-----------"); if (bitmap != null) { Log.w(TAG, "(bitmap != null) "); synchronized (sHardBitmapCache) { //final Bitmap tryData = sHardBitmapCache.get(url); // if (tryData != null) { // sHardBitmapCache.remove(url); // } Log.w(TAG, " sHardBitmapCache.put(url, bitmap);"); sHardBitmapCache.put(url, bitmap); } } } /** * @param url The URL of the image that will be retrieved from the cache. * @return The cached bitmap or null if it was not found. */ private Bitmap getBitmapFromCache(String url) { Log.e(TAG, " --------getBitmapFromCache(String url)----------------"); // First try the hard reference cache synchronized (sHardBitmapCache) { final Bitmap bitmap = sHardBitmapCache.get(url); if (bitmap != null) { Log.e(TAG, " getBitmapFromCache ---> Bitmap found in Hard cache!!!!!"); // Bitmap found in hard cache // Move element to first position, so that it is removed last sHardBitmapCache.remove(url); sHardBitmapCache.put(url, bitmap); return bitmap; }else{ Log.e(TAG, " getBitmapFromCache ---> Bitmap not found in Hard cache"); } } // Then try the soft reference cache SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url); if (bitmapReference != null) { Log.e(TAG, " getBitmapFromCache ---> bitmapReference found in SoftReference cache!"); final Bitmap bitmap = bitmapReference.get(); if (bitmap != null) { Log.e(TAG, "Bitmap found in SoftReference cache!!!!!!!"); // Bitmap found in soft cache return bitmap; } else { Log.e(TAG, "oooooooooooooooooooo --> Soft reference has been Garbage Collected"); // Soft reference has been Garbage Collected sSoftBitmapCache.remove(url); } } return null; } /** * Clears the image cache used internally to improve performance. Note that for memory * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay. */ public void clearCache() { Log.e(TAG, "----------------clearCache() ------------------"); synchronized (sHardBitmapCache) { //sHardBitmapCache.clear(); } //sSoftBitmapCache.clear(); sHardBitmapCache.clear(); sSoftBitmapCache.clear(); } /** * Allow a new delay before the automatic cache clear is done. */ private void resetPurgeTimer() { purgeHandler.removeCallbacks(purger); purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE); } 

}

Yo entiendo completamente la lógica detrás de este ejemplo sin embargo estoy confundido por qué esto no funciona correctamente.

Tenemos dos niveles de memoria caché: El caché duro y el caché SoftReference. Guardamos los mapas de bits en el caché duro y luego cuando se llena nos ahorramos en el SoftReference y reordenar caché duro.

A continuación, cierro la aplicación, cerrar el Internet y reiniciar it.In el constructor que llamamos resetPurgeTimer () que borra el caché y por lo tanto nada está en la caché. Para evitar esto, comenté esa línea para no borrar la memoria caché. Reinicio mi aplicación y puedo ver 6-7 imágenes que están presentes en la caché de SoftReference (de acuerdo con LogCat) ..

Sé que caché SoftReference tal vez se vacíe cuando GC se llama, pero es casi vacío, incluso antes de cerrar la aplicación o deshabilitar wifi … Hay un error conocido con SoftReferences que se recolectan basura incluso sin memoria de conseguir baja ..

¿Hay algo mal con este ejemplo ampliamente utilizado?

Gracias por adelantado,

Andreas

Sí, en Android 3.0 y anteriores no puede utilizar referencias débiles o suaves para mapas de bits. La JVM los recogerá agresivamente. En su lugar debe utilizar un LRUCache (en la biblioteca de soporte) para almacenarlas en caché. También recomendaría establecer el tamaño de la caché máx. A alguna fracción del tamaño del montón máximo (como 1/6 por ejemplo).

  • Mapa personalizado de Android
  • Cómo utilizar Android Map para hacer zoom con gesto de la mano (iphone like)
  • Android ¿Es posible definir un mapa en un archivo XML?
  • Cómo generar un código de hash exclusivo para la entrada de cadena en android ...?
  • Android - Bitmap y gestión de la memoria?
  • Android: Pasar un mapa hash entre las actividades
  • Obtención de 0.0 para latitud y longitud mientras muestra la ubicación actual en el mapa
  • Problema con un gran número de marcadores en el mapa
  • Cómo obtener el objeto Mat desde el byte en openCV android?
  • Android: Pregunta sobre mapas de bits, uso de memoria y escala
  • Obtener las líneas de GL10 dibujo de imágenes junto a la otra, la solución?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.