Android中圖片的三級快取詳解
圖片的三級快取機制一般是指應用載入圖片的時候,分別去訪問內容,檔案,網路獲取圖片的一種行為。
一、三級快取流程圖
三級快取流程圖
二、程式碼框架搭建
- 這裡我仿造 Picasso 的載入圖片程式碼,也做出了with,load,into等方法。
2.1 with(context)
-
這個方法傳入上下文,返回ImageManager物件。
/** * 初始化物件 * * @param context * @return */ public static ImageManager with(Context context) { mContext = context; return
-
因為ImageManager會不斷的呼叫,所以要做成單利。
/** * 獲取物件的單利 * * @return */ private static ImageManager instance; private static ImageManager getInstance() { if (instance == null) { instance = new ImageManager(); } return instance; }
2.2 load(url)
-
這個方法返回一個自定義的RequestCreator內部類,對圖片的操作都在這個內部類中進行。
/** * 載入圖片的url地址,返回RequestCreator物件 * * @param url * @return */ public RequestCreator load(String url) { return new RequestCreator(url); }
-
RequestCreator的構造方法中接收傳入的url 。
// 初始化圖片的url地址 public RequestCreator(String url) { this.url = url; }
2.3 into(imageview)
- 這是RequestCreator類的方法,也是工具類的核心方法,這個方法裡進行圖片的三個快取處理。
三、記憶體快取
3.1 Java中物件的四種引用型別介紹
-
強引用
-
Java中所有new出來的物件都是強引用型別,回收的時候,GC寧願丟擲OOM異常,也不回收它。
Map<String, Bitmap> mImageCache = new HashMap<>();
-
-
軟引用,SoftReference
-
記憶體足夠時,不回收。記憶體不夠時,就回收。這裡使用這種方式快取物件。
Map<String, SoftReference<Bitmap>> mImageCache = new HashMap<>();
-
-
弱引用,WeakReference
- GC一出來工作就回收它。
-
虛引用,PhantomReference
- 用完就消失。
3.2 使用LruCache類來做快取
-
LruCache其實是一個Hash表,內部使用的是LinkedHashMap儲存資料。使用LruCache類可以規定快取記憶體的大小,並且這個類內部使用到了最近最少使用演算法來管理快取記憶體。
LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
3.3 程式碼實現記憶體快取
-
建立快取集合
/** * 記憶體儲存圖片的集合 * 使用lrucache快取圖片,這裡不能申明在方法裡,不然會被覆蓋掉 * 使用軟引用型別物件 * 4兆的大小作為快取 */ private LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
-
訪問記憶體時先從集合中取出軟引用,獲取BitMap
// 1 去記憶體之中找,有就顯示,沒有就往下走 SoftReference<Bitmap> reference = mImageCache.get(url); Bitmap cacheBitmap; if(reference != null){ cacheBitmap = reference.get(); // 有就顯示圖片 imageView.setImageBitmap(cacheBitmap); Log.d("RequestCreator:", "記憶體中有圖片顯示"); // 不往下走了 return; }
-
如果快取集合中的資料為空,就繼續往下走。
四、檔案快取
4.1 快取檔案儲存的路徑設定
-
儲存的路徑首先要考慮SD卡的快取目錄,當SD卡不存在時,就只能存到內部儲存的快取目錄了。
/** * 獲取快取路徑目錄 */ private File getCacheDir() { // 獲取儲存的資料夾路徑 File file; if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { // 有SD卡就儲存到sd卡 file = mContext.getExternalCacheDir(); } else { // 沒有就儲存到內部儲存 file = mContext.getCacheDir(); } return file; }
4.2 解析檔案生成Bitmap物件
- 儲存的檔案的名字擷取URL中的名字。
-
檔名使用Md5加密。
/** * 從檔案中獲取bitmap * * @return */ private Bitmap getBitmapFromFile() { // 從url中獲取檔名字 String fileName = url.substring(url.lastIndexOf("/") + 1); File file = new File(getCacheDir(),MD5Util.encodeMd5(fileName)); // 確保路徑沒有問題 if (file.exists() && file.length() > 0) { // 返回圖片 return BitmapFactory.decodeFile(file.getAbsolutePath()); } else { return null; } }
4.3 判斷是否有快取
-
有快取則讀取出來顯示,並且將快取存入記憶體,沒有就繼續往下走。
// 2 去本地硬碟中找,有就顯示,沒有就繼續往下走 // 將檔案轉換成bitmap物件 Bitmap diskBitmap = getBitmapFromFile(); if (diskBitmap != null) { // 本地磁碟有就顯示圖片 imageView.setImageBitmap(diskBitmap); // 儲存到記憶體中去 mImageCache.put(url, new SoftReference<Bitmap>(diskBitmap)); Log.d("RequestCreator:", "磁碟中有圖片顯示"); // 不往下走了 return; }
五、聯網載入
5.1 簡單執行緒池處理耗時的網路請求
-
建立執行緒池物件
/** * 構建出執行緒池,5條執行緒 */ private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
-
提交任務,讓RequestCreator實現Runnable介面,run方法中執行任務
// 3 聯網請求資料 // 前面兩步都沒有的話就去聯網載入資料 // 將從網路上獲取的資料放到執行緒池去執行 mExecutorService.submit(this);
5.2 聯網載入資料
-
使用HttpUrlConnection連線網路
// 子執行緒 // 處理網路請求 try { URL loadUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(2000); if (conn.getResponseCode() == 200) { InputStream is = conn.getInputStream(); // 獲取到圖片進行顯示 final Bitmap bm = BitmapFactory.decodeStream(is); mHandler.post(new Runnable() { @Override public void run() { // 主執行緒 imageView.setImageBitmap(bm); } }); Log.d("RequestCreator:", "聯網顯示圖片"); } else { // 聯網失敗,顯示失敗圖片 showError(); } } catch (Exception e) { e.printStackTrace(); // 發生異常顯示失敗圖片 showError(); }
5.3 儲存資料到記憶體和檔案
-
使用快取儲存資料
// 3.1 儲存到記憶體 mImageCache.put(url, new SoftReference<>(bm)); // 3.2 儲存到磁碟 // 從url中獲取檔名字 String fileName = url.substring(url.lastIndexOf("/") + 1); // 獲取儲存路徑 File file = new File(getCacheDir(), MD5Util.encodeMd5(fileName)); FileOutputStream os = new FileOutputStream(file); // 將圖片轉換為檔案進行儲存 bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
六、細節處理
6.1 設定佔位圖
- 介面一上來載入圖片時肯定是空白的,所以需要一張佔位圖。
-
RequestCreator類提供一個方法,將佔點陣圖片資源ID傳進來。
/** * 設定預設圖片,佔位圖片 * * @param holderResId */ public RequestCreator placeholder(int holderResId) { this.holderResId = holderResId; return this; }
-
在into方法中,讀取快取之前,就讓預設圖顯示。
// 一進來先設定佔位圖片 imageView.setImageResource(holderResId);
6.2 設定錯誤圖片
-
載入資料出現錯誤和異常都顯示錯誤圖片。
/** * 顯示錯誤圖片 */ private void showError() { mHandler.post(new Runnable() { @Override public void run() { imageView.setImageResource(errorResId); } }); }
七、使用自己封裝的小框架載入圖片
-
使用很簡單,和 Picasso 一樣,一行程式碼就搞定。
// 使用自己封裝的圖片快取工具類載入圖片 ImageManager.with(mContext).load(imgUrl).placeholder(R.drawable.ic_default).error(R.drawable.ic_error).into(ivImage);
-
效果圖