Android有效加载图片 之 有效加载大图片

在我们的Android应用中,经常需要用到各种图片,在图片的使用过程中,我们需要保持用户界面的友好响应同时也要避免超出应用的内存限制。如果在图片的处理中不仔细,图片就会迅速耗尽应用的内存,导致OOM:

java.lang.OutofMemoryError: bitmap size exceeds VM budget.


图片加载的问题如此棘手主要有以下一些原因导致:

  • 移动设备的系统资源有限。Android设备为每个应用分配的可使用内存最少只有的16MB。
  • 图片资源占用的内存较大,特别是对于一些高分辨率的图片,如照片。Galaxy Nexus手机拍摄的照片分辨率高达2592*1936px(5百万像素)。如果图片按ARGB_8888的规格则加载这张照片需要19MB的内存(2592*1936*4 bypes),在一些设备上将直接耗尽应用的内存。
  • Android应用常常要求同时显示大量图片。如组件ListView、GridView、ViewPager等等常常需要在屏幕上同时显示多张图片,则内存中需要将显示的图片及可能要被显示的图片加载到内存中。

高效加载大图片

不同图片总是会有不同的形状、不同的大小,在很多情况下,这些图片会大于我们程序所需要的大小。比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。
考虑到内存限制,推荐的做法是将图片压缩。压缩后的图片大小应该和用来展示它的控件大小相同,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。
下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。
读取图片的尺寸和类型

BitmapFactory类提供了多个用于创建Bitmap对象的方法(decodeByteArray, decodeFile, decodeResource等)。我们应该根据图片的来源选择合适的方法。这些方法会尝试为已构建的bitmap分配内存,这时就会很容易导致OOM出现。这每一个解析方法都提供了一个BitmapFactory.Options参数。将这个参数的inJustDecodeBounds设置为true就可以在解析时禁止为bitmap分配内存,返回值也不再是Bitmap对象而是null,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个方法使我们可以在不为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;

为了避免产生OOM,我们最好在每次解析图片之前对图片的尺寸进行检查,除非你 非常信任图片的来源,保证这些图片都不会超出你程序的可用内存。

加载一个压缩版的图片

现在图片的大小已经知道了,我们就可以决定是把整张图片加载到内存中还是加载一个压缩版的图片到内存中。以下几个因素是我们需要考虑的:

  • 预估加载整张图片所需使用的内存。
  • 你愿意为加载这张图片花费的内存大小。
  • 显示这张图片的ImageView或目标控件的尺寸。
  • 当前设备的屏幕尺寸和密度。
比如你的ImageView只有128*96像素,把一张1024*768像素的图片完全加载到内存中显然是不值得的。

如何才能对图片进行压缩呢?将你的BitmapFactory.Options对象inSampleSize属性设置为true,即告诉编译器加载一个较小版本的图片到内存中。比如,一个分辨率为2048*1536的图片,把它的inSampleSize设置为4则可以把这张图片压缩成512*384px。原本加载到内存中要占用13M的内存,压缩后只需要占用0.75MB的内存了(假设图片是ARBG_8888类型,即每像素4个字节)。下面的方法可以根据目标宽高计算出计算出一个合适的inSampleSize值:

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

使用这个方法,需要先将inJustDecodeBounds设置为true,解析一次图片,计算得到合适的inSampleSize。然后将inJustDecodeBounds设置为false再解析一次图片:

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);
}
下面的代码非常简单地将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。

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


你可能感兴趣的:(Android,JAVA)