图片有大有小有各种形状。很多情况下图片比界面所需要的尺寸要大。例如,用Gallery显示图片时,你的手机屏幕分辨率有时会低于你手机摄像头所拍摄图片的清晰度。
手机的工作内存是有限制的,为降低内存使用,理想状态下你会只想加载一个低清晰度的图片。这个低清晰度图片应该和要显示图片的UI元件的尺寸匹配一致。如果仍然用高清晰度的图片,不仅不会带来什么视觉上的更佳效果,而且还占用更多宝贵的内存,而且还会导致额外的性能开销。这节课带你过一遍如何通过解码大图片来控制应用不超过内存限制。
- - 读取位图尺寸和类型
BitmapFactory类提供了几种解码方法(decodeByteArray(), decodeFile(), decodeResource(), etc.),可以以不同的源创建Bitmap对象。根据图片数据来选择合适的解码方法。这些方法会为构建bitmap而分配内存,一不小心就会跑出OutOfMemory异常。每个解码方法类型都有一个额外的签名,通过BitmapFactory.Options你可以定制解码的选项。把inJustDecodeBounds设置为ture,会避免内存分配,而设置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;
为了避免跑出OutOfMemory异常,要在解码之前先检查一下该位图的尺寸大小,除非你有绝对的信心相信这张图片不会导致内存溢出。
- - 加载一个缩小版图片
既然已经知道了图片的尺寸,就可以决定到底是原样加载完整图片还是加载一个做过抽样的缩小版图片。以下是几点要考虑的因素。
- - 估计一下加载完整图片所占内存
- - 满足了其他内存需求后,你的应用所留给用于加载图片的内存大小
- - 要显示这个图片的ImageView或其他UI组件的尺寸大小
- - 当前手机设备的屏幕尺寸和分辨率。
举个例子,为了在ImageView中显示一个128乘96像素大小的缩略图而激昂一个1024乘768像素的图片加载到内存,这显然是不值得的。
在BitmapFactory.Options中将inSampleSize设为true,就是告诉解码器要对图片进行抽样,要加载一个缩略图而非完整图片。加入bitmap配置为ARGB_8888,加载一个2048乘1536的图片要占12M内存,而如果在对其解码时设置抽样参数inSampleSize为4,即按1/4的比例抽样,从而得到一个512乘384的位图,那么所占内存只有0.75M。以下是一个根据图片的目标宽高计算抽样尺寸大小的方法。
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // 原始图片宽高 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 计算目标宽高与原始宽高的比值 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 选择两个比值中较小的作为inSampleSize的值 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; }
注意:将inSampleSize设为2,对于解码器而言,其解码更快且更高效。
下面给出了一个方法,首先将inJustDecodeBounds设为true进行解码。然后设置一个新的inSampleSize值并将inJustDecodeBounds设为false:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解码,inJustDecodeBounds=true,检查尺寸大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 计算inSampleSize抽样比 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 进行抽样解码 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }很简单,下面两行代码就将一个大尺寸位图变为100乘100的缩略图显示在ImageView中
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));