Android 将 Picasso 与自定义磁盘缓存一起使用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23280242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 06:58:55  来源:igfitidea点击:

Using Picasso with custom disk cache

androidcachingpicasso

提问by Vektor88

In Volleylibrary, the NetworkImageViewclass requires an ImageLoader?that handles all the image requests by searching for them inside an ImageCache?implementation, the user is free to choose how the cache should work, the location and the name of the images.

Volley库中,NetworkImageView该类需要一个ImageLoader? 通过在ImageCache? 实现中搜索它们来处理所有图像请求,用户可以自由选择缓存应该如何工作,图像的位置和名称。

I'm switching from Volley?to Retrofit, and for the images I decided to try Picasso.

我正在从Volley?to切换Retrofit,对于我决定尝试的图像Picasso

With the former library, I had a String parameter in each of my items containing the image URL, then I used myNetworkImageView.setImageUrl(item.getURL())and it was able to determine if image was cached on disk. If the image existed in cache folder, the image was loaded, otherwise it was downloaded and loaded.

对于前一个库,我在每个包含图像 URL 的项目中都有一个 String 参数,然后我使用myNetworkImageView.setImageUrl(item.getURL())它并能够确定图像是否缓存在磁盘上。如果图片存在于缓存文件夹中,则加载图片,否则下载并加载。

I would like to be able to do the same with Picasso, is it possible with PicassoAPIs or should I code such feature by myself?

我希望能够用 Picasso 做同样的事情,是否可以使用PicassoAPI 或者我应该自己编写这样的功能?

I was thinking to download the image to a folder (the cache folder), and use Picasso.with(mContext).load(File downloadedimage)on completion. Is this the proper way or are there any best practices?

我想将图像下载到一个文件夹(缓存文件夹),并Picasso.with(mContext).load(File downloadedimage)在完成后使用。这是正确的方法还是有任何最佳实践?

回答by Jake Wharton

Picasso doesn't have a disk cache. It delegates to whatever HTTP client you are using for that functionality (relying on HTTP cache semantics for cache control). Because of this, the behavior you seek comes for free.

Picasso 没有磁盘缓存。它委托给您用于该功能的任何 HTTP 客户端(依靠 HTTP 缓存语义进行缓存控制)。因此,您寻求的行为是免费的。

The underlying HTTP client will only download an image over the network if one does not exist in its local cache (and that image isn't expired).

如果本地缓存中不存在图像(并且该图像未过期),则底层 HTTP 客户端将仅通过网络下载图像。

That said, you cancreate custom cache implementation for java.net.HttpUrlConnection(via ResponseCacheor OkHttp (via ResponseCacheor OkResponseCache) which stores files in the format you desire. I would strongly advise against this, however.

也就是说,您可以java.net.HttpUrlConnection(viaResponseCache或 OkHttp (via ResponseCacheor OkResponseCache)创建自定义缓存实现,以您想要的格式存储文件。但是,我强烈建议不要这样做

Let Picasso and the HTTP client do the work for you!

让 Picasso 和 HTTP 客户端为您完成工作!

You can call setIndicatorsEnabled(true)on the Picassoinstance to see an indicator from where images are being loaded. It looks like this:

你可以调用setIndicatorsEnabled(true)Picasso实例看到正在加载的图像,其中一个指标。它看起来像这样:

If you never see a blue indicator, it's likely that your remote images do not include proper cache headers to enable caching to disk.

如果您从未看到蓝色指示器,则可能是您的远程图像不包含正确的缓存标头来启用缓存到磁盘。

回答by txulu

If your project is using the okhttp librarythen picasso will automatically use it as the default downloader and the disk caché will work automagically.

如果您的项目使用okhttp 库,那么 picasso 将自动将其用作默认下载器,并且磁盘缓存将自动运行。

Assuming that you use Android Studio, just add these two lines under dependenciesin the build.gradlefile and you will be set. (No extra configurations with picasso needed)

假设您使用Android Studio,只需dependenciesbuild.gradle文件下添加这两行即可设置。(无需使用毕加索进行额外配置)

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

回答by Gaurav B

As rightly pointed out by many people here, OkHttpClient is the way to go for caching.

正如这里许多人正确指出的那样,OkHttpClient 是缓存的方法。

When caching with OkHttp you might also want to gain more control on Cache-Control header in the HTTP response using OkHttp interceptors, see my response here

使用 OkHttp 缓存时,您可能还想使用 OkHttp拦截器在 HTTP 响应中获得对 Cache-Control 标头的更多控制,请在此处查看我的响应

回答by Alexander Skvortsov

How it is was written previously, Picasso uses a cache of the underlying Http client.

之前是如何编写的,毕加索使用了底层 Http 客户端的缓存。

HttpUrlConnection's built-in cache isn't working in truly offline mode and If using of OkHttpClient is unwantedby some reasons, it is possible to use your own implementation of disk-cache (of course based on DiskLruCache).

HttpUrlConnection 的内置缓存不能在真正的离线模式下工作,如果由于某些原因不需要使用 OkHttpClient,则可以使用您自己的磁盘缓存实现(当然基于DiskLruCache)。

One of ways is subclassing com.squareup.picasso.UrlConnectionDownloaderand programm whole logic at:

一种方法是com.squareup.picasso.UrlConnectionDownloader在以下位置对整个逻辑进行子类化和编程:

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

And then use your implementation like this:

然后像这样使用你的实现:

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

Here is my implementation of UrlConnectionDownloader, that works with disk-cache and ships to Picasso bitmaps even in total offline mode:

这是我的 实现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);
    }
}