高效加载图片

时间:2023-02-01 20:49:36

学习在保持用户界面(UI)组件响应和避免超过应用内存限制的情况下,使用普通技术处理图片的加载的方法。如果你不注意,图片会快速的消息应用程序与分配的有效内存致使程序因为“java.lang.OutofMemoryError: bitmap size excends VM budget”异常而崩溃。

这里有几个在应用程序中加载图片时要注意的原因:

  • 移动设备通常有系统资源限制。android设备上简单应用一般分配16M的有效内存。安卓兼容性文档 3.7版本。虚拟机的兼容性为各种屏幕尺寸和密度提供了所需的最小应用程序存储器。应用程序因该在最低内存限制下进行最优的操作。然而现在很多设备已经有了更高的配置。

  • Bitmaps占用大量内存,尤其是丰富的图片如照片。例如,在Galaxy Nexus的摄像头拍到2592x1936像素(5像素)。如果用位图配置argb_8888(默认从Android 2.3开始)然后加载这个图像到内存大约需要19mb内存(2592×1936×4字节),马上就耗尽了每个应用程序的限制,一些设备。

  • android应用程序经常需要一次性加载多个图片。例如:ListView , GridView, ViewPager通常需要一次在屏幕上加载多个图片。同时准备随手指滑动还没有显示的更多的图片。

图片用各种形状和大小,图片有丰富的信息如照片。在系统的图库应用中展示的图片,他们的手机照相机的分辨率要远远高于手机屏幕的分辨率。

由于有限的使用内存理想情况下,我们只需要在内存中加载一个较低分辨率的版本的图片。最低本的图片应该和显示图片的的组件的大小相匹配。在这种情况下使用高分辨率的图片没有什么明显的好处。由于要进行缩放,会占用宝贵的内存,带来额外的性能开销。

读取不同大小和类型的图片

BitmapFactory类提供几种不同的编码方法(decodeByteArray(), decodeResourece(), decodeResource(),…)从不同类型资源中创建一个图片。根据不同图片资源选择不同的编码方法。这些方法试图为构建位图分配内存,因此可以很容易地导致OutOfMemory异常。每个方法都添加参数让你通过配置BitmapFactory.Options类。设置Options的inJustDecodeBounds属性为true来避免在编码时真的的分配内存。这样允许您在不分配内入的情况下获取图片的结构读取图片对象的数据的尺寸和类型。

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或者其他UI组件的大小。
  • 当前设备的分辨率和尺寸

例如,不值得为1024x768像素图像加载到内存如果它最终将在ImageView中一128x96像素缩略图显示。
 

通过设BitmapFactory.Options.insampleSize为true,告诉编码器得到一个少用内存的缩放图。例如,一个图片的尺寸为2048X1536,在编码时设置insamplesize = 4时得到图片大小是512x384.加载到内存中需要0.75M而不用12M内存。这里有一个计算缩放比例的方法:

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,利用上边的方法获取缩放比例并将值赋值给Options.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);
}

这个方法可以很容易的加载任意尺寸的图片到ImageView中。例如下面的方法:

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