Dans la bibliothèque Volley
, la classe NetworkImageView
nécessite un ImageLoader
qui gère toutes les requêtes d'image en les recherchant dans une implémentation ImageCache
, l'utilisateur est libre de choisissez le fonctionnement du cache, l'emplacement et le nom des images.
Je passe de Volley
à Retrofit
, et pour les images, j'ai décidé d'essayer Picasso
.
Avec l'ancienne bibliothèque, j'avais un paramètre String dans chacun de mes éléments contenant l'URL de l'image, puis j'ai utilisé myNetworkImageView.setImageUrl(item.getURL())
et il a pu déterminer si l'image était mise en cache sur le disque. Si l'image existait dans le dossier cache, l'image a été chargée, sinon elle a été téléchargée et chargée.
J'aimerais pouvoir faire de même avec Picasso, est-ce possible avec les API Picasso
ou dois-je coder une telle fonctionnalité par moi-même?
Je pensais télécharger l'image dans un dossier (le dossier cache) et utiliser Picasso.with(mContext).load(File downloadedimage)
à la fin. Est-ce la bonne façon ou existe-t-il des meilleures pratiques?
Picasso n'a pas de cache disque. Il délègue au client HTTP que vous utilisez pour cette fonctionnalité (en s'appuyant sur la sémantique du cache HTTP pour le contrôle du cache). Pour cette raison, le comportement que vous recherchez est gratuit.
Le client HTTP sous-jacent ne téléchargera une image sur le réseau que si elle n'existe pas dans son cache local (et que cette image n'est pas expirée).
Cela dit, vous pouvez créer une implémentation de cache personnalisée pour Java.net.HttpUrlConnection
(Via ResponseCache
ou OkHttp (via ResponseCache
ou OkResponseCache
) qui stocke les fichiers dans le format que vous désirez. Je déconseille cependant fortement cela.
Laissez Picasso et le client HTTP faire le travail pour vous!
Vous pouvez appeler setIndicatorsEnabled(true)
sur l'instance Picasso
pour voir un indicateur d'où les images sont chargées. Cela ressemble à ceci:
Si vous ne voyez jamais d'indicateur bleu, il est probable que vos images distantes n'incluent pas les en-têtes de cache appropriés pour activer la mise en cache sur le disque.
Si votre projet utilise la bibliothèque okhttp alors picasso l'utilisera automatiquement comme téléchargeur par défaut et le cache disque fonctionnera automatiquement.
En supposant que vous utilisez Android Studio, ajoutez simplement ces deux lignes sous dependencies
dans le build.gradle
fichier et vous serez défini. (Aucune configuration supplémentaire avec picasso nécessaire)
dependencies {
[...]
compile 'com.squareup.okhttp:okhttp:2.+'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.+'
}
Comme l'ont souligné à juste titre de nombreuses personnes ici, OkHttpClient est la voie à suivre pour la mise en cache.
Lors de la mise en cache avec OkHttp, vous pouvez également vouloir gagner plus de contrôle sur l'en-tête Cache-Control dans la réponse HTTP en utilisant les intercepteurs OkHttp , voir ma réponse ici
Comme il a été écrit précédemment, Picasso utilise un cache du client Http sous-jacent.
Le cache intégré de HttpUrlConnection ne fonctionne pas en mode vraiment hors ligne et Si l'utilisation d'OkHttpClient n'est pas souhaitée pour certaines raisons, il est possible d'utiliser votre propre implémentation de cache de disque (bien sûr basée sur DiskLruCache
).
L'une des méthodes consiste à sous-classer com.squareup.picasso.UrlConnectionDownloader
et programmer toute la logique à:
@Override
public Response load(final Uri uri, int networkPolicy) throws IOException {
...
}
Et puis utilisez votre implémentation comme ceci:
new Picasso.Builder(context).downloader(<your_downloader>).build();
Voici mon implémentation de UrlConnectionDownloader
, qui fonctionne avec le cache de disque et est livrée aux bitmaps Picasso même en mode hors ligne total:
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);
}
}