前些天参加面试时,被问到的一个问题是:Android里面压缩图片有什么方法。当时只用了瀑布流照片墙的一些方法来回答,还是不够清楚,答得不太满意。现在就来总结一下Android中压缩图片的一些基本知识和流程。
一、引言
在移动开发中,图片压缩是一个非常重要的方面,因为设备内存有限,如果不对图片进行处理的话,可能会引起OOM;在瀑布流等图片经典demo中也使用了相应的处理,压缩图片等等。
现在先来了解一下Android图片的存在方式:
1.文件形式(即以二进制形式存在于硬盘上)
2.流的形式(即以二进制形式存在于内存中)
3.Bitmap形式
这三种形式的区别: 文件形式和流的形式对图片体积大小并没有影响,也就是说,如果你手机SD卡上的如果是100K,那么通过流的形式读到内存中,也一定是占100K的内存,注意是流的形式,不是Bitmap的形式,当图片以Bitmap的形式存在时,其占用的内存会瞬间变大, 增大的倍数是很可观的
检测图片三种形式大小的方法:
文件形式: file.length()
流的形式: 讲图片文件读到内存输入流中,看它的byte数
Bitmap: bitmap.getByteCount()
二、常用的图片压缩方法
**
1. BitmapFactory的decodeFile方法
**
特点: 通过设置采样率, 减少图片的像素, 达到对内存中的Bitmap进行压缩
首先,在处理图片的方法中,官方demo中推荐的也是这种方法。
结合现实场景来看,我们通常会将图片固定大小成缩略图,使用的就是decodeFile方法,然后通过传递进去 BitmapFactory.Option类型的参数取缩略图。
Option类在百度地图sdk或者其它地方中也看到过,可以说是参数或者限定的集和吧。在Option中,有两个重要的地方,一是inSampleSize表示采样率长宽压缩的比例,另外的就是inJustDecodeBounds,就是表示只包括一些解码边界信息即图片大小信息等等。举个具体的例子来说,还是以官方demo为主来讲吧:
inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
而inJustDecodeBounds设置为true的时候,就是提取图片大小信息,不返回实际的bitmap也不给其分配内存空间,但记得用完后就要把它设为false;
/**
* 计算压缩比率
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth) {
// 源图片的宽度
final int width = options.outWidth;
int inSampleSize = 1;
if (width > reqWidth) {
// 计算出实际宽度和目标宽度的比率
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = widthRatio;
}
return inSampleSize;
}
/**
* 官方文档的用法改写,这是一个系列的
* http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
* @param pathName
* @param reqWidth
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(String pathName,
int reqWidth) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
简要总结:
该方法就是对Bitmap形式的图片进行压缩, 也就是通过设置采样率, 减少Bitmap的像素, 从而减少了它所占用的内存。一般过程就是:
使用2的次幂来设置inSampleSize值可以使解码器执行地更加迅速、更加高效。但是,如果你想在内存或者硬盘上缓存一个调整过大小的图片,通常还是解码到合适的图片尺寸更加节省空间。
要使用这个方法,首先要使用inJustDecodeBounds为true来解码尺寸信息,将options传递过去使用新的inSampleSize值再次解码并且要将inJustDecodeBounds值设置为false。最后通过BitmapFactory.decode**的方法压缩。
2. 将图片保存到本地时进行压缩, 即将图片从Bitmap形式变为File形式时进行压缩,
特点是: File形式的图片确实被压缩了, 只对File格式进行质量压缩。注意:如果读取压缩后的file为Bitmap时,它占用的内存并没有改变
public static void compressBmpToFile(Bitmap bmp,File file){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 80;//设置开始的compress系数
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
while (baos.toByteArray().length / 1024 > 100) {
baos.reset();
options -= 10;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
方法说明: 该方法是压缩图片的质量, 注意它不会减少图片的像素,比方说, 你的图片是300K的, 1280*700像素的, 经过该方法压缩后, File形式的图片是在100K以下, 以方便上传服务器, 但是你BitmapFactory.decodeFile到内存中,变成Bitmap时,它的像素仍然是1280*700, 计算图片像素的方法是 bitmap.getWidth()和bitmap.getHeight(), 图片是由像素组成的, 每个像素又包含什么呢? 熟悉PS的人知道, 图片是有色相,明度和饱和度构成的.
该方法的官方文档也解释说, 它会让图片重新构造, 但是有可能图像的位深(即色深)和每个像素的透明度会变化,JPEG only supports opaque(不透明), 也就是说以jpeg格式压缩后, 原来图片中透明的元素将消失.所以这种格式很可能造成失真
既然它是改变了图片的显示质量, 达到了对File形式的图片进行压缩, 图片的像素没有改变的话, 那重新读取经过压缩的file为Bitmap时, 它占用的内存并不会少.
所以呢,这种方法比较适用上传服务器时的图片压缩。