Bitmap是Android应用程序引起OOM的罪魁祸首之一,当我们从网络上下载图片的时候无法知道网络图片的准确大小,所以为了节约内存,一般会在服务器上缓存 一个缩略图,提升下载速度。除此之外,我们还可以在本地显示图片前将图片进行压缩,使其完全符合imageview的大小,这样可以最大限度避免内存浪费。
本文基本思路:
(1)获取ImageView的宽和高。
(2)使用inJustDecodeBounds获取bitmap的长和宽。
(3)根据bitmap的长款和ImageView的长和宽,计算出压缩比例inSampleSize的大小。
(4)使用inSampleSize,加载一个比ImageView稍大一点的缩略图a。
(5)使用Bitmap.createScaseBitmap再次压缩A,将缩略图A生成需要的缩略图B。
1.计算ImageView的宽高
int ImageView.getWidth() int ImageView.getHeight()
这两个方法可以得到ImageView的实际大小,单位为pix,需要在view加载完之后再调用。如果不知道什么时候加载完,可以使用下面方法在run()中获取:
final ImageView iv = new ImageView(context); iv.post(new Runnable() { @Override public void run() { int w = iv.getWidth(); int h = iv.getHeight(); } })
2.通过BitmapFactory计算得到压缩后的bitmap对象。
2.1 BitmapFactory.Options介绍
BitmapFactory提供了多种方式根据不同的图片源来创建Bitmap对象:
(1)Bitmap BitmapFactory.decodeFile(String pathName, Options opts): 读取SD卡上的图片。
(2)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 读取网络上的图片。
(3)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 读取资源文件中的图片。
这三个函数默认会直接为bitmap分配内存,我们通过第二个参数Options来控制,让它不分配内存,同时可以得到图片的相关信息。具体参考:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName,options); int imageHeight = options.outHeight; int imageWidth = options.outWidth;
这样就可以在不分配内存的情况下直接得到图片的宽高。
2.2 计算图片压缩比例(采样率)
通常如果图片是针对某种分辨率设计的,直接decode图片不会有什么问题。但是,如果图片不是特定设计,且比较大的话,容易造成OOM。
假设,一个ImageView大小为512*384,有一张2048*1536的图片需要显示,那么如果加载整张图片,那就造成了浪费,这时候就需要采用不同的采样率对原图片进行一定的压缩。
计算inSampleSize的值有两种方法:
方法一:
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; }
方法二:
private 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 halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
需要注意的是,inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂。因为按照2的次方进行压缩会比较高效和方便。 方法一计算出来的inSampleSize值可能不是2的整数次幂,不如计算出来的值是inSampleSize=7,这时会被decode函数向下取为 inSampleSize=4。所以我们一般采用方法二进行计算。
此时,将设置了inSampleSize的options传给BitmapFactory.decode函数去获取图片,得到的会是一张比ImageView稍大的图片,不过这个图片要比原图小了。
public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; Bitmap src = BitmapFactory.decodeFile(pathName, options); return createScaleBitmap(src, reqWidth, reqHeight); }
2.3 得到符合ImageView大小的图片
这是通过Bitmap Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)函数来实现的。
这个函数返回一个按要求进行拉伸或者缩小后的bitmap.
private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) { // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响,我们这里是缩小图片,所以直接设置为false Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); if (src != dst) { // 如果没有缩放,那么不回收 src.recycle(); // 释放Bitmap的native像素数组 } return dst; }