补充的知识:
强引用:不会被回收
弱引用:垃圾回收器触发会被回收
软引用:系统检测内存不足时会被回收
虚引用:等于null;
为什么需要图片缓存
android默认给每个应用只分配16M的内存,所以如果加载过多的图片,为了防止内存溢出,应该将图片缓存起来。图片的三级缓存分别是:
内存缓存
本地缓存
网络缓存
其中,内存缓存应优先加载,它速度最快;本地缓存次优先加载,它速度也快;网络缓存不应该优先加载,它走网络,速度慢且耗流量。
三级缓存的概念
内存 > 硬盘 > 网络
由内存、硬盘、网络缓存形成。
原理
20161129202321287.png
Android原生为我们提供了一个LruCache,其中维护着一个LinkedHashMap。LruCache可以用来存储各种类型的数据,但最常见的是存储图片(Bitmap)。LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。
当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。
关于三级缓存用到的技术
Android高效加载大图、多图解决方案、有效避免程序OOM使用的核心技术就是LruCache。
LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,
Google又提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证。)
用法和流程
自己理解:当每次加载图片的时候都优先去内存加载图片、当内存中获取不到图片的时候则去硬盘缓存读取、如果硬盘缓存读取不到数据就去网络获取数据。不
管是从硬盘缓存还是从网络获取,读取到了数据之后都应该添加到内存缓存当中,这样的话我们下次再去读取图片的时候就能迅速从内存当中读取到
,而如果该图片从内存中被移除了的话,那就重复再执行一遍上述流程。
官方的原理:
当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。
三级缓存的具体实现
网络缓存
根据图片的url去加载图片
在本地和内存中缓存
public class NetCacheUtils {
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
public NetCacheUtils(LocalCacheUtils localCacheUtils,
MemoryCacheUtils memoryCacheUtils) {
mLocalCacheUtils = localCacheUtils;
mMemoryCacheUtils = memoryCacheUtils;
}
/**
* 从网络下载图片
*
* @param ivPic
* @param url
*/
public void getBitmapFromNet(ImageView ivPic, String url) {
new BitmapTask().execute(ivPic, url);// 启动AsyncTask,
// 参数会在doInbackground中获取
}
/**
* Handler和线程池的封装
*
* 第一个泛型: 参数类型 第二个泛型: 更新进度的泛型, 第三个泛型是onPostExecute的返回结果
*
*
*/
class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
private ImageView ivPic;
private String url;
/**
* 后台耗时方法在此执行, 子线程
*/
@Override
protected Bitmap doInBackground(Object... params) {
ivPic = (ImageView) params[0];
url = (String) params[1];
ivPic.setTag(url);// 将url和imageview绑定
return downloadBitmap(url);
}
/**
* 更新进度, 主线程
*/
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
/**
* 耗时方法结束后,执行该方法, 主线程
*/
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
String bindUrl = (String) ivPic.getTag();
if (url.equals(bindUrl)) {// 确保图片设定给了正确的imageview
ivPic.setImageBitmap(result);
mLocalCacheUtils.setBitmapToLocal(url, result);// 将图片保存在本地
mMemoryCacheUtils.setBitmapToMemory(url, result);// 将图片保存在内存
System.out.println("从网络缓存读取图片啦...");
}
}
}
}
/**
* 下载图片
*
* @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");
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream inputStream = conn.getInputStream();
//图片压缩处理
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定
option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return null;
}
}
本地缓存
两个方法:设置本地缓存,获取本地缓存
public class LocalCacheUtils {
public static final String CACHE_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/local_cache";
/**
* 从本地sdcard读图片
*/
public Bitmap getBitmapFromLocal(String url) {
try {
String fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
if (file.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
file));
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 向sdcard写图片
*
* @param url
* @param bitmap
*/
public void setBitmapToLocal(String url, Bitmap bitmap) {
try {
String fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
File parentFile = file.getParentFile();
if (!parentFile.exists()) {// 如果文件夹不存在, 创建文件夹
parentFile.mkdirs();
}
// 将图片保存在本地
bitmap.compress(CompressFormat.JPEG, 100,
new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
内存缓存
两个方法:设置内存缓存,获取内存缓存
问题:
1: 如果使用HashMap存储图片时,当图片越来越多时,会导致内存溢出,因为它是强引用,java的垃圾回收器不会回收。
2: 如若改成软引用SoftReference(内存不够时,垃圾回收器会考虑回收),仍有一个问题:在android2.3+, 系统会优先将SoftReference的对象提前回收掉, 即使内存够用。
解决办法:可以用LruCache来解决上述内存不回收或提前回收的问题。least recentlly use 最少最近使用算法 它会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定
public class MemoryCacheUtils {
// private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new
// HashMap<String, SoftReference<Bitmap>>();
private LruCache<String, Bitmap> mMemoryCache;
public MemoryCacheUtils() {
long maxMemory = Runtime.getRuntime().maxMemory() / 8;// 模拟器默认是16M
mMemoryCache = new LruCache<String, Bitmap>((int) maxMemory) {
@Override
protected int sizeOf(String key, Bitmap value) {
int byteCount = value.getRowBytes() * value.getHeight();// 获取图片占用内存大小
return byteCount;
}
};
}
/**
* 从内存读
*
* @param url
*/
public Bitmap getBitmapFromMemory(String url) {
// SoftReference<Bitmap> softReference = mMemoryCache.get(url);
// if (softReference != null) {
// Bitmap bitmap = softReference.get();
// return bitmap;
// }
return mMemoryCache.get(url);
}
/**
* 写内存
*
* @param url
* @param bitmap
*/
public void setBitmapToMemory(String url, Bitmap bitmap) {
// SoftReference<Bitmap> softReference = new
// SoftReference<Bitmap>(bitmap);
// mMemoryCache.put(url, softReference);
mMemoryCache.put(url, bitmap);
}
}
图片压缩
//图片压缩处理(在从网络获取图片的时候就进行压缩)
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定
option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
网友评论