【Android Developers Training】 56. 更效率地加载大图片
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
图像的的形状和尺寸千变万化。在很多情况下它们比一般的应用UI所需要的尺寸更大一些。例如,在系统图库这个应用中,显示的照片是用你的Android设备拍摄的照片,它们比起屏幕的尺寸来说要大多了。
假设你现在只有有限的内存,那么在理想情况下你希望在内存中加载分辨率更小的图片。这个低分辨率版本的图片需要和将它显示出来的UI组件的尺寸相匹配。一个过高分辨率的图片并不会带来什么改善视觉体验,反而会消耗大量的内存空间,并且因为在运行时需要调整图片的尺度而导致应用性能表现欠佳。
这节课将带你学习通过加载一个大位图的减采样版本的图片到内存中,以此来防止应用的内存空间耗尽。
一). 读取位图的尺寸和类型
BitmapFactory类提供了一些解码方法(decodeByteArray(),decodeFile(),decodeResource()等)来为不同的源创建位图。应该基于你的图像数据源选择最合适的解码方法。这些方法尝试为构建好的图像分配空间,因此它很容易导致OutOfMemory异常。每种解码方法都有额外的参数选项可以让你通过BitmapFactory.Options类指定解码选项。在解码时,将inJustDecodeBounds属性设置为true可以防止内存溢出,如果不设置outWidth,outHeight和outMimeType的话,那么就会对该图像对象返回null。这个技术逼迫你在构造(和分配内存时)之前先读取图像的尺寸和图像数据的类型。
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组建的尺寸。
- 当前设备的屏幕尺寸和分辨率。
例如,如果要把一幅1024x768的图片加载到一个大小为128x96大小的ImageView,显然这么做事不值得的。
为了告诉解码器对图像进行减采样,加载一个更小的版本到内存中,在你的BitmapFactory.Options对象中将inSampleSize设置为true。例如,一个分辨率为2048x1536的图像加上inSampleSize设置为4的选项后,会产生一幅大小为512x384的图。将它加载进系统需要使用0.75MB而不是全尺寸图像所需要的12MB(假定位图配置为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:
从上述代码可以看到,在计算减采样因数时,以2为级数增加, 选择以2为级数的原因是解码器在减采样时也是选择以2为级数时最接近的那个数字做减采样的,具体的阐述可以查看: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); }
这个方法使得将任意大尺寸的图片加载到值显示100x100大小的ImageView中时,非常方便,就像下面代码所显示的那样:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
你也可以通过替换适当的BitmapFactory.decode*方法(如果需要的话),对来自其他源的照片做类似的处理过程。