Android效能最佳化之實現雙緩衝的圖片非同步載入工具(LruCache+SoftReference),
之前在郭大神的部落格看到使用LruCache演算法實現圖片緩衝的.這裡仿效他的思路,自己也寫了一個. 並加入ConcurrentHashMap<String, SoftReference<Bitmap>>去實現二級緩衝,因為ConcurrentHashMap是多個鎖的安全執行緒,支援高並發.很適合這種頻繁訪問讀取記憶體的操作.
下面整個思路是,使用了系統提供的LruCache類做一級緩衝, 大小為運行記憶體的1/8,當LruCache容量要滿的時候,會自動將系統移除的圖片放到二級緩衝中,但為了避免OOM的問題,這裡將SoftReference軟引用加入來,當系統快要OOM的時候會自動清除裡面的圖片記憶體,當然記憶體充足時就會繼續儲存這些二級緩衝的圖片.強調一點,不要用SoftReference去做一級緩衝,現在的java中記憶體回收加強了對SoftReference軟引用的回收機制,它只適合臨時的儲存一些資料緩衝,並不適合長期的(相對臨時而言,並不是真正的長期).
直接上代碼,拿來即用哦:
/** * Created on 3/11/2015 * <br>圖片非同步載入工具(支援本地圖片載入,網狀圖片URL和項目內圖片資源載入) * <br>支援雙緩衝: LruCache和SoftReference * @author Mr.Et * */public class ImageLoadManager {/** 圖片源類型: 檔案,網路,資源ID **/public enum IMAGE_LOAD_TYPE{FILE_PATH,FILE_URL,FILE_RESOURCE_ID}private String TAG = "ImageLoadManager...";private Context context;private Set<ImageLoadTask> taskCollection;/** 最大記憶體 **/final static int maxCacheSize = (int)(Runtime.getRuntime().maxMemory() / 8);/** 建立安全執行緒,支援高並發的容器 **/private static ConcurrentHashMap<String, SoftReference<Bitmap>> currentHashmap= new ConcurrentHashMap<String, SoftReference<Bitmap>>();public ImageLoadManager(Context context){super();this.context = context;taskCollection = new HashSet<ImageLoadManager.ImageLoadTask>();}private static LruCache<String, Bitmap> BitmapMemoryCache = new LruCache<String, Bitmap>(maxCacheSize){@Overrideprotected int sizeOf(String key, Bitmap value){if(value != null){return value.getByteCount();//return value.getRowBytes() * value.getHeight();//舊版本的方法}else{return 0;}}//這個方法當LruCache的記憶體容量滿的時候會調用,將oldValue的元素移除出來騰出空間給新的元素加入@Overrideprotected void entryRemoved(boolean evicted, String key,Bitmap oldValue, Bitmap newValue){if(oldValue != null){// 當硬引用緩衝容量已滿時,會使用LRU演算法將最近沒有被使用的圖片轉入軟引用緩衝 currentHashmap.put(key, new SoftReference<Bitmap>(oldValue));}}};/** * 針對提供圖片資源ID來顯示圖片的方法 * @param loadType圖片載入類型 * @param imageResourceID圖片資源id * @param imageView顯示圖片的ImageView */public void setImageView(IMAGE_LOAD_TYPE loadType, int imageResourceID, ImageView imageView){if(loadType == IMAGE_LOAD_TYPE.FILE_RESOURCE_ID){//if(ifResourceIdExist(imageResourceID))//{//imageView.setImageResource(imageResourceID);////}else{//映射無法擷取該圖片,則顯示預設圖片//imageView.setImageResource(R.drawable.pic_default);//}try {imageView.setImageResource(imageResourceID);return;} catch (Exception e) {Log.e(TAG, "Can find the imageID of "+imageResourceID);e.printStackTrace();}//預設圖片imageView.setImageResource(R.drawable.pic_default);}}/** * 針對提供圖片檔案連結或下載連結來顯示圖片的方法 * @param loadType 圖片載入類型 * @param imageFilePath圖片檔案的本地檔案地址或網路URL的下載連結 * @param imageView顯示圖片的ImageView */public void setImageView(IMAGE_LOAD_TYPE loadType, String imageFilePath, ImageView imageView){if(imageFilePath == null || imageFilePath.trim().equals("")){imageView.setImageResource(R.drawable.pic_default);}else{Bitmap bitmap = getBitmapFromMemoryCache(imageFilePath);if(bitmap != null){imageView.setImageBitmap(bitmap);}else{imageView.setImageResource(R.drawable.pic_default);ImageLoadTask task = new ImageLoadTask(loadType, imageView);taskCollection.add(task);task.execute(imageFilePath);}}}/** * 從LruCache中擷取一張圖片,如果不存在就返回null * @param key 索引值可以是圖片檔案的filePath,可以是圖片URL地址 * @return Bitmap對象,或者null */public Bitmap getBitmapFromMemoryCache(String key){try {if(BitmapMemoryCache.get(key) == null){if(currentHashmap.get(key) != null){return currentHashmap.get(key).get();}}return BitmapMemoryCache.get(key);} catch (Exception e) {e.printStackTrace();}return BitmapMemoryCache.get(key);}/** * 將圖片放入緩衝 * @param key * @param bitmap */private void addBitmapToCache(String key, Bitmap bitmap){BitmapMemoryCache.put(key, bitmap);}/** * 圖片非同步載入 * @author Mr.Et * */private class ImageLoadTask extends AsyncTask<String, Void, Bitmap>{private String imagePath;private ImageView imageView;private IMAGE_LOAD_TYPE loadType;public ImageLoadTask(IMAGE_LOAD_TYPE loadType , ImageView imageView){this.loadType = loadType;this.imageView = imageView;}@Overrideprotected Bitmap doInBackground(String...params){imagePath = params[0];try {if(loadType == IMAGE_LOAD_TYPE.FILE_PATH){if(new File(imagePath).exists()){//從本地FILE讀取圖片BitmapFactory.Options opts = new BitmapFactory.Options();opts.inSampleSize = 2;Bitmap bitmap = BitmapFactory.decodeFile(imagePath, opts);//將擷取的新圖片放入緩衝addBitmapToCache(imagePath, bitmap);return bitmap;}return null;}else if(loadType == IMAGE_LOAD_TYPE.FILE_URL){//從網路下載圖片byte[] datas = getBytesOfBitMap(imagePath);if(datas != null){//BitmapFactory.Options opts = new BitmapFactory.Options();//opts.inSampleSize = 2;//Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, opts);Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length);addBitmapToCache(imagePath, bitmap);return bitmap;}return null;}} catch (Exception e) {e.printStackTrace();FileUtils.saveExceptionLog(e);//可自訂其他動作}return null;}@Overrideprotected void onPostExecute(Bitmap bitmap){try {if(imageView != null){if(bitmap != null){imageView.setImageBitmap(bitmap);}else{Log.e(TAG, "The bitmap result is null...");}}else{Log.e(TAG, "The imageView is null...");//擷取圖片失敗時顯示預設圖片imageView.setImageResource(R.drawable.pic_default);}} catch (Exception e) {e.printStackTrace();FileUtils.saveExceptionLog(e);}}}/** * InputStream轉byte[] * @param inStream * @return * @throws Exception */private byte[] readStream(InputStream inStream) throws Exception{ ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int len = 0; while( (len=inStream.read(buffer)) != -1){ outStream.write(buffer, 0, len); } outStream.close(); inStream.close(); return outStream.toByteArray(); }/** * 擷取下載圖片並轉為byte[] * @param urlStr * @return */private byte[] getBytesOfBitMap(String imgUrl){try {URL url = new URL(imgUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(10 * 1000); //10sconn.setReadTimeout(20 * 1000); conn.setRequestMethod("GET"); conn.connect(); InputStream in = conn.getInputStream(); return readStream(in);} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}return null;}/** * 該資源ID是否有效 * @param resourceId 資源ID * @return */private boolean ifResourceIdExist(int resourceId){try {Field field = R.drawable.class.getField(String.valueOf(resourceId));Integer.parseInt(field.get(null).toString());return true;} catch (Exception e) {e.printStackTrace();} return false;}/** * 取消所有任務 */public void cancelAllTask(){if(taskCollection != null){for(ImageLoadTask task : taskCollection){task.cancel(false);}}}}
In addition, 如果需要更加完美的體驗,還可以加入第三級的緩衝機制, 比如將圖片緩衝到本地的磁碟儲存空間中.但是又不想這些緩衝在本地的圖片被其他應用掃描到或者被使用者看到怎麼辦? 這裡有幾個思路, 比如將圖片用密碼編譯演算法轉為字串儲存,或者將圖片轉為自訂格式的未知檔案去放在隱形地方(很多應用都採取了這種方式). 這個不妨自己去嘗試實現哦~