前言:在Android里面,图片的使用可以说是非常重要的,根据自己平时项目中接触到的和空余时间,对图片的三级缓存做了个小结:
一、三级缓存原理:
1.图片的缓存的三级指的是:内存缓存、本地缓存及网络直接获取。针对图片缓存的位置的不同,其呈现加载的效率也不一样,由高到低分别为:
内存–>本地–>网络;内存、本地、网络就构成了缓存的三个级别,大概“三级缓存”就是这么来的吧;
2.针对缓存的位置 内存、本地、网络获取,我们也很容易理解:首先,内存中的图片以Bitmap形式存储,加载呈现的时候自然也是最快、效率更高的;其次,是本地缓存,
图片以File形式存放在本地,呈现的时候,先需要将File转换成Stream流形式读取到内存,再进行呈现;最后,是网络获取,当图片在网络(也是是服务器后台)中存放,需要呈现时,
只能通过联网获取,同样是以ByteArray或者Stream形式获取,再进行呈现,相对于从本地获取,联网获取是比本地更慢。
3.三级缓存的目的:主要是为了提高图片的加载效率,争取对于同一张图片只要加载过一次,之后就不再从网上获取,而是从本地或者内存中加载;
4.内存缓存的分类:
在三级缓存的内存缓存中,主要使用的是java引用级别里面的强引用与软引用(关于java引用级别及强引用、软引用的说明,下面会讲到)
1)强引用、软引用:强引用、软引用缓存其实就是一个Map集合,该集合是属于v4包下的集合,其内部内置了Lru算法(Lru算法:在组织三级缓存的时候,决定移除哪一部分数据、保留哪一部分数据的核心依据)
2)在三级缓存中,强引用 加载速度较快,加载优先级较高,而软引用加载优先级、效率更低,更容易被gc回收。
二、Java引用级别
Java引用级别可以分为强、软、弱、虚四级,其划分的依据是Java虚拟机对于对象回收的难易程度。强引用是我们使用得最普遍的,持有强应用的对象,其与生命周期和gc无关,就算是抛出了OOM异常,也不会被gc回收;而软应用、弱引用、虚引用都与内存有关,当内存不足时,持有月软引用的对象会被回收,而当gc扫描到持有弱引用的对象所在内存位置时,即被回收;虚引用则是无论什么情况下都有可能被回收,持有虚引用和不持有是一样的。
三、图片压缩原理分析
内存缓存
1)强引用缓存初始化:首先获取当前应用的最大可用内存,再设置强引用缓存的大小,然后在实例化LruCache集合的时候将强引用缓存的大小作为参数传入,这样就完成了强引用缓存大小的设置;
2)在初始化强引用缓存时,需要覆写两个方法:
sizeOf()————>计算BitMap的大小,获取新增图片的内存占有量
entryRemoved()—————>若强引用缓存满的时候,根据Lru算法将最近没有使用的图片放到软引用
3)软引用缓存的初始化:在初始化软引用缓存时,有一个移除最早使用的节点的方法,即把最不常使用的图片是移除。
2.从内存中获取图片:首先会根据ur从强引用中获取,如果不存在再从软引用中获取而在软引用中获取的时候,是将软引用的图片放到强引用中,这样就能从软引用中获取到图片:
3.向内存中添加图片:根据键值的形式向集合中添加图片(优先向强引用缓存中添加)
4.移除缓存:
在BitMap不再使用的时候,要调用recycle()方法将BitMap对象回收,在移除缓存中,需要调用BitMap的recycle()方法将BitMap对象回收,同时,调用集合的clear()方法,还需要调用System.gc()方法,申请垃圾回收。
package com.example.sj.imageloderdemo;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;
import java.util.Set;
/*
第一级 内存
*/
public class ImageMemoryCache {
/**
* 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
* 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
*/
private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量
private static LruCache<String, Bitmap> mLruCache; //强引用缓存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; //软引用缓存
public ImageMemoryCache(Context context) {
//获取当前应用最大可用内存
int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
//硬引用缓存容量,为系统可用内存的1/4
int cacheSize = 1024 * 1024 * memClass / 4;
//实例化硬应用内存LruCache对象,构造器中传入该对象可以占用的最大内存数
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
//计算Bitmap的大小,获取新增图片的内存占有量
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
return value.getRowBytes() * value.getHeight();
else
return 0;
}
// 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
};
//实例化软引用LinkedHashMap对象
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>> (SOFT_CACHE_SIZE, 0.75f, true) {
private static final long serialVersionUID = 6040103833179403725L;
//移除最老使用的节点---LRU
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_SIZE) {//如果软引用缓存的内存大于设置的内存大小,则把数据也从软引用缓存中删除
eldest.getValue().get().recycle();
return true;
}
return false;
}
};
}
/**
* 从内存中获取图片
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//先从硬引用缓存中获取
bitmap = mLruCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到LruCache的最前面,从而保证在LRU算法中是最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
//如果硬引用缓存中找不到,到软引用缓存中找
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
//要使用的图片,我们都放到硬引用,将图片移回硬缓存
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
mSoftCache.remove(url);
}
System.gc();
}
return null;
}
/**
* 添加图片到缓存
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
/*
清除全部内存缓存
*/
public void clearCache() {
//移除强引用缓存中的数据
mLruCache.evictAll();
Set<String> keys = mSoftCache.keySet();
//移除软引用缓存中的数据
for (String key:keys){
SoftReference<Bitmap> bitmapSoftReference = mSoftCache.get(key);
bitmapSoftReference.get().recycle();
}
mSoftCache.clear();
System.gc();
}
}
本地缓存
1.从本地缓存中获取图片:根据路径,定位到图片的url,然后就能获取到图片:
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + convertUrlToFileName(url);
System.out.println(path);
File file = new File(path);
if (file.exists()) {
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null) {
file.delete();
} else {
updateFileTime(path);
return bmp;
}
}
return null;
}
2.将图片存入本地缓存:在存入图片之前,需要判断SD卡的空间是否充足,若不足,则释放一些空间,若释放了还是不足,则不再缓存
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足,释放一些空间出来
removeCache(getDirectory());
// return;
}
//释放之后发现还不够,就不存了
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
return;
}
String filename = convertUrlToFileName(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
removeCache(getDirectory());
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/** 将图片存入文件缓存 **/
public String saveBitmap(InputStream inputStream, String url) {
if (inputStream == null) {
return null;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足,释放一些空间出来
removeCache(getDirectory());
// return;
}
//释放之后发现还不够,就不存了
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
return null;
}
String filename = convertUrlToFileName(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
FileOutputStream fos=new FileOutputStream(file);
try {
byte[] bs=new byte[1024*100];
int total=0;
while ((total=inputStream.read(bs))!=-1){
fos.write(bs,0,total);
}
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
fos.flush();
fos.close();
removeCache(getDirectory());
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
return file.getAbsolutePath();
}
3.移除缓存:计算缓存的大小,若缓存的大小大于规定或者设定的大小,则移除
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
*/
private boolean removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return false;
}
if (files == null) {
return true;
}
//计算缓存目录现在的使用量
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
//判读是否已经超限
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//删多少 ---removeFactor
int removeFactor = (int) ((0.4 * files.length) + 1);
//把数组中的文件---即全部缓存文件,根据最后修改时间来排序
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
完整代码:
package com.example.sj.imageloderdemo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;
public class ImageFileCache {
//SD卡缓存目录
private static final String CACHDIR = "ImgCach";
//缓存文件后缀
private static final String WHOLESALE_CONV = ".cach";
private static final int MB = 1024*1024;
//缓存最大容量
private static final int CACHE_SIZE = 100;
//当新增一个图片时,我们需要的SD卡最少容量
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 100;
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + convertUrlToFileName(url);
System.out.println(path);
File file = new File(path);
if (file.exists()) {
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null) {
file.delete();
} else {
updateFileTime(path);
return bmp;
}
}
return null;
}
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足,释放一些空间出来
removeCache(getDirectory());
// return;
}
//释放之后发现还不够,就不存了
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
return;
}
String filename = convertUrlToFileName(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
removeCache(getDirectory());
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/** 将图片存入文件缓存 **/
public String saveBitmap(InputStream inputStream, String url) {
if (inputStream == null) {
return null;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足,释放一些空间出来
removeCache(getDirectory());
// return;
}
//释放之后发现还不够,就不存了
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
return null;
}
String filename = convertUrlToFileName(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
FileOutputStream fos=new FileOutputStream(file);
try {
byte[] bs=new byte[1024*100];
int total=0;
while ((total=inputStream.read(bs))!=-1){
fos.write(bs,0,total);
}
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
fos.flush();
fos.close();
removeCache(getDirectory());
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
return file.getAbsolutePath();
}
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
*/
private boolean removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return false;
}
if (files == null) {
return true;
}
//计算缓存目录现在的使用量
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
//判读是否已经超限
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//删多少 ---removeFactor
int removeFactor = (int) ((0.4 * files.length) + 1);
//把数组中的文件---即全部缓存文件,根据最后修改时间来排序
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
/** 修改文件的最后修改时间 LRU **/
public void updateFileTime(String path) {
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
/** 将url转成文件名 **/
private String convertUrlToFileName(String url) {
return url.hashCode()+"" + WHOLESALE_CONV;
}
/** 获得缓存目录 **/
private String getDirectory() {
String dir = getSDPath() + "/" + CACHDIR;
return dir;
}
/** 取SD卡路径 **/
private String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED); //判断sd卡是否存在
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory(); //获取根目录
}
if (sdDir != null) {
return sdDir.toString();
} else {
return "";
}
}
/**
* 根据文件的最后修改时间进行排序
*/
private class FileLastModifSort implements Comparator<File> {
public int compare(File arg0, File arg1) {
if (arg0.lastModified() > arg1.lastModified()) {
return 1;
} else if (arg0.lastModified() == arg1.lastModified()) {
return 0;
} else {
return -1;
}
}
}
}
网络缓存
网络缓存实质就是直接从网络上获取图片,即:执行网络访问,获取数据:
总结:到这里,我们就根据缓存的原理去分析Imageloader的display()方法:
首先从内存中获取,若内存中存在则直接返回图片
若内存中不存在,则从本地缓存中获取,同样的,若不存在,则只能从网络上获取