Using Picasso with a custom disk cache

In the Volley library, the NetworkImageView class requires an ImageLoader , which processes all image requests looking for them inside the ImageCache implementation, the user is free to choose how the cache, location and image name should work.

I switch from Volley to Retrofit , and for the images I decided to try Picasso .

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

I would like to be able to do the same with Picasso, is it possible with the Picasso API or should I write such a function myself?

I thought to upload the image to a folder (folder with cache) and use Picasso.with(mContext).load(File downloadedimage) at the end. Is this the right way or are there best practices?

+20
android caching picasso
Apr 24 '14 at 21:29
source share
4 answers

Picasso has no disk cache. It delegates any HTTP client that you use for this function (relying on the HTTP cache semantics for cache management). Because of this, the behavior you are looking for comes for free.

The main HTTP client will only download the image over the network if it does not exist in its local cache (and this image has not expired).

However, you can create your own cache implementation for java.net.HttpUrlConnection (via ResponseCache or OkHttp (via ResponseCache or OkResponseCache ), which stores the files in the desired format. I would strongly recommend this.

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

You can call setIndicatorsEnabled(true) on the Picasso instance to see the indicator from which images are loading. It looks like this:

If you never see the blue indicator, it is likely that your deleted images do not include the correct cache headers to enable caching to disk.

+42
Apr 24 '14 at 22:43
source share
β€” -

If your project uses the okhttp library , picasso will automatically use it as the default bootloader, and the cachΓ© drive will work automatically.

Assuming you are using Android Studio, just add these two lines to dependencies in the build.gradle file and you will be installed. (No need for additional configurations with Picasso)

 dependencies { [...] compile 'com.squareup.okhttp:okhttp:2.+' compile 'com.squareup.okhttp:okhttp-urlconnection:2.+' } 
+11
Jul 23 '14 at 11:14
source share

As many people have rightly pointed out, OkHttpClient is the way to cache.

When caching with OkHttp, you can also get more control over the Cache-Control header in the HTTP response with OkHttp interceptors, see my answer here

+2
Oct 21 '15 at 21:25
source share

As previously written, Picasso uses the Http core client cache.

The built-in HttpUrlConnection cache does not work in truly standalone mode and if for some reason using OkHttpClient is undesirable , you can use your own implementation of the disk cache (of course, based on DiskLruCache ).

One way is to subclass com.squareup.picasso.UrlConnectionDownloader and program the entire logic at:

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

And then use your implementation as follows:

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

Here is my implementation of UrlConnectionDownloader , which works with a disk cache and is sent to Picasso bitmaps even in full offline mode:

 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); } } 
0
Mar 25 '16 at 12:49
source share



All Articles