web-dev-qa-db-fra.com

Utilisation de Picasso avec un cache disque personnalisé

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?

23
Vektor88

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.

48
Jake Wharton

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.+'
}
12
txulu

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

2
Gaurav B

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