Android快取機制詳解之硬碟快取DiskLruCache
阿新 • • 發佈:2018-11-01
簡介
防止多圖OOM的核心解決思路就是使用LruCache技術。但LruCache只是管理了記憶體中圖片的儲存與釋放,如果圖片從記憶體中被移除的話,那麼又需要從網路上重新載入一次圖片,這顯然非常耗時。對此,Google又提供了一套硬碟快取的解決方案:DiskLruCache(非Google官方編寫,但獲得官方認證),我們先來看一下有哪些應用程式已經使用了DiskLruCache技術,在我所接觸的應用範圍裡,Dropbox、Twitter、網易新聞等都是使用DiskLruCache來進行硬碟快取的。如果你手機上安裝了網頁新聞這個APP,當你開啟它的Cache目錄時,你會發現一個名叫journal的檔案,這個檔案通常是使DiskLruCache技術的標誌。
下載
可以點選這裡下載DiskLruCache的原始碼。下載好了原始碼之後,只需要在專案中新建一個libcore.io包,然後將DiskLruCache.java檔案複製到這個包中即可。
常用的快取位置
1.有SDCard:/sdcard/Android/data/<application package>/cache
cachePath = context.getExternalCacheDir().getPath();
2.沒有SDCard:/data/data/<applicationpackage>/cache
cachePath = context.getCacheDir().getPath();
常用方法
1. 開啟快取
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
2.寫入快取
public Editor edit(String key) throws IOException
3.讀取快取:
public synchronized Snapshot get(String key) throws IOException InputStream is = snapShot.getInputStream(0);
4. 其它API:
1. size(): 這個方法會返回當前快取路徑下所有快取資料的總位元組數,以byte為單位2. flush(): 這個方法用於將記憶體中的操作記錄同步到日誌檔案(也就是journal檔案)當中,標準的做法就是在Activity的onPause()方法中去呼叫一次flush()方法
3. close(): 這個方法用於將DiskLruCache關閉掉,通常只應該在Activity的onDestroy()方法中去呼叫close()方法
4. delete():這個方法用於將所有的快取資料全部刪除
解讀journal
1. journal檔案頭 格式
2. dirty -->呼叫一次DiskLruCache的edit()方法,都會向journal檔案中寫入一條DIRTY記錄
3. clean -->呼叫一次DiskLruCache的commit()方法,表示寫入快取成功。後面會跟檔案的大小
4. remove-->呼叫abort()方法表示寫入快取失敗
5. read -->呼叫get()方法去讀取一條快取資料時會呼叫
/**
* GridView的介面卡,負責非同步從網路上下載圖片展示在照片牆上。
*/
public class PhotoWallAdapter extends ArrayAdapter<String> {
private Set<BitmapWorkerTask> taskCollection;
/**
* 圖片快取技術的核心類,用於快取所有下載好的圖片,在程式記憶體達到設定值時會將最少最近使用的圖片移除掉。
*/
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private GridView mPhotoWall;
private int mItemHeight = 0;
public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects, GridView photoWall) {
super(context, textViewResourceId, objects);
mPhotoWall = photoWall;
taskCollection = new HashSet<BitmapWorkerTask>();
/**
* LruCache的使用
*/
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
try {
// 獲取圖片快取路徑
File cacheDir = getDiskCacheDir(context, "thumb");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 建立DiskLruCache例項,初始化快取資料
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
} else {
view = convertView;
}
final ImageView imageView = (ImageView) view.findViewById(R.id.photo);
if (imageView.getLayoutParams().height != mItemHeight) {
imageView.getLayoutParams().height = mItemHeight;
}
// 給ImageView設定一個Tag,保證非同步載入圖片時不會亂序
imageView.setTag(url);
imageView.setImageResource(R.drawable.empty_photo);
loadBitmaps(imageView, url);
return view;
}
/**
* 將一張圖片儲存到LruCache中。
*
* @param key
* LruCache的鍵,這裡傳入圖片的URL地址。
* @param bitmap
* LruCache的鍵,這裡傳入從網路上下載的Bitmap物件。
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 從LruCache中獲取一張圖片,如果不存在就返回null。
*
* @param key
* LruCache的鍵,這裡傳入圖片的URL地址。
* @return 對應傳入鍵的Bitmap物件,或者null。
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 載入Bitmap物件。此方法會在LruCache中檢查所有螢幕中可見的ImageView的Bitmap物件,
* 如果發現任何一個ImageView的Bitmap物件不在快取中,就會開啟非同步執行緒去下載圖片。
*/
public void loadBitmaps(ImageView imageView, String imageUrl) {
try {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(imageUrl);
} else {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 取消所有正在下載或等待下載的任務。
*/
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
/**
* 根據傳入的uniqueName獲取硬碟快取的路徑地址。
*/
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 設定item子項的高度。
*/
public void setItemHeight(int height) {
if (height == mItemHeight) {
return;
}
mItemHeight = height;
notifyDataSetChanged();
}
/**
* 使用MD5演算法對傳入的key進行加密並返回。
*/
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* 將快取記錄同步到journal檔案中。
*/
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
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();
}
/**
* 非同步下載圖片的任務。
*
*/
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
Snapshot snapShot = null;
try {
// 生成圖片URL對應的key
final String key = hashKeyForDisk(imageUrl);
// 查詢key對應的快取
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果沒有找到對應的快取,則準備從網路上請求資料,並寫入快取
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
// 快取被寫入後,再次查詢key對應的快取
snapShot = mDiskLruCache.get(key);
}
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
// 將快取資料解析成Bitmap物件
Bitmap bitmap = null;
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if (bitmap != null) {
// 將Bitmap物件新增到記憶體快取當中
addBitmapToMemoryCache(params[0], bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 根據Tag找到相應的ImageView控制元件,將下載好的圖片顯示出來。
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
taskCollection.remove(this);
}
/**
* 建立HTTP請求,並獲取Bitmap物件。
*
* @param imageUrl
* 圖片的URL地址
* @return 解析後的Bitmap物件
*/
private 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(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
}
}
詳細步驟:
1.在Adapter的getView方法中獲取圖片的url2.建立一個HashSet把所有的非同步任務放入集合中
3.開啟非同步任務,傳入url,執行非同步任務。
4.使用MD5加密演算法生成URL對應的key,呼叫DiskLruCache.get(key)方法查詢對應的快取,返回Snapshot物件
5.如果Snapshot物件為null,呼叫mDiskLruCache.edit(key).editor.newOutputStream(0)建立容器,下載圖片放入容器,成功呼叫commit(),失敗呼叫abort()
6.如果Snapshot物件不為null,將快取的資料解析成Bitmap,然後將Bitmap新增到快取中,LruCache<String, Bitmap> mMemoryCache.put(key,value)
7.根據tag獲取imageview,呼叫imageView.setImageBitmap(bitmap),ok