摘要:项目中做了一个关于在线壁纸的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