Bitmap的高速加载
Android的Bitmap使用不当经常会出现OOM,因此图片的加载和缓存是非常重要的一环
四类加载方法
BitmapFactory提供了四类加载图片的方法,从不同来源加载出一个Bitmap对象,最终的实现是在底层实现,对应BitmapFactory的几个native方法。四类方法支持BitmapFactory.Options参数
- decodeFile:从文件系统加载,间接调用decodeStream
- decodeResource:从资源加载,间接调用decodeStream
- decodeStream:从输入流加载
- decodeByteArray:从字节数组加载
参考代码:
```
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inInputShareable = true;
opts.inPurgeable = true;
BitmapFactory.decodeFile(path, opts);
BitmapFactory.decodeResource(getResources(), R.drawable.img);
BitmapFactory.decodeStream(getResources().openRawResource(R.drawable.img));//这里换了方法
```
BitmapFactory.Options缩放
高效加载Bitmap的核心思想是采用BitmapFactory.Options来缩放图片,即按照一定的采样率来加载所需尺寸的图片
例如通过ImageView加载图片,很多时候ImageView并没有图片原始尺寸那么大,这时就需要加载缩小后的图片。
缩放主要用到了inSampleSize, 采样率
- 必须大于1有缩放效果
- 设置inSampleSize = n 缩放比例为1/ n的平方,因为采样率同时作用于宽高
- 官方文档建议inSampleSize取值应该为2的指数,比如1,2,4,8,16等。如果不为2的指数,会向下取整。
例如一张1024*1024的ARG8888格式,内存为1024*1024*4(4M)。设置inSamplSize = 2,缩放后512*512*4 即1M
如何获取采样率:
- 设置inJustDecodeBounds = true,获取图片原始宽高,此处不会真正加载图片。
- 依据采样率规则结合目标View的所需大小计算 inSampleSize
- 设置inJustDecodeBounds = false,加载图片
常用的获取采样率的代码
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
//First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, Options);
//Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory,decodeResource(res, resId, options);
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
//Raw height and width of image
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;
// Caculate the largest inSampleSize value that is a power of 2 and
// keeps both height and width larger than the requested height and width
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth){
inSampleSize *= 2;
}
}
return inSampleSize;
}
Android中的缓存策略
目前常用的缓存算法是LRU(Least Recently Used) 近期最少使用:当缓存快满时,会淘汰近期最少使用的缓存目标
系统中采用LRU算法的缓存有两种:LruCache(内存缓存)和DiskLruCache(磁盘缓存)
LruCache 内部通过LinkedHashMap来实现LRU,提供了get, put, remove方法
总容量大小是
62 public class LruCache<K, V> { 63 private final LinkedHashMap<K, V> map; ...... 104 /** 105 * Returns the value for {@code key} if it exists in the cache or can be 106 * created by {@code #create}. If a value was returned, it is moved to the 107 * head of the queue. This returns null if a value is not cached and cannot 108 * be created. 109 */ 110 public final V get(K key) { 111 if (key == null) { 112 throw new NullPointerException("key == null"); 113 } 114 115 V mapValue; 116 synchronized (this) { 117 mapValue = map.get(key); 118 if (mapValue != null) { 119 hitCount++; 120 return mapValue; 121 } 122 missCount++; 123 } 124 125 /* 126 * Attempt to create a value. This may take a long time, and the map 127 * may be different when create() returns. If a conflicting value was 128 * added to the map while create() was working, we leave that value in 129 * the map and release the created value. 130 */ 131 132 V createdValue = create(key); 133 if (createdValue == null) { 134 return null; 135 } 136 137 synchronized (this) { 138 createCount++; 139 mapValue = map.put(key, createdValue); 140 141 if (mapValue != null) { 142 // There was a conflict so undo that last put 143 map.put(key, mapValue); 144 } else { 145 size += safeSizeOf(key, createdValue); 146 } 147 } 148 149 if (mapValue != null) { 150 entryRemoved(false, key, createdValue, mapValue); 151 return mapValue; 152 } else { 153 trimToSize(maxSize); 154 return createdValue; 155 } 156 } 157 158 /** 159 * Caches {@code value} for {@code key}. The value is moved to the head of 160 * the queue. 161 * 162 * @return the previous value mapped by {@code key}. 163 */ 164 public final V put(K key, V value) { 165 if (key == null || value == null) { 166 throw new NullPointerException("key == null || value == null"); 167 } 168 169 V previous; 170 synchronized (this) { 171 putCount++; 172 size += safeSizeOf(key, value); 173 previous = map.put(key, value); 174 if (previous != null) { 175 size -= safeSizeOf(key, previous); 176 } 177 } 178 179 if (previous != null) { 180 entryRemoved(false, key, previous, value); 181 } 182 183 trimToSize(maxSize); 184 return previous; 185 }
典型的初始化Code
mLruCache = new LruCache<String, Bitmap>(Math.round(Runtime.getRuntime().maxMemory() / 10) { protected int sizeOf(final String key, final Bitmap value) { return value.getRowBytes() * value.getHeight(); } };
DiskLruCache 磁盘缓存,可查看Android 4.1.1 libcore下面的DiskLruCache源码和okhttp的DiskLruCache源码
常用的图片缓存库:Fresco,Glide,Picasso