android中图片的三级cache策略(内存、文件、网络) 一

1. 简介

现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。

 

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.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;
	}
}

 

 

 

posted @ 2013-06-08 20:46  爱生活,爱编程  阅读(17036)  评论(0编辑  收藏  举报