在Android应用中加载Bitmaps的操作是需要特别小心处理的,有下面几个方面的原因:
- 移动设备的系统资源有限。Android设备对于单个程序至少需要16MB的内存。Android Compatibility Definition Document (CDD), Section 3.7. Virtual Machine Compatibility 中给出了对于不同大小与密度的屏幕的最低内存需求。 应用应该在这个最低内存限制下去优化程序的效率。当然,大多数设备的都有更高的限制需求。
- Bitmap会消耗很多内存,特别是对于类似照片等内容更加丰富的图片。 例如,Galaxy Nexus的照相机能够拍摄2592x1936 pixels (5 MB)的图片。 如果bitmap的图像配置是使用ARGB_8888 (从Android 2.3开始的默认配置) ,那么加载这张照片到内存大约需要19MB(2592*1936*4 bytes) 的空间,从而迅速消耗掉该应用的剩余内存空间。
- Android应用的UI通常会在一次操作中立即加载许多张bitmaps。 例如在ListView, GridView 与 ViewPager 等控件中通常会需要一次加载许多张bitmaps,而且需要预先加载一些没有在屏幕上显示的内容,为用户滑动的显示做准备。
高效加载大图:如何加载一个缩小版的图片,从而避免超出程序的内存限制
首先我们需要读取到位图的尺寸和类型
BitmapFactory提供了一些解码(decode)的方法(decodeByteArray(), decodeFile(), decodeResource()等),用来从不同的资源中创建一个Bitmap。 我们应该根据图片的数据源来选择合适的解码方法。 这些方法在构造位图的时候会尝试分配内存,因此会容易导致OutOfMemory的异常。每一种解码方法都可以通过BitmapFactory.Options设置一些附加的标记,以此来指定解码选项。设置 inJustDecodeBounds 属性为true可以在解码的时候避免内存的分配,它会返回一个null的Bitmap,但是可以获取到 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;
为了避免java.lang.OutOfMemory的异常,我们需要在真正解析图片前检查它的尺寸(除非你能确定这个数据源提供了准确无误的图片且不会导致占用过多内存)
接下来我们需要加载一个按比例缩小的版本到内存中
通过上面的步骤我们已经获取到了图片的尺寸,这些数据可以帮我们决定应该加载整个图片还是加载一个缩小的版本到内存中。有下面这一些因素需要考虑:
- 评估加载完整图片所需要耗费的内存。
- 程序在加载这张图片时可能涉及到其他内存需求。
- 呈现这张图片的控件的尺寸大小。
- 屏幕大小与当前设备的屏幕密度。
例如,如果把一个大小为1024*768像素的图片显示到大小为128*96像素的ImageView上吗,就没有必要把整张图都加载到内存中。
为了告诉解码器去加载一个缩小版本的图片到内存中,需要在BitmapFactory.Options 中设置 inSampleSize 的值。例如, 一个分辨率为2048x1536的图片,如果设置 inSampleSize 为4,那么会产出一个大约512x384大小的Bitmap。加载这张缩小的图片仅仅使用大概0.75MB的内存,如果是加载完整尺寸的图片,那么大概需要花费12MB(前提都是Bitmap的配置是 ARGB_8888)。下面有一段根据目标图片大小来计算Sample图片大小的代码示例:
public static int calculateInSample(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 halfHeight = height / 2;
final int halfWidth = width / 2;
//Calculate the largest inSampleSize value that is a power of 2 and keep both
//height an width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / imSampleSize) > reqWidth){
inSample *= 2;
}
}
return inSampleSize;
}
设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数
为了使用该方法,首先需要设置inJusDecodeBounds为true,把options的值传递过来,然后设置inSampleSize的值并设置inJustDecodeBounds为false,之后重现调用相关的解码方法。
public static Bitmap decodeSampledBitmapFromRescource(Resource 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
opions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//Decode bitmap with inSampleSize set
options,inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
使用上面的方法可以简单地加载一张任意大小的图片。如下面的代码样例显示了一个接近100*100像素的缩略图:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResource(), R.id.myimage, 100, 100));
我们可以通过替换合适的BitmapFactory.decode*方法来实现一个类似的方法,从其他的数据源解析Bitmap。
2640

被折叠的 条评论
为什么被折叠?



