大图&多图加载方案

大图&多图加载方案

四月 27, 2018

按需加载

inJustDecodeBounds

BitmapFactory.Options.inJustDecodeBounds

设置为 true 就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。
虽然 Bitmap 是 null 了,但是 BitmapFactory.Options的outWidth、outHeight 和 outMimeType 属性都会被赋值

考虑因素

  • 预估一下加载整张图片所需占用的内存;
  • 为了加载这一张图片你所愿意提供多少内存;
  • 用于展示这张图片的控件的实际大小;
  • 当前设备的屏幕尺寸和分辨率

比如,你的 ImageView 只有128x96像素的大小,只是为了显示一张缩略图,这时候把一张1024x768像素的图片完全加载到内存中显然是不值得的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public 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 heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}

mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

图片缓存-LruCache

考虑因素

  • 设备可以为每个应用程序分配多大的内存;
  • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载;
  • 设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 1080P、1440P) 比起一个较低分辨率的设备(例如 480P、720P),在持有相同数量图片的时候,需要更大的缓存空间;
  • 图片的尺寸和大小,还有每张图片会占据多少内存空间;
  • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片;
  • 能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private LruCache<String, Bitmap> mMemoryCache;  

@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}

public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
// 在后台加载图片。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
}