・バックグラウンドで Bitmap を処理する
・Bitmap をキャッシュする
をまとめて、DiskLruCache として Displaying Bitmaps Efficiently のサンプル BitmapFun.zip の DiskLruCache.java をベースにしたコードを github に公開しました。Google I/O 2012 のコードも参考にしています。
github - yanzm/ImageLoadLib -
特徴は API Level 4 から使えるようにしてあることです(BitmapFun や Google I/O 2012 のコードはそうなっていません)。
こんな感じで使います。
private ImageFetcher mImageFetcher;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mImageFetcher = Utils.getImageFetcher(getActivity());
}
private void loadImage(ImageView iv, String imageUrl) {
mImageFetcher.loadImage(imageUrl, iv, R.drawable.loading);
}
BitmapFun の DiskLruCache は Google I/O 2012 や Androdi 4.0 のソースコードに含まれている DiskLruCache よりかなり単純化されています。 なので読めばわかると思いますが、少しだけ解説します。
内部で行っている処理は
1. キャッシュディレクトリの決定(外部ストレージが使えるなら、外部ストレージを、そうでないなら内部ストレージを使う)
public static File getDiskCacheDir(Context context, String uniqueName) {
final String cachePath =
Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
|| !isExternalStorageRemovable() ?
getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private static boolean isExternalStorageRemovable() {
if (Utils.hasGingerbread()) {
return Environment.isExternalStorageRemovable();
}
return true;
}
@TargetApi(Build.VERSION_CODES.FROYO)
private static File getExternalCacheDir(Context context) {
if (Utils.hasFroyo()) {
File cacheDir = context.getExternalCacheDir();
if (cacheDir != null) {
return cacheDir;
}
}
// Froyo 以前は自前でディレクトリを作成する
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
2. キャッシュディレクトリに作成(なければ)とサイズチェック
public static DiskLruCache openCache(Context context, File cacheDir, long maxByteSize) {
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
if (cacheDir.isDirectory() && cacheDir.canWrite() && getUsableSpace(cacheDir) > maxByteSize) {
return new DiskLruCache(cacheDir, maxByteSize);
}
return null;
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private static long getUsableSpace(File path) {
if (Utils.hasGingerbread()) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
private DiskLruCache(File cacheDir, long maxByteSize) {
mCacheDir = cacheDir;
maxCacheByteSize = maxByteSize;
}
3. ディスクキャッシュに Bitmap を追加(ファイルとして保存)
private final Map<String, String> mLinkedHashMap = Collections.synchronizedMap(new LinkedHashMap<String, String>(
INITIAL_CAPACITY, LOAD_FACTOR, true));
public void put(String key, Bitmap data) {
synchronized (mLinkedHashMap) {
if (mLinkedHashMap.get(key) == null) {
try {
final String file = createFilePath(mCacheDir, key);
if (writeBitmapToFile(data, file)) {
put(key, file);
flushCache();
}
} catch (final FileNotFoundException e) {
Log.e(TAG, "Error in put: " + e.getMessage());
} catch (final IOException e) {
Log.e(TAG, "Error in put: " + e.getMessage());
}
}
}
}
public static String createFilePath(File cacheDir, String key) {
return cacheDir.getAbsolutePath() + File.separator + CACHE_FILENAME_PREFIX + key;
}
private boolean writeBitmapToFile(Bitmap bitmap, String file) throws IOException, FileNotFoundException {
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file), IO_BUFFER_SIZE);
return bitmap.compress(mCompressFormat, mCompressQuality, out);
} finally {
if (out != null) {
out.close();
}
}
}
4. ハッシュマップに追加し、最大キャッシュサイズを超えていたら、超えなくなるまで最後に参照したファイルから順番に削除
private void put(String key, String file) {
mLinkedHashMap.put(key, file);
cacheSize = mLinkedHashMap.size();
cacheByteSize += new File(file).length();
}
private void flushCache() {
Entry>String, String> eldestEntry;
File eldestFile;
long eldestFileSize;
int count = 0;
while (count < MAX_REMOVALS && (cacheSize > maxCacheItemSize || cacheByteSize > maxCacheByteSize)) {
eldestEntry = mLinkedHashMap.entrySet().iterator().next();
eldestFile = new File(eldestEntry.getValue());
eldestFileSize = eldestFile.length();
mLinkedHashMap.remove(eldestEntry.getKey());
eldestFile.delete();
cacheSize = mLinkedHashMap.size();
cacheByteSize -= eldestFileSize;
count++;
if (BuildConfig.DEBUG) {
Log.d(TAG, "flushCache - Removed cache file, " + eldestFile + ", " + eldestFileSize);
}
}
}
5. ディスクキャッシュから Bitmap を取得
public Bitmap get(String key) {
synchronized (mLinkedHashMap) {
final String file = mLinkedHashMap.get(key);
if (file != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache hit");
}
return BitmapFactory.decodeFile(file);
} else {
final String existingFile = createFilePath(mCacheDir, key);
if (new File(existingFile).exists()) {
put(key, existingFile);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache hit (existing file)");
}
return BitmapFactory.decodeFile(existingFile);
}
}
return null;
}
}
Android 4.0 のソースコードの DiskLruCache の解説はまぁ、そのうち、、、、