Androidの画像キャッシュ管理
同じ画像をロードするたびにネットワークから取得すると、その代価はあまりにも大きい.したがって、同じピクチャはネットワークから1回だけ取得してローカルにキャッシュし、その後同じピクチャをロードするときにキャッシュからロードすればよい.メモリキャッシュから画像を読み取るのが一番速いですが、メモリ容量が限られているので、ファイルキャッシュを加えたほうがいいです.ファイルキャッシュスペースも無限大ではなく、容量が大きいほど読み取り効率が低下するため、10 Mなどの限定サイズを設定したり、保存時間を1日などに限定したりすることができます.
したがって、画像をロードするプロセスは、次のとおりです.
1、メモリキャッシュから先に取得し、取得したら戻り、取得しない場合は次のステップに進む.
2、ファイルキャッシュから取得し、取得したら戻ってメモリキャッシュに更新し、取得しなければ次のステップに進む.
3、ネットワークから画像をダウンロードし、メモリキャッシュとファイルキャッシュに更新する.
後の2つのステップの純砕はビジネスロジックに属し,しばらく表に出さないが,ここではQが使用するピクチャキャッシュ管理ポリシーを見る.キャッシュ管理といえば、まずjavaの強いリファレンスと弱いリファレンスについてお話しします
強参照:最も一般的な参照です.オブジェクトに強い参照がある場合、GCはそれを回収しません.A a=new A()弱引用:弱引用は次の3つに分類されます.
ソフトリファレンス(SoftReference):このようなリファレンスは、メモリ容量がGC未満の場合にのみ回収されます.
弱引用(WeakReference):このような引用はより短いライフサイクルを有し、GCスキャン中にこのような引用が発見されると、現在のメモリが十分であるかどうかにかかわらず、を直ちに回収する.
ダミーリファレンス(PhantomReference):このようなリファレンスはオブジェクトのライフサイクルを決定しません.オブジェクトがダミーリファレンスのみを持っている場合は、いつでもが回収されます.
次に、キャッシュをより大幅に使用するために、強参照キャッシュ(強参照)と弱参照キャッシュ(弱参照)の2重キャッシュが使用されているピクチャキャッシュクラスを見てみましょう.強参照キャッシュは簡単に回収されず、共通データを保存するために使用され、弱参照キャッシュに頻繁に転送されません.**
キャッシュポリシー全体は、弱いリファレンスキャッシュと強いリファレンスキャッシュを組み合わせて使用し、LRUCacheを組み合わせて、できるだけキャッシュを利用した上で、キャッシュヒット率を大幅に向上させます.個人的にはこのクラスに改善点があると思います.例えば、LRUCacheが要素を削除したとき、デフォルトは直接削除します.ここでより良い方法は、LRUCacheのentryRemovedメソッドを書き換えることで、強いリファレンスキャッシュがいっぱいになると、LRUアルゴリズムに基づいて最近最も長く使用されていないピクチャを自動的に弱いリファレンスキャッシュに移動します.以下のようにします.
したがって、画像をロードするプロセスは、次のとおりです.
1、メモリキャッシュから先に取得し、取得したら戻り、取得しない場合は次のステップに進む.
2、ファイルキャッシュから取得し、取得したら戻ってメモリキャッシュに更新し、取得しなければ次のステップに進む.
3、ネットワークから画像をダウンロードし、メモリキャッシュとファイルキャッシュに更新する.
後の2つのステップの純砕はビジネスロジックに属し,しばらく表に出さないが,ここではQが使用するピクチャキャッシュ管理ポリシーを見る.キャッシュ管理といえば、まずjavaの強いリファレンスと弱いリファレンスについてお話しします
強参照:最も一般的な参照です.オブジェクトに強い参照がある場合、GCはそれを回収しません.A a=new A()弱引用:弱引用は次の3つに分類されます.
ソフトリファレンス(SoftReference):このようなリファレンスは、メモリ容量がGC未満の場合にのみ回収されます.
弱引用(WeakReference):このような引用はより短いライフサイクルを有し、GCスキャン中にこのような引用が発見されると、現在のメモリが十分であるかどうかにかかわらず、を直ちに回収する.
ダミーリファレンス(PhantomReference):このようなリファレンスはオブジェクトのライフサイクルを決定しません.オブジェクトがダミーリファレンスのみを持っている場合は、いつでもが回収されます.
次に、キャッシュをより大幅に使用するために、強参照キャッシュ(強参照)と弱参照キャッシュ(弱参照)の2重キャッシュが使用されているピクチャキャッシュクラスを見てみましょう.強参照キャッシュは簡単に回収されず、共通データを保存するために使用され、弱参照キャッシュに頻繁に転送されません.**
ImageCache.java
public class ImageCache {
private static final String TAG = "ImageCache";
//CustomLruCache LruCache , ,
private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size
//
private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MB
private final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();
public ImageCache(int memSize) {
memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);
QLog.d(TAG, "Memory cache size = " + memSize + "MB");
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {
// LruCache sizeOf ,
@Override
protected int sizeOf(String key, Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
// bitmap
return bitmap.getRowBytes() * bitmap.getHeight();
}
return 0;
} else if (drawable instanceof AnimationDrawable) {
// , ,
AnimationDrawable anim = (AnimationDrawable) drawable;
int count = anim.getNumberOfFrames();
int memSize = 0;
for (int i = 0; i < count; i++) {
Drawable dr = anim.getFrame(i);
if (dr instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
if (bitmap != null) {
memSize += bitmap.getRowBytes() * bitmap.getHeight();
}
}
}
return memSize;
}
return 0;
}
};
}
//
public Drawable getImageFromMemCache(String key) {
Drawable memDrawable = null;
if (mMemoryCache != null) {
// , , CustomLruCache , LRU ?
// , LinkedHashMap LRU , ,get ,
memDrawable = mMemoryCache.remove(key);
if (memDrawable != null) {
memDrawable = memDrawable.getConstantState().newDrawable();
mMemoryCache.put(key, memDrawable);
return memDrawable;
}
}
// ,
WeakReference<Drawable> ref = mRefCache.get(key);
if (ref != null) {
// , ,
memDrawable = ref.get();
if (memDrawable == null) {
mRefCache.remove(key);
}
}
return memDrawable;
}
// ,
public void addImageToCache(String data, Drawable drawable) {
// Add to memory cache
if (mMemoryCache != null && mMemoryCache.get(data) == null) {
mMemoryCache.put(data, drawable);
mRefCache.put(data, new WeakReference<Drawable>(drawable));
}
}
//
public void removeImageFromCache(String data) {
if (mRefCache != null) {
mRefCache.remove(data);
}
if (mMemoryCache != null) {
mMemoryCache.remove(data);
}
}
public Drawable getImageFromDiskCache(String pathName) {
// TODO disk cache
return null;
}
public void clearCaches() {
// mDiskCache.clearCache();
mMemoryCache.evictAll();
mRefCache.clear();
}
}
キャッシュポリシー全体は、弱いリファレンスキャッシュと強いリファレンスキャッシュを組み合わせて使用し、LRUCacheを組み合わせて、できるだけキャッシュを利用した上で、キャッシュヒット率を大幅に向上させます.個人的にはこのクラスに改善点があると思います.例えば、LRUCacheが要素を削除したとき、デフォルトは直接削除します.ここでより良い方法は、LRUCacheのentryRemovedメソッドを書き換えることで、強いリファレンスキャッシュがいっぱいになると、LRUアルゴリズムに基づいて最近最も長く使用されていないピクチャを自動的に弱いリファレンスキャッシュに移動します.以下のようにします.
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {
// LruCache sizeOf ,
@Override
protected int sizeOf(String key, Drawable drawable) {
.........
}
, , ,
@Override
protected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {
if (oldDawable != null) {
mRefCache.put(data, new WeakReference<Drawable>(oldDawable));
}
}
};
:ImageMemoryCache
public class ImageMemoryCache {
private static final int SOFT_CACHE_SIZE = 15; //
private static LruCache mLruCache; //
private static LinkedHashMap> mSoftCache; //
public ImageMemoryCache(Context context) {
int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 4; // , 1/4
mLruCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
return value.getRowBytes() * value.getHeight();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
// , LRU
mSoftCache.put(key, new SoftReference(oldValue));
}
};
mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {
private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry> eldest) {
if (size() > SOFT_CACHE_SIZE){
return true;
}
return false;
}
};
}
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
// , LinkedHashMap , LRU
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
// ,
synchronized (mSoftCache) {
SoftReference bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
//
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
mSoftCache.remove(url);
}
}
}
return null;
}
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}
:ImageMemoryCache
public class ImageFileCache {
private static final String CACHDIR = "ImgCach";
private static final String WHOLESALE_CONV = ".cach";
private static final int MB = 1024*1024;
private static final int CACHE_SIZE = 10;
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache() {
//
removeCache(getDirectory());
}
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + convertUrlToFileName(url);
File file = new File(path);
if (file.exists()) {
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null) {
file.delete();
} else {
updateFileTime(path);
return bmp;
}
}
return null;
}
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
// sdcard
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD
return;
}
String filename = convertUrlToFileName(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
private boolean removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (files == null) {
return true;
}
if (!android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return false;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 * files.length) + 1);
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
public void updateFileTime(String path) {
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
private String convertUrlToFileName(String url) {
String[] strs = url.split("/");
return strs[strs.length - 1] + WHOLESALE_CONV;
}
private String getDirectory() {
String dir = getSDPath() + "/" + CACHDIR;
return dir;
}
private String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED); // sd
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory(); //
}
if (sdDir != null) {
return sdDir.toString();
} else {
return "";
}
}
private class FileLastModifSort implements Comparator {
public int compare(File arg0, File arg1) {
if (arg0.lastModified() > arg1.lastModified()) {
return 1;
} else if (arg0.lastModified() == arg1.lastModified()) {
return 0;
} else {
return -1;
}
}
}
}
:
public class ImageGetFromHttp {
private static final String LOG_TAG = "ImageGetFromHttp";
public static Bitmap downloadBitmap(String url) {
final HttpClient client = new DefaultHttpClient();
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
FilterInputStream fit = new FlushedInputStream(inputStream);
return BitmapFactory.decodeStream(fit);
} finally {
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {
client.getConnectionManager().shutdown();
}
return null;
}
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
}
, :
public Bitmap getBitmap(String url) {
//
Bitmap result = memoryCache.getBitmapFromCache(url);
if (result == null) {
//
result = fileCache.getImage(url);
if (result == null) {
//
result = ImageGetFromHttp.downloadBitmap(url);
if (result != null) {
fileCache.saveBitmap(result, url);
memoryCache.addBitmapToCache(url, result);
}
} else {
//
memoryCache.addBitmapToCache(url, result);
}
}
return result;
}