上一节,我们说的是如何从信息源获得一个Bitmap。然后,我们直接将Bitmap放到了ImageView中。看似简单的过程其实充满了安全隐患。为啥呢?因为Bitmap是个贪婪的类。为了完成自己的任务,它会肆意地向系统索要内存,不管系统的死活。所以如果我们需要加载的图片大到一定程度时,就会发生OOM的异常。这也是Bitmap的一个大坑。那么我们如何解决这个问题呢?Google官方提供了一个简单的图像压缩的方案,我把它记录在此。
首先,从上节我们可以看到,BitmapFactory在生成bitmap时,有一个每种方式必备的参数,Options。Options里面包含了很多关于图片,关于设备的信息。我们在加载图片时可以用过控制它的outWidth和outHeight来控制生成Bitmap的宽高。
但是,这样的方法非常不灵活。虽然我们可以设定一个确保不会OOM的定值,但是这样很不合理。如果我们要加载的图片非常小呢,我们设定了固定的宽高,强行拉大了它,会使图片显示效果很差。因此,我们希望的方法是,我们在加载之前就知道这个图片原本的大小,然后,我们根据实际的情况来决定如何压缩它,或者不进行什么特殊处理。
正好,Google官方给我们提供了这样一个特殊的属性——options.inJustDecodeBounds:
public boolean inJustDecodeBounds
If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.
如官方描述,这相当于一个预加载。当inJustDecodeBounds被设为true时它不会返回一个bitmap,但它会先加载一个边框出来,开发人员可以通过它,在不为加载图片分配内存的情况下访问图片,并获取一些有用的信息。比如:原图的宽高。
在这个方法的帮助下,我们就可以先预加载一遍图片,获取宽高。然后根据宽高,考虑压缩,以避免OOM,然后对图片进行处理,最后再真正地加载出bitmap。
下面是一个根据现有图片大小,计算压缩比例的方法。
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; }
下面的就是根据图片的原本大小,进行适当地压缩然后加载图片的过程。
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); }
其中,inSampleSize表示图片加载时的缩小倍数。
public int inSampleSize If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
今晚先这样~Duang~!