您的位置:首页 > 移动开发 > Android开发

Android之图片缓存管理

2015-10-12 00:06 393 查看
如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
3、从网络下载图片,并更新到内存缓存和文件缓存。

后两个步骤纯碎属于业务逻辑,暂且不表,这里来看一下手Q使用的图片缓存管理策略。

说到缓存管理,首先谈一下java中的强引用和弱引用
强引用:最普遍的引用,若一个对象具有强引用,那么GC绝不会回收它。如A a = new A()
弱引用: 弱引用又分为以下三类:
软引用(SoftReference): 这类引用只有当内存空间不足GC才会回收它
弱引用(WeakReference): 这类引用拥有更短的生命周期,GC扫描过程中一旦发现了此类引用,不管当前内存是否足够,立即回收
虚引用(PhantomRefence): 这类引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,则任何时刻都可能被回收

下面来看看这样一个图片缓存类,为了更大限度使用缓存,它使用了强引用缓存(强引用)和弱引用缓存(弱引用)双重缓存,强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入弱引用缓存。**
ImageCache.java

public class ImageCache {
private static final String TAG = "ImageCache";

//CustomLruCache是一个继承了LruCache的继承类,它代表强引用缓存,它的缓存大小一般由业务方提供
private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size

//这里设置的是弱引用缓存以及它所占据的空间大小
private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MB
private final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();

public ImageCache(int memSize) {
memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);
QLog.d(TAG, "Memory cache size = " + memSize + "MB");
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {

//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
@Override
protected int sizeOf(String key, Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
//若是bitmap位图则直接计算它的大小
return bitmap.getRowBytes() * bitmap.getHeight();
}
return 0;
} else if (drawable instanceof AnimationDrawable) {
//若是逐帧动画,则首先获取它所有的帧数,再计算总共的大小
AnimationDrawable anim = (AnimationDrawable) drawable;
int count = anim.getNumberOfFrames();
int memSize = 0;
for (int i = 0; i < count; i++) {
Drawable dr = anim.getFrame(i);
if (dr instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
if (bitmap != null) {
memSize += bitmap.getRowBytes() * bitmap.getHeight();
}
}
}
return memSize;
}
return 0;
}
};
}

//从缓存中获取图片
public Drawable getImageFromMemCache(String key) {
Drawable memDrawable = null;
if (mMemoryCache != null) {
//首先从强引用缓存中获取图片,若找到的话,把元素移动到CustomLruCache的最后面,从而保证它在LRU算法中最后被删除?
//疑问,其实LinkedHashMap本身就存在LRU的算法机制,因此,get的时候,会自动移入到队列尾部
memDrawable = mMemoryCache.remove(key);
if (memDrawable != null) {
memDrawable = memDrawable.getConstantState().newDrawable();
mMemoryCache.put(key, memDrawable);
return memDrawable;
}
}

//强引用缓存中没有找到,开始在弱引用缓存中查找
WeakReference<Drawable> ref = mRefCache.get(key);
if (ref != null) {
//若找到的话,这里是否添加一步,将其从弱引用缓存移入强引用缓存中比较好
memDrawable = ref.get();
if (memDrawable == null) {
mRefCache.remove(key);
}
}
return memDrawable;
}

//添加图片到缓存,这里不理解为什么要向强引用缓存和弱引用缓存都要添加一份
public void addImageToCache(String data, Drawable drawable) {
// Add to memory cache
if (mMemoryCache != null && mMemoryCache.get(data) == null) {
mMemoryCache.put(data, drawable);
mRefCache.put(data, new WeakReference<Drawable>(drawable));
}
}

//从缓存中删除资源
public void removeImageFromCache(String data) {
if (mRefCache != null) {
mRefCache.remove(data);
}
if (mMemoryCache != null) {
mMemoryCache.remove(data);
}
}

public Drawable getImageFromDiskCache(String pathName) {
// TODO 暂不支持disk cache
return null;
}

public void clearCaches() {
// mDiskCache.clearCache();
mMemoryCache.evictAll();
mRefCache.clear();
}
}

整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {

//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
@Override
protected int sizeOf(String key, Drawable drawable) {
.........
}

当强引用缓存满时,会自动调用这个方法,这时候,将移除的元素添加到弱引用缓存中
@Override
protected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {
if (oldDawable != null) {
mRefCache.put(data, new WeakReference<Drawable>(oldDawable));
}
}
};
接下来看内存缓存类:ImageMemoryCache
public class ImageMemoryCache {

private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量
private static LruCache mLruCache;  //硬引用缓存
private static LinkedHashMap> 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(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(oldValue));
}
};
mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {
private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry> 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 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();
}
}
接下来看内存缓存类:ImageMemoryCache
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");
}
}

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);
}

private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}

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;
}

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 {
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;
}

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: