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.setImageResoure 时, 持有的是 图片资源的 原始大小,没有压缩
2、使用ImageLoader 加载图片时,ImagerLoader 会用使用 options.inSampleSize
3、直接按ImageView的宽高比进行压缩, 能保证压缩后的图片与 ImageView 显示上 相同, 做到 最大程度的压缩bitmap, 但cpu 计时会耗时一点。 体现在界面上 就是 图片出现 的更晚了,也不会晚太多,
结论:
对于 资讯类app, 图片 非常多, OOM概率很大, 此时 在 cpu 和 内存 之间二选一, 更好的做法是 以 cpu运行时间 换取 内存空间, 带来的价值更大。
1.计算ImageView的宽高
int ImageView.getWidth()
int ImageView.getHeight()
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) : 读取资源文件中的图片。
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;
}