1. 程式人生 > >Android網路圖片的三級快取

Android網路圖片的三級快取

1.快取原理

實現圖片快取也不難,需要有相應的cache策略。這裡我採用記憶體-檔案-網路三層cache機制,其中記憶體快取包括強引用快取和軟引用快取(SoftReference),其實網路不算cache,這裡姑且也把它劃到快取的層次結構中。當根據url向網路拉取圖片的時候,先從記憶體中找,如果記憶體中沒有,再從快取檔案中查詢,如果快取檔案中也沒有,再從網路上通過http請求拉取圖片。在鍵值對(key-value)中,這個圖片快取的key是圖片url的hash值,value就是bitmap。所以,按照這個邏輯,只要一個url被下載過,其圖片就被快取起來了。

關於Java中物件的軟引用(SoftReference),如果一個物件具有軟引用,記憶體空間足夠,垃 圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的高 速快取。使用軟引用能防止記憶體洩露,增強程式的健壯性。  

2.程式碼

<pre name="code" class="java">/*
 * 圖片管理
 * 非同步獲取圖片,直接呼叫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;
	}
}