如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
3、从网络下载图片,并更新到内存缓存和文件缓存。
接下来看内存缓存类:ImageMemoryCache
- 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();
- int cacheSize = 1024 * 1024 * memClass / 4; //硬引用缓存容量,为系统可用内存的1/4
- mLruCache = new LruCache<String, Bitmap>(cacheSize) {
- @Override
- protected int sizeOf(String key, Bitmap value) {
- if (value != null)
- return value.getRowBytes() * value.getHeight();
- else
- return 0;
- }
- @Override
- protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
- if (oldValue != null)
- // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
- mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
- }
- };
- mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
- private static final long serialVersionUID = 6040103833179403725L;
- @Override
- protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
- if (size() > SOFT_CACHE_SIZE){
- return true;
- }
- return false;
- }
- };
- }
- /**
- * 从缓存中获取图片
- */
- public Bitmap getBitmapFromCache(String url) {
- Bitmap bitmap;
- //先从硬引用缓存中获取
- synchronized (mLruCache) {
- bitmap = mLruCache.get(url);
- if (bitmap != null) {
- //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
- mLruCache.remove(url);
- mLruCache.put(url, bitmap);
- return bitmap;
- }
- }
- //如果硬引用缓存中找不到,到软引用缓存中找
- synchronized (mSoftCache) {
- 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);
- }
- }
- }
- return null;
- }
- /**
- * 添加图片到缓存
- */
- public void addBitmapToCache(String url, Bitmap bitmap) {
- if (bitmap != null) {
- synchronized (mLruCache) {
- mLruCache.put(url, bitmap);
- }
- }
- }
- public void clearCache() {
- mSoftCache.clear();
- }
- }
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();
int cacheSize = 1024 * 1024 * memClass / 4; //硬引用缓存容量,为系统可用内存的1/4
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
return value.getRowBytes() * value.getHeight();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
// 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
};
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_SIZE){
return true;
}
return false;
}
};
}
/**
* 从缓存中获取图片
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//先从硬引用缓存中获取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
//如果硬引用缓存中找不到,到软引用缓存中找
synchronized (mSoftCache) {
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);
}
}
}
return null;
}
/**
* 添加图片到缓存
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}
文件缓存类:ImageFileCache
- public class ImageFileCache {
- 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 = 10;
- private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
- public ImageFileCache() {
- //清理文件缓存
- removeCache(getDirectory());
- }
- /** 从缓存中获取图片 **/
- public Bitmap getImage(final String url) {
- final String path = getDirectory() + "/" + convertUrlToFileName(url);
- 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空间不足
- 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();
- } catch (FileNotFoundException e) {
- Log.w("ImageFileCache", "FileNotFoundException");
- } catch (IOException e) {
- Log.w("ImageFileCache", "IOException");
- }
- }
- /**
- * 计算存储目录下的文件大小,
- * 当文件总大小大于规定的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 (files == null) {
- return true;
- }
- if (!android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED)) {
- return false;
- }
- 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()) {
- 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;
- }
- /** 修改文件的最后修改时间 **/
- 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) {
- String[] strs = url.split("/");
- return strs[strs.length - 1] + WHOLESALE_CONV;
- }
- /** 获得缓存目录 **/
- private String getDirectory() {
- String dir = getSDPath() + "/" + CACHDIR;
- return dir;
- }
- /** 取SD卡路径 **/
- private String getSDPath() {
- File sdDir = null;
- boolean sdCardExist = Environment.getExternalStorageState().equals(
- android.os.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;
- }
- }
- }
- }
public class ImageFileCache {
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 = 10;
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache() {
//清理文件缓存
removeCache(getDirectory());
}
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + convertUrlToFileName(url);
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空间不足
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();
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的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 (files == null) {
return true;
}
if (!android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return false;
}
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()) {
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;
}
/** 修改文件的最后修改时间 **/
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) {
String[] strs = url.split("/");
return strs[strs.length - 1] + WHOLESALE_CONV;
}
/** 获得缓存目录 **/
private String getDirectory() {
String dir = getSDPath() + "/" + CACHDIR;
return dir;
}
/** 取SD卡路径 **/
private String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
android.os.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;
}
}
}
}
从网络获取图片:
- public class ImageGetFromHttp {
- private static final String LOG_TAG = "ImageGetFromHttp";
- public static Bitmap downloadBitmap(String url) {
- final HttpClient client = new DefaultHttpClient();
- final HttpGet getRequest = new HttpGet(url);
- try {
- HttpResponse response = client.execute(getRequest);
- final int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);
- return null;
- }
- final HttpEntity entity = response.getEntity();
- if (entity != null) {
- InputStream inputStream = null;
- try {
- inputStream = entity.getContent();
- FilterInputStream fit = new FlushedInputStream(inputStream);
- return BitmapFactory.decodeStream(fit);
- } finally {
- if (inputStream != null) {
- inputStream.close();
- inputStream = null;
- }
- entity.consumeContent();
- }
- }
- } catch (IOException e) {
- getRequest.abort();
- Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
- } catch (IllegalStateException e) {
- getRequest.abort();
- Log.w(LOG_TAG, "Incorrect URL: " + url);
- } catch (Exception e) {
- getRequest.abort();
- Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
- } finally {
- client.getConnectionManager().shutdown();
- }
- return null;
- }
- /*
- * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
- */
- static class FlushedInputStream extends FilterInputStream {
- public FlushedInputStream(InputStream inputStream) {
- super(inputStream);
- }
- @Override
- public long skip(long n) throws IOException {
- long totalBytesSkipped = 0L;
- while (totalBytesSkipped < n) {
- long bytesSkipped = in.skip(n - totalBytesSkipped);
- if (bytesSkipped == 0L) {
- int b = read();
- if (b < 0) {
- break; // we reached EOF
- } else {
- bytesSkipped = 1; // we read one byte
- }
- }
- totalBytesSkipped += bytesSkipped;
- }
- return totalBytesSkipped;
- }
- }
- }
public class ImageGetFromHttp {
private static final String LOG_TAG = "ImageGetFromHttp";
public static Bitmap downloadBitmap(String url) {
final HttpClient client = new DefaultHttpClient();
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
FilterInputStream fit = new FlushedInputStream(inputStream);
return BitmapFactory.decodeStream(fit);
} finally {
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {
client.getConnectionManager().shutdown();
}
return null;
}
/*
* An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
*/
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
}
最后,获取一张图片的流程就如下代码所示:
- /*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
- public Bitmap getBitmap(String url) {
- // 从内存缓存中获取图片
- Bitmap result = memoryCache.getBitmapFromCache(url);
- if (result == null) {
- // 文件缓存中获取
- result = fileCache.getImage(url);
- if (result == null) {
- // 从网络获取
- result = ImageGetFromHttp.downloadBitmap(url);
- if (result != null) {
- fileCache.saveBitmap(result, url);
- memoryCache.addBitmapToCache(url, result);
- }
- } else {
- // 添加到内存缓存
- memoryCache.addBitmapToCache(url, result);
- }
- }
- return result;
- }
/*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
public Bitmap getBitmap(String url) {
// 从内存缓存中获取图片
Bitmap result = memoryCache.getBitmapFromCache(url);
if (result == null) {
// 文件缓存中获取
result = fileCache.getImage(url);
if (result == null) {
// 从网络获取
result = ImageGetFromHttp.downloadBitmap(url);
if (result != null) {
fileCache.saveBitmap(result, url);
memoryCache.addBitmapToCache(url, result);
}
} else {
// 添加到内存缓存
memoryCache.addBitmapToCache(url, result);
}
}
return result;
}