天天看点

Android-->网络图片下载的三级缓存策略

摘要:项目中做了一个关于在线壁纸的App,其中对于网络图片的处理是至关重要的,既要考虑性能,又要考虑图片所占用内存的限制。因此这里就把网络图片下载部分的代码整理处来,这里主要介绍图片三级缓存的策略。

图片的缓存策略主要涉及到内存缓存,和磁盘缓存。这也是主流缓存策略。通过查阅资料,内存的主流缓存策略大致有以下三种:

1.      软引用

2.      LruCache(android-support-v4.jar提供的一个类)

3.      LruCache+软引用

但是在这里不太建议仅仅使用软引用,因为高版本的Android系统中,软引用变的不那么靠谱,就算是内存足够的时候也可能会将其回收,因此命中率会降低。

若单使用LruCache虽然能够满足需求,但是那些替换出来的对象就白白浪费了。

在我的项目中使用的是第三种策略,当被LruCache“遗弃”的对象将其变成软引用对象。这样可以提高命中率。

关于LruCache算法,这里不做介绍了,有兴趣的可以阅读相应源码或者参考http://blog.csdn.net/linghu_java/article/details/8574102。

磁盘缓存策略,我采用的是DiskLruCache类,该类不是Android系统提供的,不过开源库上有提供源码,我们只需要将其拷贝进入工程就行。有兴趣的也可以读读其源码,加深认识。

不管LruCache和DiskLruCache其中核心的属性是LinkedHashMap,并将LinkedHashMap设置为按照访问顺序排列。有兴趣的可以参考源码或者参考http://uule.iteye.com/blog/1522291。

有了相关知识储备,现在开始编码实现,废话不多说直接上代码。

package com.dighammer.imageloader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.R.mipmap;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.ImageView;

/**
 * @author DigHammer
 * */

public class ImageLoader {

	private static final String TAG = "Dig.ImageLoader";
	// 内存缓存
	private LruMemoryCache mMemoryCache = LruMemoryCache.getInstance();
	// 下载图片所使用的线程池
	private ExecutorService mExecutorService;
	// 文件缓存
	public static DiskLruCache sDiskLruCache;
	// SD卡分配的存储大小
	private static final int DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB
	// 单利模式
	private static ImageLoader instance = null;
	// 监听图片下载完成事件
	private OnImageDownloadedListener mListener;

	private Context mContext;

	public synchronized static ImageLoader getInstance(Context context) {
		if (instance == null) {
			instance = new ImageLoader(context);
		}
		return instance;
	}

	private ImageLoader(Context context) {
		this.mContext = context;
		// 获取图片缓存路径
		String cachePath = FileUtil.getImageCacheDirPath();
		// 实例化DiskLruCache
		if (cachePath != null) {
			File cacheDir = new File(cachePath);
			sDiskLruCache = DiskLruCache.openCache(cacheDir, DISK_CACHE_SIZE);
		}
		mExecutorService = Executors.newFixedThreadPool(2);
	}

	/** 当loadMode为false的时候,表示仅仅从内存中获取对象 */
	public void loadImage(String url, ImageView imageView, boolean loadMode) {
		Bitmap bitmap = mMemoryCache.getBitmapFromMemCache(url);
		if (bitmap != null & mListener != null) {
			mListener.onImageloaded(bitmap, imageView);
		} else if (!loadMode) {
			mExecutorService.submit(new BitmapLoader(url, imageView));
		}
	}

	/**
	 * 
	 * 加载图片
	 * */
	private Bitmap loadBitmap(String url, ImageView imageView) {
		// 首先判断SD卡缓存中是否有图片
		Bitmap b = null;
		if (FileUtil.hasSDCard()) {
			if (sDiskLruCache != null) {
				b = sDiskLruCache.get(url);
			}
		}
		if (b != null) {
			return b;
		}
		// 从网络上下载图片
		b = downloadBitmp(url, imageView);
		// 写入SD卡缓存
		if (FileUtil.hasSDCard() && FileUtil.isSDHasFree()) {
			if (sDiskLruCache != null & b != null) {
				sDiskLruCache.put(url, b);
			}
		}
		return b;
	}

	/**
	 * 
	 * 从网络下载图片
	 * **/
	private Bitmap downloadBitmp(String url, ImageView imageView) {
		Bitmap bitmap = null;
		Activity activity = (Activity) imageView.getContext();
		// 判断是否有网络连接
		if (!HttpUtil.hasInternet(mContext)) {
			activity.runOnUiThread(new InformUiThreadUpdate(
					OnImageDownloadedListener.NO_NET));
			return null;
		}
		// 解决url中文乱码问题,这个地方很重要。
		String realUrl = HttpUtil.encodeUTF8(url);
		URL imageUrl = null;
		InputStream is = null;
		HttpURLConnection conn = null;
		// 下面还要主要捕获异常,方便我们对异常情况的处理
		try {
			imageUrl = new URL(realUrl);
		} catch (MalformedURLException e) {
			if (mListener != null) {
				activity.runOnUiThread(new InformUiThreadUpdate(
						OnImageDownloadedListener.URL_FORMAT_ERROR));
			}
			return null;
		}
		try {
			conn = (HttpURLConnection) imageUrl.openConnection();
		} catch (IOException e) {
			if (mListener != null) {
				activity.runOnUiThread(new InformUiThreadUpdate(
						OnImageDownloadedListener.CONN_TIME_OUT));
			}

			return null;
		}
		// 设置连接和读取超时时间
		conn.setConnectTimeout(30000);
		conn.setReadTimeout(30000);
		// 设置支持重定向
		conn.setInstanceFollowRedirects(true);
		try {
			// 获取输入流
			is = conn.getInputStream();
		} catch (IOException e) {
			if (mListener != null) {
				activity.runOnUiThread(new InformUiThreadUpdate(
						OnImageDownloadedListener.READ_TIME_OUT));
			}
			return null;
		}
		// 将输入流转换成bitmap对象
		bitmap = BitmapFactory.decodeStream(is);
		try {
			// 关闭输入流,以免内存泄露
			is.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return bitmap;

	}

	class BitmapLoader implements Runnable {

		private String mUrl;

		private ImageView mImageView;

		public BitmapLoader(String url, ImageView imageView) {
			this.mImageView = imageView;
			this.mUrl = url;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			Bitmap bitmap = loadBitmap(mUrl, mImageView);
			if (bitmap == null) {
				return;
			}
			mMemoryCache.addBitmapToMemoryCache(mUrl, bitmap);
			Activity activity = (Activity) mImageView.getContext();
			// 主线程更新,这里不能直接调用接口对象中onImageloaded方法
			// 不然不能更新UI,因为这里不是主线程。
			activity.runOnUiThread(new InformUiThreadUpdate(bitmap, mImageView));

		}
	}

	/**
	 * 
	 * 调用接口方法,通知UI线程进行相应的操作
	 * */
	class InformUiThreadUpdate implements Runnable {

		private Bitmap mBitmap;

		private ImageView mImageView;

		private int mOperation = -1;

		public InformUiThreadUpdate(Bitmap bitmap, ImageView imageView) {
			this.mImageView = imageView;
			this.mBitmap = bitmap;
		}

		public InformUiThreadUpdate(int operation) {
			this.mOperation = operation;

		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			if (mListener != null) {
				if (mOperation == -1) {
					mListener.onImageloaded(mBitmap, mImageView);

				} else {
					mListener.onDowndloadFailure(mOperation);
				}

			}
		}
	}

	public void setOnImageDownloadListener(OnImageDownloadedListener listener) {
		this.mListener = listener;
	}

	/** 下载接口监听处理 */
	public interface OnImageDownloadedListener {

		public static final int NO_NET = 0;

		public static final int URL_FORMAT_ERROR = 1;

		public static final int CONN_TIME_OUT = 2;

		public static final int READ_TIME_OUT = 3;

		void onImageloaded(Bitmap b, ImageView imageView);

		void onDowndloadFailure(int type);
	}

}
           

接着介绍LruCache+软引用的内存缓存策略,代码如下:

package com.dighammer.imageloader;

import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;

/**
 * @author DigHammer
 * */
public class LruMemoryCache {

	private static final String TAG = "LruMemoryCache";
    /**向内存申请8M的空间,这里可以自己定义,也可以
     * 通过获取系统分配给应用的最大内存然后取其1/4*/
	private int mCacheSize = 8 * 1024 * 1024;
	/** Android 系统提供的LRUCache类 */
	private LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(
			mCacheSize) {
		// 必须重写此方法,来测量Bitmap的大小
		@Override
		protected int sizeOf(String key, Bitmap value) {
			return value.getRowBytes() * value.getHeight();
		}

		@Override
		protected void entryRemoved(boolean evicted, String key,
				Bitmap oldValue, Bitmap newValue) {
			// 当对象移除的时候,将其添加到软引用中
			softMemoryCache.put(key, new SoftReference<Bitmap>(oldValue));
		}

	};

	private static final int SOFT_CACHE_CAPACITY = 16;

	private static LinkedHashMap<String, SoftReference<Bitmap>> softMemoryCache = new LinkedHashMap<String, SoftReference<Bitmap>>(
			SOFT_CACHE_CAPACITY, 0.75f, true);

	private static LruMemoryCache instance = null;

	public synchronized static LruMemoryCache getInstance() {
		if (instance == null) {
			instance = new LruMemoryCache();
		}
		return instance;
	}

	/**
	 * 添加Bitmap到内存缓存
	 * 
	 */
	public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
		if (getBitmapFromMemCache(key) == null && bitmap != null) {
			synchronized (mMemoryCache) {
				mMemoryCache.put(key, bitmap);
			}
		}
	}

	/**
	 * 从内存缓存中获取一个Bitmap
	 * 
	 */
	public Bitmap getBitmapFromMemCache(String key) {
		synchronized (mMemoryCache) {
			Bitmap bitmap = mMemoryCache.get(key);
			if (bitmap != null) {
				return bitmap;
			}
			// 硬引用缓存区间中读取失败,从软引用缓存区间读取
			synchronized (softMemoryCache) {
				SoftReference<Bitmap> bitmapReference = softMemoryCache
						.get(key);
				if (bitmapReference != null) {
					Bitmap softBitmap = bitmapReference.get();
					if (softBitmap != null) {
						return softBitmap;
					}
				} else {
					//对象为空时,从哈希表中移除该键值对。
					softMemoryCache.remove(key);
				}
			}
		}
		return null;
	}

}
           

到这里,三级缓存策略的主要步骤介绍完了,然后说下项目中遇到的两个主要问题。

1.大图片的优化处理,主要是对原图进行内存压缩处理,避免出现OOM。

public static int calculateInSampleSize(BitmapFactory.Options options,
			int reqWidth, int reqHeight) {
		// Raw height and width of image
		final int height = options.outHeight;
		final int width = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {
			if (width > height) {
				inSampleSize = Math.round((float) height / (float) reqHeight);
			} else {
				inSampleSize = Math.round((float) width / (float) reqWidth);
			}
		}
		return inSampleSize;
	}

	public static Bitmap decodeSampledBitmapFromResource(Resources res,
			int resId, int reqWidth, int reqHeight) {

		// First decode with inJustDecodeBounds=true to check dimensions
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(res, resId, options);
		// Calculate inSampleSize
		options.inSampleSize = calculateInSampleSize(options, reqWidth,
				reqHeight);
		Log.d("ViewPagerAdapter", "inSampleSize = " + options.inSampleSize);
		options.inPreferredConfig = Bitmap.Config.RGB_565;
		options.inPurgeable = true;
		options.inInputShareable = true;
		// Decode bitmap with inSampleSize set
		options.inJustDecodeBounds = false;
		InputStream is = res.openRawResource(resId);
		return BitmapFactory.decodeStream(is, null, options);
	}
           

上面两个方法是项目中所使用到的,针对大图进行处理,有兴趣的可以查阅相关资料进行研究学习。

2.解决Url下载链接的中文乱码问题。在附件中有提供源码,可以直接使用。

最后,欢迎大家交流指正,共同进步!

附件Demo下载地址(先前代码引用的包可能不一样,导致大家导入错误,先已重新上传):三级缓存Demo