本文出自 “阿敏其人” 簡(jiǎn)書博客脱惰,轉(zhuǎn)載或引用請(qǐng)注明出處搏嗡。
本篇主要做的事情,利用LruCache和DiskLruCache造一個(gè)ImageLoader拉一。
對(duì)于OOM和LruCache和DiskLruCache還了解的猴子可以先看一下這兩篇:
安卓OOM和Bitmap圖片二級(jí)緩存機(jī)制(一)
安卓OOM和Bitmap圖片二級(jí)緩存機(jī)制(二)
一采盒、ImageLoader應(yīng)該具備的功能:
- 圖片的同步加載
- 圖片的異步處理
- 圖片壓縮
- 內(nèi)存緩存
- 磁盤緩存
- 網(wǎng)絡(luò)拉取圖片
同步加載: 就是以二級(jí)緩存的方式同步獲取圖片
異步加載: 異步,多線程蔚润,二級(jí)緩存獲取圖片
圖片壓縮: 壓縮圖片磅氨,有效防止OOM
內(nèi)存緩存: 利用LruCache實(shí)現(xiàn)
磁盤緩存: 利用DiskLruCache實(shí)現(xiàn)
網(wǎng)絡(luò)拉取: 沒有緩存時(shí)嫡纠,網(wǎng)絡(luò)拉取
其中烦租,LruCahce和DiskLruCache是這個(gè)圖片框架的核心,除此之外除盏,我們還需要注意的在ListView或者GridView上的一個(gè)復(fù)用問題叉橱。
(前提準(zhǔn)備:網(wǎng)上拿到DiskLruCache的類源碼,放入我們的工程中)
二者蠕、實(shí)現(xiàn)分析:
1窃祝、首先在ImageLoader這個(gè)方法里面初始化Lruchche和DiskLruCache這兩個(gè)類
private ImageLoader(Context context) {
mContext = context.getApplicationContext();
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、我們圖片需要壓縮踱侣,為了解耦粪小,我們把圖片壓縮單獨(dú)設(shè)置建立一個(gè)壓縮類
public class ImageResizer {
private static final String TAG = "ImageResizer";
public ImageResizer() {
}
public Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
public int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
}
3甩栈、LruCache的取存
取和存
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
刪除的話滿了就自動(dòng)刪除
4、DiskLruCache的取存刪
取和刪
/**
* 磁盤緩存的添加
* @param url
* @param reqWidth
* @param reqHeight
* @return
* @throws IOException
*
*
* 磁盤緩存的添加:添加需要通過Editor來完成糕再,利用commit和abort方法來 提交 和 撤銷 操作
*/
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit network from UI Thread.");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (downloadUrlToStream(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
/**
* 磁盤緩存的讀取
* @param url
* @param reqWidth
* @param reqHeight
* @return
* @throws IOException
* 讀攘棵弧:需要通過Snapshot來完成,通過Snapshot可以得到磁盤緩存對(duì)象對(duì)應(yīng)的FileInputStream
* 但是FileInputStream無法很好地進(jìn)行壓縮
* 所以我們通過 FileDescripot 來加載壓縮后的圖片突想,得到加載后的Bitmap添加到內(nèi)存緩存中
*/
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
int reqHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
}
if (mDiskLruCache == null) {
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
return bitmap;
}
5殴蹄、同步接口的實(shí)現(xiàn)
以同步接口的方式去加載圖片,走的是內(nèi)存猾担,磁盤接著網(wǎng)絡(luò)的獲取渠道
同步接口的不能再主線程執(zhí)行袭灯,需要在外部的線程調(diào)用
/**
*
* 同步加載 (從內(nèi)存緩存、磁盤緩存绑嘹、網(wǎng)絡(luò))
* @param uri http url
* @param reqWidth the width ImageView desired
* @param reqHeight the height ImageView desired
* @return bitmap, maybe null.
*
* 同步加載的設(shè)計(jì)步驟:
* 先從內(nèi)存緩存嘗試加載圖片稽荧,找不到就去磁盤緩存拿,磁盤緩存拿不到就去網(wǎng)絡(luò)拿
* 這個(gè)方法不能再線程執(zhí)行工腋,在主線程執(zhí)行就拋異常(有一個(gè)檢查當(dāng)前線程的Looper是否為主線程的Looper的判斷)
*/
public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
return bitmap;
}
bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null && !mIsDiskLruCacheCreated) {
Log.w(TAG, "encounter error, DiskLruCache is not created.");
bitmap = downloadBitmapFromUrl(uri);
}
return bitmap;
}
6姨丈、異步接口的設(shè)計(jì)
/**
* 異步接口
* load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
* NOTE THAT: should run in UI Thread
* @param uri http url
* @param imageView bitmap's bind object
*/
public void bindBitmap(final String uri, final ImageView imageView) {
bindBitmap(uri, imageView, 0, 0);
}
/**
* 異步接口
* @param uri
* @param imageView
* @param reqWidth
* @param reqHeight
*
* 實(shí)現(xiàn)過程:
* 首先先去內(nèi)存緩存讀取圖片
* 如果讀取就直接返回結(jié)果
*
* 如果讀取不到就調(diào)用loadBitmap方法,當(dāng)圖片加載成功后再將圖片擅腰,圖片的地址以及需要綁定的imageView封裝成
* 一個(gè)LoaderResult對(duì)象蟋恬,然后再通過mHMainandler向主線程發(fā)送一條消息,這樣就可以在imageView中設(shè)置圖片了
* 之所以通過Handler中轉(zhuǎn)是因?yàn)樽泳€程無法直接更新UI
*
* bindBitmap中用到了線程池和Handler
*
*
*/
public void bindBitmap(final String uri, final ImageView imageView,
final int reqWidth, final int reqHeight) {
imageView.setTag(TAG_KEY_URI, uri);
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
if (bitmap != null) {
LoaderResult result = new LoaderResult(imageView, uri, bitmap);
mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
7趁冈、解決View的復(fù)用錯(cuò)位
每次設(shè)置圖片之前都檢查他的url有沒有改變歼争,如果改變就不再給其設(shè)置圖片,這樣就解決了圖片錯(cuò)誤的問題
private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
imageView.setImageBitmap(result.bitmap);
String uri = (String) imageView.getTag(TAG_KEY_URI);
if (uri.equals(result.uri)) {
imageView.setImageBitmap(result.bitmap);
} else {
Log.w(TAG, "set image bitmap,but url has changed, ignored!");
}
};
};
完整代碼
ImageLoader完整代碼
public class ImageLoader {
private static final String TAG = "ImageLoader";
public static final int MESSAGE_POST_RESULT = 1;
private static final int CPU_COUNT = Runtime.getRuntime()
.availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10L;
private static final int TAG_KEY_URI = R.id.imageloader_uri;
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
private static final int IO_BUFFER_SIZE = 8 * 1024;
private static final int DISK_CACHE_INDEX = 0;
private boolean mIsDiskLruCacheCreated = false;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
}
};
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), sThreadFactory);
private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
imageView.setImageBitmap(result.bitmap);
String uri = (String) imageView.getTag(TAG_KEY_URI);
if (uri.equals(result.uri)) {
imageView.setImageBitmap(result.bitmap);
} else {
Log.w(TAG, "set image bitmap,but url has changed, ignored!");
}
};
};
private Context mContext;
private ImageResizer mImageResizer = new ImageResizer();
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private ImageLoader(Context context) {
mContext = context.getApplicationContext();
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* build a new instance of ImageLoader
* @param context
* @return a new instance of ImageLoader
*/
public static ImageLoader build(Context context) {
return new ImageLoader(context);
}
// 內(nèi)存緩存的添加
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 內(nèi)存緩存的讀取
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
/**
* 異步接口
* load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
* NOTE THAT: should run in UI Thread
* @param uri http url
* @param imageView bitmap's bind object
*/
public void bindBitmap(final String uri, final ImageView imageView) {
bindBitmap(uri, imageView, 0, 0);
}
/**
* 異步接口
* @param uri
* @param imageView
* @param reqWidth
* @param reqHeight
*
* 實(shí)現(xiàn)過程:
* 首先先去內(nèi)存緩存讀取圖片
* 如果讀取就直接返回結(jié)果
*
* 如果讀取不到就調(diào)用loadBitmap方法渗勘,當(dāng)圖片加載成功后再將圖片沐绒,圖片的地址以及需要綁定的imageView封裝成
* 一個(gè)LoaderResult對(duì)象,然后再通過mHMainandler向主線程發(fā)送一條消息旺坠,這樣就可以在imageView中設(shè)置圖片了
* 之所以通過Handler中轉(zhuǎn)是因?yàn)樽泳€程無法直接更新UI
*
* bindBitmap中用到了線程池和Handler
*
*
*/
public void bindBitmap(final String uri, final ImageView imageView,
final int reqWidth, final int reqHeight) {
imageView.setTag(TAG_KEY_URI, uri);
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
if (bitmap != null) {
LoaderResult result = new LoaderResult(imageView, uri, bitmap);
mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
/**
*
* 同步加載 (從內(nèi)存緩存乔遮、磁盤緩存、網(wǎng)絡(luò))
* @param uri http url
* @param reqWidth the width ImageView desired
* @param reqHeight the height ImageView desired
* @return bitmap, maybe null.
*
* 同步加載的設(shè)計(jì)步驟:
* 先從內(nèi)存緩存嘗試加載圖片价淌,找不到就去磁盤緩存拿申眼,磁盤緩存拿不到就去網(wǎng)絡(luò)拿
* 這個(gè)方法不能再線程執(zhí)行,在主線程執(zhí)行就拋異常(有一個(gè)檢查當(dāng)前線程的Looper是否為主線程的Looper的判斷)
*/
public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
return bitmap;
}
bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null && !mIsDiskLruCacheCreated) {
Log.w(TAG, "encounter error, DiskLruCache is not created.");
bitmap = downloadBitmapFromUrl(uri);
}
return bitmap;
}
private Bitmap loadBitmapFromMemCache(String url) {
final String key = hashKeyFormUrl(url);
Bitmap bitmap = getBitmapFromMemCache(key);
return bitmap;
}
/**
* 磁盤緩存的添加
* @param url
* @param reqWidth
* @param reqHeight
* @return
* @throws IOException
*
*
* 磁盤緩存的添加:添加需要通過Editor來完成蝉衣,利用commit和abort方法來 提交 和 撤銷 操作
*/
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit network from UI Thread.");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (downloadUrlToStream(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
/**
* 磁盤緩存的讀取
* @param url
* @param reqWidth
* @param reqHeight
* @return
* @throws IOException
* 讀壤ㄊ:需要通過Snapshot來完成,通過Snapshot可以得到磁盤緩存對(duì)象對(duì)應(yīng)的FileInputStream
* 但是FileInputStream無法很好地進(jìn)行壓縮
* 所以我們通過 FileDescripot 來加載壓縮后的圖片病毡,得到加載后的Bitmap添加到內(nèi)存緩存中
*/
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
int reqHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
}
if (mDiskLruCache == null) {
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
return bitmap;
}
public boolean downloadUrlToStream(String urlString,
OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),
IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (IOException e) {
Log.e(TAG, "downloadBitmap failed." + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
private Bitmap downloadBitmapFromUrl(String urlString) {
Bitmap bitmap = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(),
IO_BUFFER_SIZE);
bitmap = BitmapFactory.decodeStream(in);
} catch (final IOException e) {
Log.e(TAG, "Error in downloadBitmap: " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
MyUtils.close(in);
}
return bitmap;
}
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
public File getDiskCacheDir(Context context, String uniqueName) {
boolean externalStorageAvailable = Environment
.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if (externalStorageAvailable) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
@TargetApi(VERSION_CODES.GINGERBREAD)
private long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
private static class LoaderResult {
public ImageView imageView;
public String uri;
public Bitmap bitmap;
public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
this.imageView = imageView;
this.uri = uri;
this.bitmap = bitmap;
}
}
}
附上DiskLruCache源碼
DiskLruCache源碼參考地址
說明濒翻,本文主要參考《Android開發(fā)藝術(shù)探索》