一、概述:
现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。
现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。
二、实现原理:
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。
但这里不使用SoftReference,而使用LruCache进行图片的缓存
为什么使用LruCache:
这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
三、具体实现:
1)在构造方法里初始化LruCache mCache
if (mCache == null) {
// 最大使用的内存空间
int maxSize = (int) (Runtime.getRuntime().freeMemory() / 4);
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
2)去内存中取
Bitmap bitmap = mCache.get(url);
if (bitmap != null) {
// 直接显示
iv.setImageBitmap(bitmap);
return;
}
3)去硬盘上取
bitmap = loadBitmapFromLocal(url);
if (bitmap != null) {
// 直接显示
iv.setImageBitmap(bitmap);
return;
}
4)从网络加载
loadBitmapFromNet(iv, url);
四、详细代码:
/**
* @项目名: 3G缓存加载图片
* @包名: com.android.news.tools
* @类名: ImageHelper
* @创建者: chen.lin
* @创建时间: 2015-4-27 上午10:50:37
*
*/
public class ImageHelper {
// 内存缓存池
// private Map<String, SoftReference<Bitmap>> mCache = new
// LinkedHashMap<String, SoftReference<Bitmap>>();
// LRUCahce 池子
private static LruCache<String, Bitmap> mCache;
private static Handler mHandler;
private static ExecutorService mThreadPool;
private static Map<ImageView, Future<?>> mTaskTags = new LinkedHashMap<ImageView, Future<?>>();
private Context mContext;
public ImageHelper(Context context) {
this.mContext = context;
if (mCache == null) {
// 最大使用的内存空间
int maxSize = (int) (Runtime.getRuntime().freeMemory() / 4);
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
if (mHandler == null) {
mHandler = new Handler();
}
if (mThreadPool == null) {
// 最多同时允许的线程数为3个
mThreadPool = Executors.newFixedThreadPool(3);
}
}
public void display(ImageView iv, String url) {
// 1.去内存中取
Bitmap bitmap = mCache.get(url);
if (bitmap != null) {
// 直接显示
iv.setImageBitmap(bitmap);
return;
}
// 2.去硬盘上取
bitmap = loadBitmapFromLocal(url);
if (bitmap != null) {
// 直接显示
iv.setImageBitmap(bitmap);
return;
}
// 3. 去网络获取图片
loadBitmapFromNet(iv, url);
}
private void loadBitmapFromNet(ImageView iv, String url) {
// 开线程去网络获取
// 使用线程池管理
// new Thread(new ImageLoadTask(iv, url)).start();
// 判断是否有线程在为 imageView加载数据
Future<?> futrue = mTaskTags.get(iv);
if (futrue != null && !futrue.isCancelled() && !futrue.isDone()) {
System.out.println("取消 任务");
// 线程正在执行
futrue.cancel(true);
futrue = null;
}
// mThreadPool.execute(new ImageLoadTask(iv, url));
futrue = mThreadPool.submit(new ImageLoadTask(iv, url));
// Future 和 callback/Runable
// 返回值,持有正在执行的线程
// 保存
mTaskTags.put(iv, futrue);
System.out.println("标记 任务");
}
class ImageLoadTask implements Runnable {
private String mUrl;
private ImageView iv;
public ImageLoadTask(ImageView iv, String url) {
this.mUrl = url;
this.iv = iv;
}
@Override
public void run() {
// HttpUrlconnection
try {
// 获取连接
HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();
conn.setConnectTimeout(30 * 1000);// 设置连接服务器超时时间
conn.setReadTimeout(30 * 1000);// 设置读取响应超时时间
// 连接网络
conn.connect();
// 获取响应码
int code = conn.getResponseCode();
if (200 == code) {
InputStream is = conn.getInputStream();
// 将流转换为bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is);
// 存储到本地
write2Local(mUrl, bitmap);
// 存储到内存
mCache.put(mUrl, bitmap);
// 图片显示:不可取
// iv.setImageBitmap(bitmap);
mHandler.post(new Runnable() {
@Override
public void run() {
// iv.setImageBitmap(bitmap);
display(iv, mUrl);
}
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 本地种去去图片
*
* @param url
*/
private Bitmap loadBitmapFromLocal(String url) {
// 去找文件,将文件转换为bitmap
String name;
try {
name = MD5Encoder.encode(url);
File file = new File(getCacheDir(), name);
if (file.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
// 存储到内存
mCache.put(url, bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private void write2Local(String url, Bitmap bitmap) {
String name;
FileOutputStream fos = null;
try {
name = MD5Encoder.encode(url);
File file = new File(getCacheDir(), name);
fos = new FileOutputStream(file);
// 将图像写到流中
bitmap.compress(CompressFormat.JPEG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private String getCacheDir() {
String state = Environment.getExternalStorageState();
File dir = null;
if (Environment.MEDIA_MOUNTED.equals(state)) {
// 有sd卡
dir = new File(Environment.getExternalStorageDirectory(), "/Android/data/" + mContext.getPackageName()
+ "/icon");
} else {
// 没有sd卡
dir = new File(mContext.getCacheDir(), "/icon");
}
if (!dir.exists()) {
dir.mkdirs();
}
return dir.getAbsolutePath();
}
}
五、使用方法:
在Adapter 的getView方法里
ImageView iv = (contentView)findViewById(R.id.iv);
String url = "http://localhost:8080/web/1.jpg";
new IamgeHelper(this).display(iv,url);