图片的大小形状千变万化。在很多情况下图片都比一个app的UI所需要展示的大小大很多。例如,系统的相册应用所展示的用系统相机拍摄的相片,这些相片比手机屏幕的分辨率大得多。
假设你的app所使用的内存有限制,理想情况下你只想要在内存中加载一个较低分辨率的图片。同事这个低分辨率的图片要匹配用来显示它的UI组件的大小。高分辨率的图片并不能带来任何可见的好处,但是仍然会消耗珍贵的内存同时还会导致额外的性能开销。
本课将会讲解如何在不溢出app内存限制的情况下通过在内存中加载小版本图片来解码大bitmaps。
读取Bitmap的尺寸和类型
BitmapFactory
类提供几种解码方法(decodeByteArray()
, decodeFile()
,decodeResource()
, 等等.)来通过多种多样的资源文件创建 Bitmap
。根据你的图片数据源选择一个最合适的解码方法。这些方法为bitmap分配内存,这样就很容易导致OutOfMemory
异常。每种类型的解码方法都有额外的参数以让你通过BitmapFactory.Options
类指定解码的选项。在解码时设置inJustDecodeBounds
属性为 true
能够避免内存分配,同时还能够获取到outWidth
, outHeight
和 outMimeType
的值,此时解码方法返回的bitmap对象为null。这种发式允许你在构建这个bitmap(和内存分配)之前读取它的大小和类型。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
为了避免
java.lang.OutOfMemory
异常,可以在每次解码它之前取得它的尺寸,除非你完全肯定这个图片源的大小的消耗能够在可用内存之内。
在内存中加载图片的压缩版本
现在图片的尺寸已经知道了,我们就能够决定是否加载原图还是图片的缩小版本。下面有几点需要考虑:
- 预估加载完整图片所需的内存使用量。
- 你想要为加载此图片分配的内存的总量。
- 要载入的目标
- 当前设备的屏幕大小和密度。
128x96像素的缩略图,那么加载一个1024x768 像素的图片进内存是完全不值得的。
告诉解码者压缩图片,加载一个压缩版图片进入内存,需要在BitmapFactory.Options对象中设置 inSampleSize
为 true
。例如,一个图片,分辨率为 2048x1536以 inSampleSize
= 4为参数被解码,将会产生一个分辨率大概为512x384的bitmap。加载它进内存只需要使用0.75MB 内存,而非加载完整图片所需的12MB (假定bitmap的配置为ARGB_8888
)。下面是一个基于目标长度和宽度计算压缩图片尺寸值的方法:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 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;
// Calculate 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;
}
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize
inSampleSize
public static 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);
}
下面这种方式很简单的实现了加载一个很大的图片到一个100x100像素缩略图的ImageView中:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));