前言
在Android開發中,如果圖片過多,而我們又沒有對圖片進行有效的緩存,就很容易導致OOM(Out Of Memory)錯誤。因此,圖片的緩存是非常重要的,尤其是對圖片非常多的應用。現在很多框架都做了很好的圖片緩存處理。
一直想寫一個自己的圖片緩存框架,之前一直用Glide、Fresco等一些主流的圖片框架。這些框架對處理的處理都做的非常好,也查閱了這些框架的一些源碼,整體思路是使用三級緩存策略,通過網絡、本地、內存三級緩存圖片,來減少不必要的網絡交互,避免浪費流量。
什麼是三級緩存
- 網絡緩存, 不優先加載, 速度慢,浪費流量
- 本地緩存, 次優先加載, 速度快
- 內存緩存, 優先加載, 速度最快(包括強引用(LruCache)和軟引用(SoftReference))
三級緩存原理
加載圖片時,判斷內存緩存中是否有該圖片,沒有則到本地SD卡中查找,SD卡中沒有則通過網絡請求獲取圖片。
因爲Android內存空間是很珍貴的,我們不能發過多的圖片在緩存中,容易導致OOM,這個時候我們就要使用軟引用(SoftReference)。軟引用的一個好處是當系統空間緊張的時候,軟引用可以隨時銷燬,因此軟引用是不會影響系統運行的,換句話說,如果系統因爲某個原因OOM了,那麼這個原因肯定不是軟引用引起的。
LruCache創建LruCache時,我們需要設置它的大小,一般是系統最大存儲空間的八分之一。LruCache的機制是存儲最近、最後使用的圖片,如果LruCache中的圖片大小超過了其默認大小,則會將最老、最遠使用的圖片移除出去。當圖片被LruCache移除的時候,我們需要手動將這張圖片添加到軟引用(SoftReference)中。我們需要在項目中維護一個由SoftReference組成的集合,其中存儲被LruCache移除出來的圖片
流程
實現
1、自定義圖片加載工具類(VImageUtils)
通過 VImageUtils.disPlay(ImageView ivPic, String url)提供給外部方法進行圖片加載。
/**
* 自定義圖片緩存工具類
*
* @author Veer
* @email [email protected]
* @date 18/12/11
*/
public class VImageUtils {
private static final String TAG = "VImageUtils";
public static void disPlay(ImageView ivPic, String url) {
Bitmap bitmap;
//內存緩存
bitmap=MemoryCacheUtils.getInstance().getBitmapFromMemory(url);
if (bitmap!=null){
ivPic.setImageBitmap(bitmap);
Log.d(TAG,"從內存獲取圖片啦.....");
return;
}
//本地緩存
bitmap = LocalCacheUtils.getInstance().getBitmapFromLocal(url);
if(bitmap !=null){
ivPic.setImageBitmap(bitmap);
Log.d(TAG,"從本地獲取圖片啦.....");
//從本地獲取圖片後,保存至內存中
MemoryCacheUtils.getInstance().setBitmapToMemory(url,bitmap);
return;
}
//網絡緩存
NetCacheUtils.getInstance().getBitmapFromNet(ivPic,url);
}
}
2、網絡緩存(NetCacheUtils)
- 網絡緩存中主要用到了AsyncTask來進行異步數據的加載
- 簡單來說,AsyncTask可以看作是一個對handler和線程池的封裝,通常,AsyncTask主要用於數據簡單時,handler+thread主要用於數據量多且複雜時,當然這也不是必須的,仁者見仁智者見智。
- 同時,爲了避免內存溢出的問題,我們可以在獲取網絡圖片後。對其進行圖片壓縮。
/**
* 網絡緩存
*
* @author Veer
* @email [email protected]
* @date 18/12/11
*/
public class NetCacheUtils {
private static NetCacheUtils mInstance;
private static final String TAG = "NetCacheUtils";
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
private NetCacheUtils() {
mMemoryCacheUtils = MemoryCacheUtils.getInstance();
mLocalCacheUtils = LocalCacheUtils.getInstance();
}
public static NetCacheUtils getInstance(){
if(mInstance == null){
synchronized (NetCacheUtils.class) {
if (mInstance == null) {
mInstance = new NetCacheUtils();
}
}
}
return mInstance;
}
/**
* 從網絡下載圖片
* @param ivPic 顯示圖片的imageview
* @param url 下載圖片的網絡地址
*/
public void getBitmapFromNet(ImageView ivPic, String url) {
new BitmapTask().execute(ivPic, url);//啓動AsyncTask
}
/**
* AsyncTask就是對handler和線程池的封裝
* 第一個泛型:參數類型
* 第二個泛型:更新進度的泛型
* 第三個泛型:onPostExecute的返回結果
*/
class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
private ImageView ivPic;
private String url;
/**
* 後臺耗時操作,存在於子線程中
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(Object[] params) {
ivPic = (ImageView) params[0];
url = (String) params[1];
return downLoadBitmap(url);
}
/**
* 更新進度,在主線程中
* @param values
*/
@Override
protected void onProgressUpdate(Void[] values) {
super.onProgressUpdate(values);
}
/**
* 耗時方法結束後執行該方法,主線程中
* @param result
*/
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
ivPic.setImageBitmap(result);
Log.d(TAG,"從網絡緩存圖片啦.....");
//從網絡獲取圖片後,保存至本地緩存
mLocalCacheUtils.setBitmapToLocal(url, result);
//保存至內存中
mMemoryCacheUtils.setBitmapToMemory(url, result);
}
}
}
/**
* 網絡下載圖片
* @param url
* @return
*/
private Bitmap downLoadBitmap(String url) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
//圖片壓縮
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize=2;//寬高壓縮爲原來的1/2
options.inPreferredConfig=Bitmap.Config.ARGB_4444;
Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(),null,options);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return null;
}
}
3、本地緩存(LocalCacheUtils)
- 在初次通過網絡獲取圖片後,我們可以在本地SD卡中將圖片保存起來
- 可以使用MD5加密圖片的網絡地址,來作爲圖片的名稱保存
/**
* 本地緩存
*
* @author Veer
* @email [email protected]
* @date 18/12/11
*/
public class LocalCacheUtils {
private static final String CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VImageUtils";
private static LocalCacheUtils mInstance;
private LocalCacheUtils(){
}
public static LocalCacheUtils getInstance(){
if(mInstance == null){
synchronized (LocalCacheUtils.class) {
if (mInstance == null) {
mInstance = new LocalCacheUtils();
}
}
}
return mInstance;
}
/**
* 從本地讀取圖片
* @param url
* @return Bitmap
*/
public Bitmap getBitmapFromLocal(String url) {
String fileName = null;//把圖片的url當做文件名,並進行MD5加密
try {
fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 從網絡獲取圖片後,保存至本地緩存
* @param url
* @param bitmap
*/
public void setBitmapToLocal(String url, Bitmap bitmap) {
try {
String fileName = MD5Encoder.encode(url);//把圖片的url當做文件名,並進行MD5加密
File file = new File(CACHE_PATH, fileName);
//通過得到文件的父文件,判斷父文件是否存在
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
//把圖片保存至本地
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、內存緩存(MemoryCacheUtils)
- 這塊東西比較重要,也是圖片緩存的關鍵一步
- 通過LruCache<String,Bitmap> 來緩存我們的圖片,由於我們LruCache的空間有限,我們把剩餘的圖片放入
Map<String, SoftReference<Bitmap>> 軟引用中,這樣不會造成內存泄露。
- 在向緩存中取圖片時,通過最少最近使用算法,先從LruCache中查找,在LruCache中沒有再從SoftReference軟引用中查找。在SoftReference查到然後重新放入LruCache中。
/**
* 內存緩存
*
* @author Veer
* @email [email protected]
* @date 18/12/11
*/
public class MemoryCacheUtils {
private static MemoryCacheUtils mInstance;
private ImageCache mImageCache;
private MemoryCacheUtils(){
Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
mImageCache = new ImageCache(cacheMap);
}
public static MemoryCacheUtils getInstance(){
if(mInstance == null){
synchronized (MemoryCacheUtils.class) {
if (mInstance == null) {
mInstance = new MemoryCacheUtils();
}
}
}
return mInstance;
}
/**
* 從內存中讀圖片
* @param url
*/
public Bitmap getBitmapFromMemory(String url) {
Bitmap bitmap = mImageCache.get(url);
// 如果圖片不存在強引用中,則去軟引用(SoftReference)中查找
if(bitmap == null){
Map<String, SoftReference<Bitmap>> cacheMap = mImageCache.getCacheMap();
SoftReference<Bitmap> softReference = cacheMap.get(url);
if(softReference!=null){
bitmap = softReference.get();
//重新放入強引用緩存中
mImageCache.put(url,bitmap);
}
}
return bitmap;
}
/**
* 往內存中寫圖片
* @param url
* @param bitmap
*/
public void setBitmapToMemory(String url, Bitmap bitmap) {
mImageCache.put(url,bitmap);
}
}
ImageCache代碼:
/**
* 圖片緩存
*
* @author Veer
* @email [email protected]
* @date 18/12/11
*/
public class ImageCache extends LruCache<String,Bitmap> {
private Map<String, SoftReference<Bitmap>> cacheMap;
public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
super((int) (Runtime.getRuntime().maxMemory() / 8));
this.cacheMap = cacheMap;
}
@Override // 獲取圖片大小
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
// 當有圖片從LruCache中移除時,將其放進軟引用集合中
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
cacheMap.put(key, softReference);
}
}
public Map<String, SoftReference<Bitmap>> getCacheMap() {
return cacheMap;
}
}
總結
以上就是圖片緩存的策略,後期我會完善我的這個圖片框架,爭取做到向那些主流框架一樣。
會一直在我的github上更新,希望多多關注