图片的大小形状千变万化。在很多情况下图片都比一个app的UI所需要展示的大小大很多。例如,系统的相册应用所展示的用系统相机拍摄的相片,这些相片比手机屏幕的分辨率大得多。

假设你的app所使用的内存有限制,理想情况下你只想要在内存中加载一个较低分辨率的图片。同事这个低分辨率的图片要匹配用来显示它的UI组件的大小。高分辨率的图片并不能带来任何可见的好处,但是仍然会消耗珍贵的内存同时还会导致额外的性能开销。

本课将会讲解如何在不溢出app内存限制的情况下通过在内存中加载小版本图片来解码大bitmaps。

读取Bitmap的尺寸和类型


BitmapFactory 类提供几种解码方法(decodeByteArray()decodeFile(),decodeResource(), 等等.)来通过多种多样的资源文件创建 Bitmap 。根据你的图片数据源选择一个最合适的解码方法。这些方法为bitmap分配内存,这样就很容易导致OutOfMemory 异常。每种类型的解码方法都有额外的参数以让你通过BitmapFactory.Options 类指定解码的选项。在解码时设置inJustDecodeBounds 属性为 true 能够避免内存分配,同时还能够获取到outWidthoutHeight 和 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 异常,可以在每次解码它之前取得它的尺寸,除非你完全肯定这个图片源的大小的消耗能够在可用内存之内。

在内存中加载图片的压缩版本


现在图片的尺寸已经知道了,我们就能够决定是否加载原图还是图片的缩小版本。下面有几点需要考虑:

  • 预估加载完整图片所需的内存使用量。
  • 你想要为加载此图片分配的内存的总量。
  • 要载入的目标

ImageView

  • 当前设备的屏幕大小和密度。

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 inSampleSizeinSampleSize


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));

BitmapFactory.decode*