概述
Bitmap在Android中指的是一张图片,可以是各种常见的图片格式。可以使用BitmapFactory类来加载一个图片。BitmapFactory提供了四类方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象。
高效加载Bitmap
高效加载Bitmap的思想是,采用Bitmap.Options来加载所需尺寸的图片。通过options按一定采样率来加载缩小后的图片,可以降低内存占用,从而来一定程度上避免OOM。
通过BitmapFactory.Options来缩放图片,用到它的inSampleSize参数(采样率)。inSampleSize必须是大于1的整数(值应总是2的指数,如果不是,会被向下取整并选择一个最接近2的指数来代替。小于1时会被当做1。),设置后可以将图片的宽高都缩小(1/inSampleSize)倍,相应地,缩放比例为(1/inSampleSize的2次方)。
获取采样率的流程:
①、将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片(设为true后,BitmapFactory只会解析图片的宽高信息,而不会真正加载图片,故这个操作是轻量级的)。
②、从BitmapFactory.Options中取出图片的原始宽高信息,对应于outWidth和outHeight。
③、根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
④、将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
代码如下:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize >= reqWidth)) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
在上面的calculateInSampleSize是计算图片的采样率。使采样率为“宽或高的最大缩放值”中的较小的一个。
内存缓存(LruCache)
目前常用的一种缓存算法是LRU(Least Recently Used,最近最少使用)。思想是,当缓存满时,优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LruCache(实现内存缓存)和DiskLruCache(实现存储设备缓存)。
LruCache是Android 3.1提供的一个缓存类。内部采用
LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象。使用LinkedHashMap的原因是它保留了插入顺序,使得输出顺序和输入顺序一致,在put方法和get方法执行create之后,会调用到trimToSize方法来根据需要移除之前的缓存,使用LinkedHashMap有助于优先移除最近最少使用的缓存。
使用LruCache只需要提供缓存的总容量大小并重写sizeOf方法即可。在需要的情况下,也可以重写entryRemoved方法,在其中完成一些资源回收工作。一个使用的例子如下:
private static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
private static int cacheSize = maxMemory / 8;
private static LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
下面看下它的put方法:
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
比较简单。先判断key和value是否为null,是的话抛异常。否则计算要缓存的内容的大小,然后使用map.put方法将其加进去。map.put方法会替换并返回当前key之前已经保存的value(如果没有就返回null)。若返回的不是null,则说明map.put方法执行的是替换,故把size相应减小。同时回调entryRemoved方法。最后调用trimToSize方法,判断是否需要移除一些缓存以使缓存容量符合maxSize。可以看到put方法中添加缓存的代码块是由synchronized修饰的,故是线程安全的。
再看下get方法:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
get方法也比较简单。首先从map中提取key对应的缓存,如果存在就直接返回。如果不存在,就尝试去创建一个,我们可以通过重写create方法来进行创建(猜测用途是从存储设备中获取并缓存进内存)。创建成功后就把它添加进map中,思路和put方法中差不多。
存储设备缓存(DiskLruCache)
基本用法:
①、首先创建DiskLruCache实例:
DiskLruCache mDiskLruCache = DiskLruCache.open(File directory, int appVersion, int valueCount, long maxSize);
②、获取SnapShot:
SnapShot snapShot = mDiskLruCache.get(key);
③、对snapShot第一次判空:
I、若为空,则从网上下载:
DiskLruCache.Editor editor = mDiskLruCache.edit(key); //打开Editor
OutputStream os = editor.newOutputStream(0);
...... //从网上下载,写入os输出流。
if(下载成功){
editor.commit();
} else{
editor.abort();
}
mDiskLruCache.flush();
II、若不为空,则取缓存:
FileInputStream fis = snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fis.get(0);
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
以上是大致总结,更具体看郭霖博文:
http://blog.youkuaiyun.com/guolin_blog/article/details/28863651