Android Image Caching

June 18, 2011 Tyler Schultz

Web development spoiled me. Set the src tag on an img tag and away you go. For the most part, browsers just do the right thing and cache these requests. Disappointingly, Android does not come with out of the box support for caching content downloaded from the network. Server apis often require SSL, request headers, support for different HTTP methods, multipart post bodies, etc. requiring the use of the apache http client libraries. These libraries are powerful, but are a bear to work with. As far as I can tell there is no support for caching http responses built into the apache libraries, requiring that you roll your own caching scheme. Images often do not have all these complicated HTTP requirements. They’re usually simple HTTP GET calls. This makes the java.net.* libraries appealing. The java.net libraries come with some handy classes that makes caching a breeze.

Here is a snippet that will cause your request to be pulled from the cache, should it exist. If it doesn’t exist, the retrieved response will be written to the cache.

URL url = new URL("https://pivotallabs.com/images/pivotallabs-logo.png");
URLConnection connection = url.openConnection();
connection.setUseCaches(true);
Drawable drawable = Drawable.createFromStream(connection.getInputStream(), "src");

In the past I tried to use the code above alone and found it didn’t work, and no solutions seemed obvious. The javadocs for URLConnection don’t mention anything about ResponseCache, but there is work to be done there to make it all work. The following example shows using Android’s cache directory to cache the responses. You’ll want to take a moment to read the Android docs about the cache dir. There are guidelines for use of the directory. This looks like a lot of code (Java, Java, Java, Java), but IntelliJ will allow you to cntl-shift-space your way to happiness and write most of it for you.

final String cacheDir = context.getCacheDir();
ResponseCache.setDefault(new ResponseCache() {
    @Override
    public CacheResponse get(URI uri, String s, Map<String, List<string>> headers) throws IOException {
        final File file = new File(cacheDir, escape(uri.getPath()));
        if (file.exists()) {
            return new CacheResponse() {
                @Override
                public Map<String, List<string>> getHeaders() throws IOException {
                    return null;
                }

                @Override
                public InputStream getBody() throws IOException {
                    return new FileInputStream(file);
                }
            };
        } else {
            return null;
        }
    }

    @Override
    public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
        final File file = new File(cacheDir, escape(urlConnection.getURL().getPath()));
        return new CacheRequest() {
            @Override
            public OutputStream getBody() throws IOException {
                return new FileOutputStream(file);
            }

            @Override
            public void abort() {
                file.delete();
            }
        };
    }

    private String escape(String url) {
       return url.replace("/", "-").replace(".", "-");
    }
});
</string></string>

This is a simple implementation that pays no attention to the response headers, returns no cached response headers, etc. but I think you get the gist. There is one gotcha: the URI passed into the put method does not contain the path info for the url, it must be retrieved from the URLConnection. Happy caching!

About the Author

Biography

Previous
Android Tidbits 6/21/2011: Unregister? Nah!
Android Tidbits 6/21/2011: Unregister? Nah!

C2DM Unregister Issues It turns out when you follow the client-side C2DM unregistration process, this does...

Next
New in Pivotal Tracker: Multi-story select and drag/drop, iPad usability improvements
New in Pivotal Tracker: Multi-story select and drag/drop, iPad usability improvements

Happy Friday! We've just rolled out some changes to Pivotal Tracker, which we're hoping will have a very n...