Usando Picasso con caché de disco personalizada

En la biblioteca Volley , la clase NetworkImageView requiere un ImageLoader que maneja todas las solicitudes de imágenes buscándolas dentro de una implementación de ImageCache , el usuario puede elegir libremente cómo debería funcionar la memoria caché, la ubicación y el nombre de las imágenes.

Estoy cambiando de Volley a Retrofit , y para las imágenes decidí probar con Picasso .

Con la biblioteca anterior, tenía un parámetro String en cada uno de mis elementos que contenía la URL de la imagen, luego usé myNetworkImageView.setImageUrl(item.getURL()) y pude determinar si la imagen estaba almacenada en caché en el disco. Si la imagen existía en la carpeta de caché, la imagen se cargó; de lo contrario, se descargó y cargó.

Me gustaría poder hacer lo mismo con Picasso, ¿es posible con las API de Picasso o debería codificar esa característica yo mismo?

Estaba pensando en descargar la imagen a una carpeta (la carpeta de caché), y usar Picasso.with(mContext).load(File downloadedimage) al finalizar. ¿Es esta la manera correcta o existen algunas mejores prácticas?

Picasso no tiene un caché de disco. Se delega en cualquier cliente HTTP que esté utilizando para esa funcionalidad (basándose en la semántica de caché HTTP para el control de caché). Debido a esto, el comportamiento que busca es gratis.

El cliente HTTP subyacente solo descargará una imagen a través de la red si no existe en su caché local (y esa imagen no está expirada).

Dicho esto, puede crear una implementación de caché personalizada para java.net.HttpUrlConnection (a través de ResponseCache u OkHttp (a través de ResponseCache o OkResponseCache ) que almacena los archivos en el formato que desee. Sin embargo, recomendaría encarecidamente no OkResponseCache .

¡Deja que Picasso y el cliente HTTP hagan el trabajo por ti!

Puede llamar a setIndicatorsEnabled(true) en la instancia de Picasso para ver un indicador desde donde se cargan las imágenes. Se parece a esto:

Si nunca ve un indicador azul, es probable que sus imágenes remotas no incluyan encabezados de caché adecuados para habilitar el almacenamiento en caché en el disco.

Si su proyecto está utilizando la biblioteca okhttp, entonces picasso lo usará automáticamente como el descargador predeterminado y el caché de disco funcionará automágicamente.

Suponiendo que usa Android Studio, simplemente agregue estas dos líneas en dependencies en el archivo build.gradle y se establecerá. (No se necesitan configuraciones adicionales con picasso)

 dependencies { [...] compile 'com.squareup.okhttp:okhttp:2.+' compile 'com.squareup.okhttp:okhttp-urlconnection:2.+' } 

Como lo señaló mucha gente aquí, OkHttpClient es el camino a seguir para el almacenamiento en caché.

Al almacenar en caché con OkHttp, es posible que también desee obtener más control sobre el encabezado Cache-Control en la respuesta HTTP utilizando los interceptores OkHttp, consulte mi respuesta aquí.

Como está escrito anteriormente, Picasso usa un caché del cliente Http subyacente.

La memoria caché incorporada de HttpUrlConnection no funciona en el modo verdaderamente fuera de línea y si el uso de OkHttpClient no es deseado por alguna razón, es posible usar su propia implementación de caché de disco (por supuesto, basada en DiskLruCache ).

Una de las formas es subclasificar com.squareup.picasso.UrlConnectionDownloader y programm toda la lógica en:

 @Override public Response load(final Uri uri, int networkPolicy) throws IOException { ... } 

Y luego usa tu implementación de esta manera:

 new Picasso.Builder(context).downloader().build(); 

Aquí está mi implementación de UrlConnectionDownloader , que funciona con caché de disco y se envía a mapas de bits Picasso incluso en modo totalmente fuera de línea:

 public class PicassoBitmapDownloader extends UrlConnectionDownloader { private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB @NonNull private Context context; @Nullable private DiskLruCache diskCache; public class IfModifiedResponse extends Response { private final String ifModifiedSinceDate; public IfModifiedResponse(InputStream stream, boolean loadedFromCache, long contentLength, String ifModifiedSinceDate) { super(stream, loadedFromCache, contentLength); this.ifModifiedSinceDate = ifModifiedSinceDate; } public String getIfModifiedSinceDate() { return ifModifiedSinceDate; } } public PicassoBitmapDownloader(@NonNull Context context) { super(context); this.context = context; } @Override public Response load(final Uri uri, int networkPolicy) throws IOException { final String key = getKey(uri); { Response cachedResponse = getCachedBitmap(key); if (cachedResponse != null) { return cachedResponse; } } IfModifiedResponse response = _load(uri); if (cacheBitmap(key, response.getInputStream(), response.getIfModifiedSinceDate())) { IfModifiedResponse cachedResponse = getCachedBitmap(key); if (cachedResponse != null) {return cachedResponse; } } return response; } @NonNull protected IfModifiedResponse _load(Uri uri) throws IOException { HttpURLConnection connection = openConnection(uri); int responseCode = connection.getResponseCode(); if (responseCode >= 300) { connection.disconnect(); throw new ResponseException(responseCode + " " + connection.getResponseMessage(), 0, responseCode); } long contentLength = connection.getHeaderFieldInt("Content-Length", -1); String lastModified = connection.getHeaderField(Constants.HEADER_LAST_MODIFIED); return new IfModifiedResponse(connection.getInputStream(), false, contentLength, lastModified); } @Override protected HttpURLConnection openConnection(Uri path) throws IOException { HttpURLConnection conn = super.openConnection(path); DiskLruCache diskCache = getDiskCache(); DiskLruCache.Snapshot snapshot = diskCache == null ? null : diskCache.get(getKey(path)); if (snapshot != null) { String ifModifiedSince = snapshot.getString(1); if (!isEmpty(ifModifiedSince)) { conn.addRequestProperty(Constants.HEADER_IF_MODIFIED_SINCE, ifModifiedSince); } } return conn; } @Override public void shutdown() { try { if (diskCache != null) { diskCache.flush(); diskCache.close(); } } catch (IOException e) { e.printStackTrace(); } super.shutdown(); } public boolean cacheBitmap(@Nullable String key, @Nullable InputStream inputStream, @Nullable String ifModifiedSince) { if (inputStream == null || isEmpty(key)) { return false; } OutputStream outputStream = null; DiskLruCache.Editor edit = null; try { DiskLruCache diskCache = getDiskCache(); edit = diskCache == null ? null : diskCache.edit(key); outputStream = edit == null ? null : new BufferedOutputStream(edit.newOutputStream(0)); if (outputStream == null) { return false; } ChatUtils.copy(inputStream, outputStream); outputStream.flush(); edit.set(1, ifModifiedSince == null ? "" : ifModifiedSince); edit.commit(); return true; } catch (Exception e) { e.printStackTrace(); } finally { if (edit != null) { edit.abortUnlessCommitted(); } ChatUtils.closeQuietly(outputStream); } return false; } @Nullable public IfModifiedResponse getCachedBitmap(String key) { try { DiskLruCache diskCache = getDiskCache(); DiskLruCache.Snapshot snapshot = diskCache == null ? null : diskCache.get(key); InputStream inputStream = snapshot == null ? null : snapshot.getInputStream(0); if (inputStream == null) { return null; } return new IfModifiedResponse(inputStream, true, snapshot.getLength(0), snapshot.getString(1)); } catch (Exception e) { e.printStackTrace(); } return null; } @Nullable synchronized public DiskLruCache getDiskCache() { if (diskCache == null) { try { File file = new File(context.getCacheDir() + "/images"); if (!file.exists()) { //noinspection ResultOfMethodCallIgnored file.mkdirs(); } long maxSize = calculateDiskCacheSize(file); diskCache = DiskLruCache.open(file, BuildConfig.VERSION_CODE, 2, maxSize); } catch (Exception e) { e.printStackTrace(); } } return diskCache; } @NonNull private String getKey(@NonNull Uri uri) { String key = md5(uri.toString()); return isEmpty(key) ? String.valueOf(uri.hashCode()) : key; } @Nullable public static String md5(final String toEncrypt) { try { final MessageDigest digest = MessageDigest.getInstance("md5"); digest.update(toEncrypt.getBytes()); final byte[] bytes = digest.digest(); final StringBuilder sb = new StringBuilder(); for (byte aByte : bytes) { sb.append(String.format("%02X", aByte)); } return sb.toString().toLowerCase(); } catch (Exception e) { e.printStackTrace(); return null; } } static long calculateDiskCacheSize(File dir) { long available = ChatUtils.bytesAvailable(dir); // Target 2% of the total space. long size = available / 50; // Bound inside min/max size for disk cache. return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE); } }