前言
在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误。因此,图片的缓存是非常重要的,尤其是对图片非常多的应用。现在很多框架都做了很好的图片缓存处理。
一直想写一个自己的图片缓存框架,之前一直用Glide、Fresco等一些主流的图片框架。这些框架对处理的处理都做的非常好,也查阅了这些框架的一些源码,整体思路是使用三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量。
什么是三级缓存
- 网络缓存, 不优先加载, 速度慢,浪费流量
- 本地缓存, 次优先加载, 速度快
- 内存缓存, 优先加载, 速度最快(包括强引用(LruCache)和软引用(SoftReference))
三级缓存原理
加载图片时,判断内存缓存中是否有该图片,没有则到本地SD卡中查找,SD卡中没有则通过网络请求获取图片。
因为Android内存空间是很珍贵的,我们不能发过多的图片在缓存中,容易导致OOM,这个时候我们就要使用软引用(SoftReference)。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。
LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片
流程
实现
1、自定义图片加载工具类(VImageUtils)
通过 VImageUtils.disPlay(ImageView ivPic, String url)提供给外部方法进行图片加载。
/**
* 自定义图片缓存工具类
*
* @author Veer
* @email 276412667@qq.com
* @date 18/12/11
*/
public class VImageUtils {
private static final String TAG = "VImageUtils";
public static void disPlay(ImageView ivPic, String url) {
Bitmap bitmap;
//内存缓存
bitmap=MemoryCacheUtils.getInstance().getBitmapFromMemory(url);
if (bitmap!=null){
ivPic.setImageBitmap(bitmap);
Log.d(TAG,"从内存获取图片啦.....");
return;
}
//本地缓存
bitmap = LocalCacheUtils.getInstance().getBitmapFromLocal(url);
if(bitmap !=null){
ivPic.setImageBitmap(bitmap);
Log.d(TAG,"从本地获取图片啦.....");
//从本地获取图片后,保存至内存中
MemoryCacheUtils.getInstance().setBitmapToMemory(url,bitmap);
return;
}
//网络缓存
NetCacheUtils.getInstance().getBitmapFromNet(ivPic,url);
}
}
2、网络缓存(NetCacheUtils)
- 网络缓存中主要用到了AsyncTask来进行异步数据的加载
- 简单来说,AsyncTask可以看作是一个对handler和线程池的封装,通常,AsyncTask主要用于数据简单时,handler+thread主要用于数据量多且复杂时,当然这也不是必须的,仁者见仁智者见智。
- 同时,为了避免内存溢出的问题,我们可以在获取网络图片后。对其进行图片压缩。
/**
* 网络缓存
*
* @author Veer
* @email 276412667@qq.com
* @date 18/12/11
*/
public class NetCacheUtils {
private static NetCacheUtils mInstance;
private static final String TAG = "NetCacheUtils";
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
private NetCacheUtils() {
mMemoryCacheUtils = MemoryCacheUtils.getInstance();
mLocalCacheUtils = LocalCacheUtils.getInstance();
}
public static NetCacheUtils getInstance(){
if(mInstance == null){
synchronized (NetCacheUtils.class) {
if (mInstance == null) {
mInstance = new NetCacheUtils();
}
}
}
return mInstance;
}
/**
* 从网络下载图片
* @param ivPic 显示图片的imageview
* @param url 下载图片的网络地址
*/
public void getBitmapFromNet(ImageView ivPic, String url) {
new BitmapTask().execute(ivPic, url);//启动AsyncTask
}
/**
* AsyncTask就是对handler和线程池的封装
* 第一个泛型:参数类型
* 第二个泛型:更新进度的泛型
* 第三个泛型:onPostExecute的返回结果
*/
class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
private ImageView ivPic;
private String url;
/**
* 后台耗时操作,存在于子线程中
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(Object[] params) {
ivPic = (ImageView) params[0];
url = (String) params[1];
return downLoadBitmap(url);
}
/**
* 更新进度,在主线程中
* @param values
*/
@Override
protected void onProgressUpdate(Void[] values) {
super.onProgressUpdate(values);
}
/**
* 耗时方法结束后执行该方法,主线程中
* @param result
*/
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
ivPic.setImageBitmap(result);
Log.d(TAG,"从网络缓存图片啦.....");
//从网络获取图片后,保存至本地缓存
mLocalCacheUtils.setBitmapToLocal(url, result);
//保存至内存中
mMemoryCacheUtils.setBitmapToMemory(url, result);
}
}
}
/**
* 网络下载图片
* @param url
* @return
*/
private Bitmap downLoadBitmap(String url) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
//图片压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize=2;//宽高压缩为原来的1/2
options.inPreferredConfig=Bitmap.Config.ARGB_4444;
Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(),null,options);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return null;
}
}
3、本地缓存(LocalCacheUtils)
- 在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来
- 可以使用MD5加密图片的网络地址,来作为图片的名称保存
/**
* 本地缓存
*
* @author Veer
* @email 276412667@qq.com
* @date 18/12/11
*/
public class LocalCacheUtils {
private static final String CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VImageUtils";
private static LocalCacheUtils mInstance;
private LocalCacheUtils(){
}
public static LocalCacheUtils getInstance(){
if(mInstance == null){
synchronized (LocalCacheUtils.class) {
if (mInstance == null) {
mInstance = new LocalCacheUtils();
}
}
}
return mInstance;
}
/**
* 从本地读取图片
* @param url
* @return Bitmap
*/
public Bitmap getBitmapFromLocal(String url) {
String fileName = null;//把图片的url当做文件名,并进行MD5加密
try {
fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 从网络获取图片后,保存至本地缓存
* @param url
* @param bitmap
*/
public void setBitmapToLocal(String url, Bitmap bitmap) {
try {
String fileName = MD5Encoder.encode(url);//把图片的url当做文件名,并进行MD5加密
File file = new File(CACHE_PATH, fileName);
//通过得到文件的父文件,判断父文件是否存在
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
//把图片保存至本地
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、内存缓存(MemoryCacheUtils)
- 这块东西比较重要,也是图片缓存的关键一步
- 通过LruCache<String,Bitmap> 来缓存我们的图片,由于我们LruCache的空间有限,我们把剩余的图片放入
Map<String, SoftReference<Bitmap>> 软引用中,这样不会造成内存泄露。
- 在向缓存中取图片时,通过最少最近使用算法,先从LruCache中查找,在LruCache中没有再从SoftReference软引用中查找。在SoftReference查到然后重新放入LruCache中。
/**
* 内存缓存
*
* @author Veer
* @email 276412667@qq.com
* @date 18/12/11
*/
public class MemoryCacheUtils {
private static MemoryCacheUtils mInstance;
private ImageCache mImageCache;
private MemoryCacheUtils(){
Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
mImageCache = new ImageCache(cacheMap);
}
public static MemoryCacheUtils getInstance(){
if(mInstance == null){
synchronized (MemoryCacheUtils.class) {
if (mInstance == null) {
mInstance = new MemoryCacheUtils();
}
}
}
return mInstance;
}
/**
* 从内存中读图片
* @param url
*/
public Bitmap getBitmapFromMemory(String url) {
Bitmap bitmap = mImageCache.get(url);
// 如果图片不存在强引用中,则去软引用(SoftReference)中查找
if(bitmap == null){
Map<String, SoftReference<Bitmap>> cacheMap = mImageCache.getCacheMap();
SoftReference<Bitmap> softReference = cacheMap.get(url);
if(softReference!=null){
bitmap = softReference.get();
//重新放入强引用缓存中
mImageCache.put(url,bitmap);
}
}
return bitmap;
}
/**
* 往内存中写图片
* @param url
* @param bitmap
*/
public void setBitmapToMemory(String url, Bitmap bitmap) {
mImageCache.put(url,bitmap);
}
}
ImageCache代码:
/**
* 图片缓存
*
* @author Veer
* @email 276412667@qq.com
* @date 18/12/11
*/
public class ImageCache extends LruCache<String,Bitmap> {
private Map<String, SoftReference<Bitmap>> cacheMap;
public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
super((int) (Runtime.getRuntime().maxMemory() / 8));
this.cacheMap = cacheMap;
}
@Override // 获取图片大小
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
// 当有图片从LruCache中移除时,将其放进软引用集合中
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
cacheMap.put(key, softReference);
}
}
public Map<String, SoftReference<Bitmap>> getCacheMap() {
return cacheMap;
}
}
总结
以上就是图片缓存的策略,后期我会完善我的这个图片框架,争取做到向那些主流框架一样。
会一直在我的github上更新,希望多多关注