Использование Picasso с пользовательским дисковым кешем

В Volley библиотека, NetworkImageView класс требует ImageLoader который обрабатывает все запросы изображений, ища их внутри ImageCache Реализация, пользователь может свободно выбирать, как должен работать кеш, расположение и название изображений.

Я переключаюсь с Volley в Retrofitи для изображений решил попробовать Picasso,

В предыдущей библиотеке у меня был параметр String в каждом из моих элементов, содержащих URL изображения, затем я использовал myNetworkImageView.setImageUrl(item.getURL()) и он был в состоянии определить, было ли изображение кэшировано на диске. Если изображение существовало в папке кэша, оно было загружено, в противном случае оно было загружено и загружено.

Я хотел бы иметь возможность сделать то же самое с Пикассо, возможно ли это с Picasso API или я должен сам написать такую ​​функцию?

Я думал, чтобы загрузить изображение в папку (папку кеша), и использовать Picasso.with(mContext).load(File downloadedimage) по окончании. Это правильный путь или есть лучшие практики?

4 ответа

Решение

У Пикассо нет дискового кэша. Он делегирует любому HTTP-клиенту, который вы используете для этой функции (полагаясь на семантику HTTP-кэша для управления кэшем). Из-за этого, поведение, которое вы ищете, приходит бесплатно.

Базовый HTTP-клиент будет загружать изображение только по сети, если оно не существует в его локальном кэше (и это изображение не просрочено).

Тем не менее, вы можете создать собственную реализацию кэша для java.net.HttpUrlConnection (с помощью ResponseCache или OkHttp (через ResponseCache или же OkResponseCache) который хранит файлы в нужном вам формате. Однако я настоятельно рекомендую против этого.

Пусть Пикассо и HTTP-клиент сделают всю работу за вас!

Ты можешь позвонить setIndicatorsEnabled(true) на Picasso экземпляр, чтобы увидеть индикатор, откуда изображения загружаются. Это выглядит так:

Если вы никогда не видите синий индикатор, вероятно, ваши удаленные образы не содержат надлежащих заголовков кэша для включения кэширования на диск.

Если ваш проект использует библиотеку okhttp, то Picasso автоматически использует ее в качестве загрузчика по умолчанию, а дисковый кэш будет работать автоматически.

Предполагая, что вы используете Android Studio, просто добавьте эти две строки в dependencies в build.gradle файл, и вы будете установлены. (Никаких дополнительных настроек с Пикассо не требуется)

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

Как справедливо отмечают многие здесь, OkHttpClient - это способ кэширования.

При кэшировании с помощью OkHttp вы также можете получить больший контроль над заголовком Cache-Control в ответе HTTP с помощью перехватчиков OkHttp, см. Мой ответ здесь.

Как это было написано ранее, Picasso использует кеш основного клиента Http.

Встроенный кэш HttpUrlConnection не работает в действительно автономном режиме, и если использование OkHttpClient нежелательно по некоторым причинам, можно использовать собственную реализацию дискового кэша (конечно, на основе DiskLruCache).

Одним из способов является подкласс com.squareup.picasso.UrlConnectionDownloader и запрограммировать всю логику в:

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

А затем используйте вашу реализацию следующим образом:

new Picasso.Builder(context).downloader(<your_downloader>).build();

Вот моя реализация UrlConnectionDownloader, который работает с дисковым кешем и отправляет растровые изображения Пикассо даже в автономном режиме:

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);
    }
}
Другие вопросы по тегам