android中圖片的三級cache策略(內存、文件、網絡) 一

1. 簡介java

如今android應用中不可避免的要使用圖片,有些圖片是能夠變化的,須要每次啓動時從網絡拉取,這種場景在有廣告位的應用以及純圖片應用(好比百度美拍)中比較多。android

如今有一個問題:假如每次啓動的時候都從網絡拉取圖片的話,勢必會消耗不少流量。在當前的情況下,對於非wifi用戶來講,流量仍是很貴的,一個很耗流量的應用,其用戶數量級確定要受到影響。固然,我想,向百度美拍這樣的應用,必然也有其內部的圖片緩存策略。總之,圖片緩存是很重要並且是必須的。canvas

 

2.圖片緩存的原理緩存

實現圖片緩存也不難,須要有相應的cache策略。這裏我採用 內存-文件-網絡 三層cache機制,其中內存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網絡不算cache,這裏姑且也把它劃到緩存的層次結構中。當根據url向網絡拉取圖片的時候,先從內存中找,若是內存中沒有,再從緩存文件中查找,若是緩存文件中也沒有,再從網絡上經過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。因此,按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。網絡


關於Java中對象的軟引用(SoftReference),若是一個對象具備軟引用,內存空間足夠,垃 圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高 速緩存。使用軟引用能防止內存泄露,加強程序的健壯性。  多線程


從代碼上來講,採用一個ImageManager來負責圖片的管理和緩存,函數接口爲public void loadBitmap(String url, Handler handler) ;其中url爲要下載的圖片地址,handler爲圖片下載成功後的回調,在handler中處理message,而message中包含了圖片的信息以及bitmap對象。ImageManager中使用的ImageMemoryCache(內存緩存)、ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會在後續文章中介紹。異步


3.代碼ImageManager.javaide

 

/*
 * 圖片管理
 * 異步獲取圖片,直接調用loadImage()函數,該函數本身判斷是從緩存仍是網絡加載
 * 同步獲取圖片,直接調用getBitmap()函數,該函數本身判斷是從緩存仍是網絡加載
 * 僅從本地獲取圖片,調用getBitmapFromNative()
 * 僅從網絡加載圖片,調用getBitmapFromHttp()
 * 
 */

public class ImageManager implements IManager
{
	private final static String TAG = "ImageManager";
	
	private ImageMemoryCache imageMemoryCache; //內存緩存
	
	private ImageFileCache   imageFileCache; //文件緩存
	
	//正在下載的image列表
	public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>();
	
	//等待下載的image列表
	public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>();
	
	//同時下載圖片的線程個數
	final static int MAX_DOWNLOAD_IMAGE_THREAD = 4;
	
	private final Handler downloadStatusHandler = new Handler(){
		public void handleMessage(Message msg)
		{
			startDownloadNext();
		}
	};
	
	public ImageManager()
	{
		imageMemoryCache = new ImageMemoryCache();
		imageFileCache = new ImageFileCache();
    }
	
    /**
     * 獲取圖片,多線程的入口
     */
    public void loadBitmap(String url, Handler handler) 
    {
        //先從內存緩存中獲取,取到直接加載
        Bitmap bitmap = getBitmapFromNative(url);
        
        if (bitmap != null)
        {
            Logger.d(TAG, "loadBitmap:loaded from native");
        	Message msg = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("url", url);
            msg.obj = bitmap;
            msg.setData(bundle);
            handler.sendMessage(msg);
        } 
        else
        {
        	Logger.d(TAG, "loadBitmap:will load by network");
        	downloadBmpOnNewThread(url, handler);
        }
    }
    
    /**
     * 新起線程下載圖片
     */
    private void downloadBmpOnNewThread(final String url, final Handler handler)
    {
		Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size());
    	
		if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD) 
		{
			synchronized (waitingTaskMap) 
			{
				waitingTaskMap.put(url, handler);
			}
		} 
		else 
		{
			synchronized (ongoingTaskMap) 
			{
				ongoingTaskMap.put(url, handler);
			}

			new Thread() 
			{
				public void run() 
				{
					Bitmap bmp = getBitmapFromHttp(url);

					// 不論下載是否成功,都從下載隊列中移除,再由業務邏輯判斷是否從新下載
					// 下載圖片使用了httpClientRequest,自己已經帶了重連機制
					synchronized (ongoingTaskMap) 
					{
						ongoingTaskMap.remove(url);
					}
					
					if(downloadStatusHandler != null)
					{
						downloadStatusHandler.sendEmptyMessage(0);
					
					}

					Message msg = Message.obtain();
					msg.obj = bmp;
					Bundle bundle = new Bundle();
					bundle.putString("url", url);
					msg.setData(bundle);
					
					if(handler != null)
					{
						handler.sendMessage(msg);
					}

				}
			}.start();
		}
	}

    
	/**
     * 依次從內存,緩存文件,網絡上加載單個bitmap,不考慮線程的問題
     */
	public Bitmap getBitmap(String url)
	{
	    // 從內存緩存中獲取圖片
	    Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url);
	    if (bitmap == null) 
	    {
	        // 文件緩存中獲取
	    	bitmap = imageFileCache.getImageFromFile(url);
	        if (bitmap != null) 
	        {	            
	        	// 添加到內存緩存
	        	imageMemoryCache.addBitmapToMemory(url, bitmap);
	        } 
	        else 
	        {
	            // 從網絡獲取
	        	bitmap = getBitmapFromHttp(url);
	        }
	    }
	    return bitmap;
	}
	
	/**
	 * 從內存或者緩存文件中獲取bitmap
	 */
	public Bitmap getBitmapFromNative(String url)
	{
		Bitmap bitmap = null;
		bitmap = imageMemoryCache.getBitmapFromMemory(url);
		
		if(bitmap == null)
		{
			bitmap = imageFileCache.getImageFromFile(url);
			if(bitmap != null)
			{
				// 添加到內存緩存
				imageMemoryCache.addBitmapToMemory(url, bitmap);
			}
		}
		return bitmap;
	}
	
	/**
	 * 經過網絡下載圖片,與線程無關
	 */
	public Bitmap getBitmapFromHttp(String url)
	{
		Bitmap bmp = null;
		
		try
		{
			byte[] tmpPicByte = getImageBytes(url);
	
			if (tmpPicByte != null) 
			{
				bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0,
						tmpPicByte.length);
			}
			tmpPicByte = null;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		if(bmp != null)
		{
			// 添加到文件緩存
			imageFileCache.saveBitmapToFile(bmp, url);
			// 添加到內存緩存
			imageMemoryCache.addBitmapToMemory(url, bmp);
		}

		return bmp;
	}
	
	/**
	 * 下載連接的圖片資源
	 * 
	 * @param url
	 *            
	 * @return 圖片
	 */
	public byte[] getImageBytes(String url) 
	{
		byte[] pic = null;
		if (url != null && !"".equals(url)) 
		{
			Requester request = RequesterFactory.getRequester(
					Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC);
			// 執行請求
			MyResponse myResponse = null;
			MyRequest mMyRequest;
			mMyRequest = new MyRequest();
			mMyRequest.setUrl(url);
			mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity");
			InputStream is = null;
			ByteArrayOutputStream baos = null;
			try {
				myResponse = request.execute(mMyRequest);
				is = myResponse.getInputStream().getImpl();
				baos = new ByteArrayOutputStream();
				byte[] b = new byte[512];
				int len = 0;
				while ((len = is.read(b)) != -1) 
				{
					baos.write(b, 0, len);
					baos.flush();
				}
				pic = baos.toByteArray();
				Logger.d(TAG, "icon bytes.length=" + pic.length);

			} 
			catch (Exception e3) 
			{
				e3.printStackTrace();
				try 
				{
					Logger.e(TAG,
							"download shortcut icon faild and responsecode="
									+ myResponse.getStatusCode());
				} 
				catch (Exception e4) 
				{
					e4.printStackTrace();
				}
			} 
			finally 
			{
				try 
				{
					if (is != null) 
					{
						is.close();
						is = null;
					}
				} 
				catch (Exception e2) 
				{
					e2.printStackTrace();
				}
				try 
				{
					if (baos != null) 
					{
						baos.close();
						baos = null;
					}
				} 
				catch (Exception e2) 
				{
					e2.printStackTrace();
				}
				try 
				{
					request.close();
				} 
				catch (Exception e1) 
				{
					e1.printStackTrace();
				}
			}
		}
		return pic;
	}
	
	/**
	 * 取出等待隊列第一個任務,開始下載
	 */
	private void startDownloadNext()
	{
		synchronized(waitingTaskMap)
		{	
			Logger.d(TAG, "begin start next");
			Iterator iter = waitingTaskMap.entrySet().iterator(); 
		
			while (iter.hasNext()) 
			{
				
				Map.Entry entry = (Map.Entry) iter.next();
				Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey());
				
				if(entry != null)
				{
					waitingTaskMap.remove(entry.getKey());
					downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue());
				}
				break;
			}
		}
	}
	
	public String startDownloadNext_ForUnitTest()
	{
		String urlString = null;
		synchronized(waitingTaskMap)
		{
			Logger.d(TAG, "begin start next");
			Iterator iter = waitingTaskMap.entrySet().iterator(); 
		
			while (iter.hasNext()) 
			{
				Map.Entry entry = (Map.Entry) iter.next();
				urlString = (String)entry.getKey();
				waitingTaskMap.remove(entry.getKey());
				break;
			}
		}
		return urlString;
	}
	
	/**
	 * 圖片變爲圓角
	 * @param bitmap:傳入的bitmap
	 * @param pixels:圓角的度數,值越大,圓角越大
	 * @return bitmap:加入圓角的bitmap
	 */
	public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) 
	{ 
        if(bitmap == null)
        	return null;
        
		Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); 
        Canvas canvas = new Canvas(output); 
 
        final int color = 0xff424242; 
        final Paint paint = new Paint(); 
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); 
        final RectF rectF = new RectF(rect); 
        final float roundPx = pixels; 
 
        paint.setAntiAlias(true); 
        canvas.drawARGB(0, 0, 0, 0); 
        paint.setColor(color); 
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint); 
 
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); 
        canvas.drawBitmap(bitmap, rect, rect, paint); 
 
        return output; 
    }
	
	public byte managerId() 
	{
		return IMAGE_ID;
	}
}
相關文章
相關標籤/搜索