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)
	{
		@Override
		protected int sizeOf(String key, Bitmap value)
		{
			if(value != null)
			{
				return value.getByteCount();
				//return value.getRowBytes() * value.getHeight();	//舊版本的方法
			}
			else
			{
				return 0;
			}
		}
		
		//這個方法當LruCache的內存容量滿的時候會調用,將oldValue的元素移除出來騰出空間給新的元素加入
		@Override
		protected 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;
		}
		
		@Override
		protected 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;
		}
		
		@Override
		protected 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);  //10s
			conn.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, 如果需要更加完美的體驗,還可以加入第三級的緩存機制, 比如將圖片緩存到本地的磁盤存儲空間中.但是又不想這些緩存在本地的圖片被其他應用掃描到或者被用戶看到怎麼辦? 這裏有幾個思路, 比如將圖片用加密算法轉爲字符串存儲,或者將圖片轉爲自定義格式的未知文件去放在隱蔽的地方(很多應用都採取了這種方式). 這個不妨自己去嘗試實現哦~




發佈了68 篇原創文章 · 獲贊 18 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章