Loading Large Bitmaps Efficiently 高效的加载大图
图片可能以各种各样的形状,尺寸展现出来,大多数情况下它们的展现方式,是超出一般应用程序的UI需求的。举例来说,系统的”相册”应用中展现的图片(Android设备拍摄的相片)是超过你的设备屏幕的分辨率的。
假设你在一个内存有限的环境下工作,理想情况下你只需要加载一个低分辨率的版本进入内存。相对较低的分辨率的图片应该匹配UI组件的需求,并且将它们显示出来。 在这种情况下(需求的分辨率不需要很高),一张高分辨率的图片不会在视觉上给人更好的感受,因为你显示的效果不需要那么好。但是这张图片会占用很多内存,当你在快速缩放的时候它会给视觉显示任务带来负担。
这篇文章将教会你 在解析高分辨率图片的时候 通过 处理将其变为相对低一点的分辨率的图片然后加载进内存,这样就不会超出每个应用程序的内存限制了。
Read Bitmap Dimensions and Type 获取图片的尺寸和类型
BitmapFactory这个类提供了几种解析图片的方法 (decodeByteArray(), decodeFile(), decodeResource() )用来从不同的数据源创建一个Bitmap对象。 基于你的数据源,来选择合适的方法。
这些方法在执行过程中会为已经创建的bitmap分配内存,所以会很容易的出现OOM(内存溢出)异常。
每一个方法都会自己的特征让你来区分它们。通过 BitmapFactory.Options类。在解析的过程中,设置inJustDecodeBounds属性 为 true,通过返回值 null 就可以避免内存分配。然后设置outWidth,outHeight,outMimeType.通过这种方式你就可以在图片创建(内存分配)之前读取图片的尺寸和类型。
下面是代码示例
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;
为了避免OOM异常,你必须在加载和解析图片的时候检查一下尺寸,除非你很清楚的知道你要加载的图片是在合理的内存范围之内的。
Load a Scaled Down Version into Memory 加载一个缩放的图片进入内存
现在我们已经知道了图片的尺寸,它们可以用来加载(或者缩放之后)进入内存了,以下这些因素是你要考虑的。
- 当你加载一整张图片进入内存的时候,你必须估算一下内存的消耗。
- 包括了应用的其他内存消耗之后,你可以给你当前要加载的图片分配多少内存。
- 用来显示图片的 目标控件(ImageView,或者UI组件)的尺寸。
- 当前设备的屏幕尺寸和分辨率。
举例来说,当你要 将一个1024x768像素的图片显示在 128x96像素的ImageView上的时候是非常浪费的。
在 BitmapFactory.Options 对象中设置 inSampleSize 为 true,就可以在解析图片的时候,加载一个缩小的图片进入内存。 举例来说 如果你把 inSampleSize的值设置为4的话,一张 2048x1536分辨率的图片就可以被处理为 大约512x384分辨率的bitmap。那么原先一张加载入内存需要12M的图片就仅仅用了0.75M(假设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 documentation.
这句话目前不知道怎么翻译
用下面这个方法解析一张图片的时候 首先将 inJustDecodeBounds 设置为 true,计算出inSampleSize并且设置options的inSampleSize 然后将inJustDecodeBounds 设置为false;
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
//第一次 先将 inJustDecodeBounds=true 查看尺寸,计算inSampleSize的值。
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//计算完了之后 设置为 false
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
下面这个方法可以很简单的让一张超过ImageView尺寸的图片以缩略图的形式显示出来。
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
你也可以模仿这个过程,定制一些 别的方法 BitmapFactory.decode*。