图片的三级缓存
1、优先从内存中加载图片, 速度最快, 不浪费流量
2、其次从本地(sdcard)加载图片, 速度快, 不浪费流量
3、最后从网络下载图片, 速度慢, 浪费流量
XUtils中的BitmapUtils 等开源框架中底层都做了图片的三级缓存。
接下来,我们试着自己写一个类似于
BitmapUtils
图片三级缓存工具类。
MyBitmapUtils.java
/**
* 自定义三级缓存图片加载工具
*
*/
public class MyBitmapUtils {
private NetCacheUtils mNetCacheUtils;//网络缓存工具类
private LocalCacheUtils mLocalCacheUtils;//本地缓存工具类
private MemoryCacheUtils mMemoryCacheUtils;
public MyBitmapUtils() {
mMemoryCacheUtils = new MemoryCacheUtils();
mLocalCacheUtils = new LocalCacheUtils();
mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils, mMemoryCacheUtils);
}
public void display(ImageView imageView, String url) {
// 设置默认图片
imageView.setImageResource(R.drawable.pic_item_list_default);
// 优先从内存中加载图片, 速度最快, 不浪费流量
Bitmap bitmap = mMemoryCacheUtils.getMemoryCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
System.out.println("从内存加载图片啦");
return;
}
// 其次从本地(sdcard)加载图片, 速度快, 不浪费流量
bitmap = mLocalCacheUtils.getLocalCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
System.out.println("从本地加载图片啦");
// 写内存缓存
mMemoryCacheUtils.setMemoryCache(url, bitmap);
return;
}
// 最后从网络下载图片, 速度慢, 浪费流量
mNetCacheUtils.getBitmapFromNet(imageView, url);
}
}
NetCacheUtils.java网络缓存类
其中用了AsyncTask异步访问网络请求
/**
* 网络缓存
*
*/
public class NetCacheUtils {
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
public NetCacheUtils(LocalCacheUtils localCacheUtils,
MemoryCacheUtils memoryCacheUtils) {
mLocalCacheUtils = localCacheUtils;
mMemoryCacheUtils = memoryCacheUtils;
}
public void getBitmapFromNet(ImageView imageView, String url) {
// AsyncTask 异步封装的工具, 可以实现异步请求及主界面更新(对线程池+handler的封装)
new BitmapTask().execute(imageView, url);// 启动AsyncTask
}
/**
* 三个泛型意义: 第一个泛型:doInBackground里的参数类型
* 第二个泛型: onProgressUpdate里的参数类型
* 第三个泛型: onPostExecute里的参数类型及doInBackground的返回类型
*
*/
class BitmapTask extends AsyncTask<Object, Integer, Bitmap> {
private ImageView imageView;
private String url;
// 1.预加载, 运行在主线程
@Override
protected void onPreExecute() {
super.onPreExecute();
// System.out.println("onPreExecute");
}
// 2.正在加载, 运行在子线程(核心方法), 可以直接异步请求
@Override
protected Bitmap doInBackground(Object... params) {
// System.out.println("doInBackground");
imageView = (ImageView) params[0];
url = (String) params[1];
imageView.setTag(url);// 打标记, 将当前imageview和url绑定在了一起
// 开始下载图片
Bitmap bitmap = download(url);
// publishProgress(values) 调用此方法实现进度更新(会回调onProgressUpdate)
return bitmap;
}
// 3.更新进度的方法, 运行在主线程
@Override
protected void onProgressUpdate(Integer... values) {
// 更新进度条
super.onProgressUpdate(values);
}
// 4.加载结束, 运行在主线程(核心方法), 可以直接更新UI
@Override
protected void onPostExecute(Bitmap result) {
// System.out.println("onPostExecute");
if (result != null) {
// 给imageView设置图片
// 由于listview的重用机制导致imageview对象可能被多个item共用,
// 从而可能将错误的图片设置给了imageView对象
// 所以需要在此处校验, 判断是否是正确的图片
String url = (String) imageView.getTag();
if (url.equals(this.url)) {// 判断图片绑定的url是否就是当前bitmap的url // 如果是,说明图片正确
imageView.setImageBitmap(result);
System.out.println("从网络加载图片啦!!!");
// 写本地缓存
mLocalCacheUtils.setLocalCache(url, result);
// 写内存缓存
mMemoryCacheUtils.setMemoryCache(url, result);
}
}
super.onPostExecute(result);
}
}
// 下载图片
public Bitmap download(String url) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);// 连接超时
conn.setReadTimeout(5000);// 读取超时
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream inputStream = conn.getInputStream();
// 根据输入流生成bitmap对象
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
}
LocalCacheUtils.java
本地缓存类
/**
* 本地缓存
*/
public class LocalCacheUtils {
private static final String LOCAL_CACHE_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/image_cache";
// 写本地缓存
public void setLocalCache(String url, Bitmap bitmap) {
File dir = new File(LOCAL_CACHE_PATH);
if (!dir.exists() || !dir.isDirectory()) {
dir.mkdirs();// 创建文件夹
}
try {
String fileName = MD5Encoder.encode(url);
File cacheFile = new File(dir, fileName);
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(
cacheFile));// 参数1:图片格式;参数2:压缩比例0-100; 参数3:输出流
} catch (Exception e) {
e.printStackTrace();
}
}
// 读本地缓存
public Bitmap getLocalCache(String url) {
try {
File cacheFile = new File(LOCAL_CACHE_PATH, MD5Encoder.encode(url));
if (cacheFile.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
cacheFile));
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
MemoryCacheUtils.java 内存缓存类
/**
* 内存缓存
*/
public class MemoryCacheUtils {
private HashMap<String, Bitmap> mMemoryCache = new HashMap<String,Bitmap>();
/**
* 写缓存
*/
public void setMemoryCache(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
}
/**
* 读缓存
*/
public Bitmap getMemoryCache(String url) {
return mMemoryCache.get(url);
}
}
但是由于不管android设备总内存是多大, 都只给每个app分配一定内存大小, 16M, 一旦超出16M就内存溢出了。所以内存缓存图片数量过多就会OOM。于是尝试采用软引用SoftReference修改内存缓存类,修改如下:
/**
* 内存缓存
*/
public class MemoryCacheUtils {
<span style="white-space:pre"> </span>private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<String, SoftReference<Bitmap>>();
/**
* 写缓存
*/
public void setMemoryCache(String url, Bitmap bitmap) {
SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap);//
// 使用软引用将bitmap包装起来
mMemoryCache.put(url, soft);
}
/**
* 读缓存
*/
public Bitmap getMemoryCache(String url) {
SoftReference<Bitmap> softReference = mMemoryCache.get(url);
if (softReference != null) {
Bitmap bitmap = softReference.get();
return bitmap;
}
return null;
}
}
tip:
- 默认强引用, 垃圾回收器不会回收
- 软引用, 垃圾回收器会考虑回收 SoftReference
- 弱引用, 垃圾回收器更会考虑回收 WeakReference
- 虚引用, 垃圾回收器最优先回收 PhantomReference
但是又因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。Google建议使用LruCache。
下面这个是官方文档的截图:
所以再次修改喽~
修改如下:
/**
* 内存缓存
* 因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让
* 软引用和弱引用变得不再可靠。Google建议使用LruCache
*/
public class MemoryCacheUtils {
// private HashMap<String, Bitmap> mMemoryCache = new HashMap<String,
// Bitmap>();
// private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new
// HashMap<String, SoftReference<Bitmap>>();
private LruCache<String, Bitmap> mMemoryCache;
public MemoryCacheUtils() {
// LruCache 可以将最近最少使用的对象回收掉, 从而保证内存不会超出范围
// Lru: least recentlly used 最近最少使用算法
long maxMemory = Runtime.getRuntime().maxMemory();// 获取分配给app的内存大小
System.out.println("maxMemory:" + maxMemory);
mMemoryCache = new LruCache<String, Bitmap>((int) (maxMemory / 8)) {
// 返回每个对象的大小
@Override
protected int sizeOf(String key, Bitmap value) {
// int byteCount = value.getByteCount();
int byteCount = value.getRowBytes() * value.getHeight();// 计算图片大小:每行字节数*高度
return byteCount;
}
};
}
/**
* 写缓存
*/
public void setMemoryCache(String url, Bitmap bitmap) {
// mMemoryCache.put(url, bitmap);
// SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap);//
// 使用软引用将bitmap包装起来
// mMemoryCache.put(url, soft);
mMemoryCache.put(url, bitmap);
}
/**
* 读缓存
*/
public Bitmap getMemoryCache(String url) {
// SoftReference<Bitmap> softReference = mMemoryCache.get(url);
//
// if (softReference != null) {
// Bitmap bitmap = softReference.get();
// return bitmap;
// }
return mMemoryCache.get(url);
}
}