Android でインターネットの画像をキャッシュする

Android アプリを作っていると、インターネットから画像を取得して利用することがよくありますが、 それをローカルにキャッシュするにはどうすればいいでしょう、という話を書きます。

ネットからのダウンロードはバックグラウンド・スレッドでやらないと(3.0 以降で)落ちるので、AsyncTask みたいにして doInBackground でダウンロードするとだいたいこんな風になるでしょうか (mUrl というインスタンス変数に画像の URL が入ってる)。

    @Override
    protected Bitmap doInBackground(Void... params) {
        try {
            URL url = new URL(mUrl);
            URLConnection connection = url.openConnection();
            connection.setUseCaches(true);  // ここが大切
            return BitmapFactory.decodeStream(connection.getInputStream());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

途中の強調表示したところがポイントです。Java 標準でキャッシュする機能があるとか知らなかった。

実はこれだけでは動かなくて、どのようにキャッシュするのか自分で実装しないと駄目です。ResponseCache というクラスを継承して作ります。こんな感じです。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.ResponseCache;
import java.net.URI;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

public class AndroidResponseCache extends ResponseCache {

    private final File mCacheDir;

    public AndroidResponseCache(File cacheDir) {
        mCacheDir = cacheDir;
    }

    @Override
    public CacheResponse get(URI uri, String s, Map<String, List<String>> arg2) throws IOException {
        final File file = new File(mCacheDir, 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);
                }
            };
        }
        return null;
    }

    @Override
    public CacheRequest put(URI uri, URLConnection connection) throws IOException {
        final File file = new File(mCacheDir, escape(connection.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 uri) {
        // ちょっとオーバーキルらしい (ファイル名に使える文字変換してしまう)
        return URLEncoder.encode(uri);
    }
}

Application か Activity の onCreate あたりでキャッシングに上のクラスを使うように設定します。

ResponseCache.setDefault(new AndroidResponseCache(getCacheDir()));

これまで何度も自前で作ってきた苦労は何だったんだ。

参考にしたページ (というかほとんど組み合わせて翻訳しただけかもしれない)

コメント

このブログの人気の投稿

Volley で同期的にリクエストを実行する