摘要:
我们做Android应用的时候最不可避免的就是与图片打交道,而图片通常又是应用内存开销,影响性能的大头,因而这也是网上的帖子关于图片方面的内容热度比较高的原因之一。另外,发现身边的一些做Android应用开发的朋友,在处理图片这方面的问题的时候,基本就是“一把梭,拿起就干“,从网上拷贝了代码贴上去看到没报错了,就完事。缺乏深度的思考,孔子曰“学而不思则罔” ,这也就促使我冲动的写下该文。
一、图片大小
了解图片大小之前,先说下图片的存放形式:本地文件、流和Bitmap三种,或许我们会疑问,流和Bitmap不都存放在内存当中吗,那岂不应该是同一种形式才对吗?实际上,都存放进内存,没错,但它们两者在内存中的数据结构是不一样的,即表示每个像素的数据是不相等的。那么三种形式图片的计算是怎样的?
A、本地文件和流都是通过计算文件的长度得到图片的大小,如:
File f = new File(path);
long size = f.length();
所以,本地文件的大小 = 图片流的大小,即本地存放一张1M的图片,通过流的方式读到内存当中,流的大小也是1M。
B、Bitmap的大小 = bitmap.getRowBytes() * bitmap.getHeight() ,一行的字节数 x 高度。
经常我们会看到,本地一张1M的图片以Bitmap的方式读到内存中,会比流的方式读到内存中大好几倍。这是因为,本地的图片通常都是通过Bitmap的方式压缩(有损或算法的压缩方式)保存到本地的。如,一张Bitmap.Config.ARGB_8888类型的图片以
Bitmap.Config.RGB_565的类型保存到本地,那么保存后的图片大小就会比Bitmap在内存中的大小小很多,究其原因,就是他们表示每个像素的数据变了,后者没有alpha通道数据,不支持透明和半透明。到此,我觉得有必要让我们来了解一下Bitmap的Config类型:
上面图是从官网截图下来的,然而,我再用我的理解来详说下,这几种类型的区别:
1、Bitmap.Config.ALPHA_8 表示图片只有alpha值,没有RGB值,1个像素占用一个字节
2、Bitmap.Config.ARGB_4444 表示一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites共16bites,即2个字节
3、Bitmap.Config.ARGB_8888 表示一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。(Android上Bitmap的默认格式)
4、Bitmap.Config.RGB_565 表示一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节。
ok,了解了Bitmap图片不同类型所占据的内存字节数后,对于刚才上面提到的本地图片与Bitmap图片大小差异的问题,应该就迎刃而解了。
二、Bitmap的图片压缩
上面说到了图片的大小,实际上,图片的大小应该分为质量的大小和尺寸的大小。那么,Bitmap图片的压缩也应该包括质量的压缩和尺寸的压缩两种。
1、图片质量压缩
public static boolean saveImageFileToLoc(final File file, final Bitmap photoBitmap) {
FileOutputStream fos = null;
try {
if (null != file) {
fos = new FileOutputStream(file);
if (null != fos) {
photoBitmap.compress(Bitmap.CompressFormat.JPEG,75, fos);
fos.flush();
}
return true;
}
} catch (IOException e) {
return false;
} finally {
photoBitmap.recycle();
try {
if (null != fos) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
上面是对Bimtap图片进行质量的压缩,如果原来Bitmap的图片类型是
Bitmap.Config.ARGB_8888,那么现在使用Bitmap.CompressFormat.JPEG的方式压缩,每个图片像素存储大小是会变小的,另外,compress这种方法的压缩,实际是一种算法(哈夫曼)的压缩,打个比方:原来用a b c d来存储一个像素,现在经过一定的算法计算后,使用另外一个或多个更小的字节来存储,从而达到压缩的作用,其中75就是计算压缩的程度。这种图片的压缩使用情景是,我们要保存一张图片、或上传图片到服务器的时候,对图片进行了压缩能有效的减少图片在手机当中存储空间,或上传的流量。那么,从这里看,图片最终存在在本地,图片的质量压缩与内存大小实际上是无关的。
2、图片尺寸压缩
public static Bitmap resizeBitmap(String imgPath, float pixelW, float pixelH) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容
newOpts.inJustDecodeBounds = true;
newOpts.inPreferredConfig = Bitmap.Config.ARGB_8888;
// Get bitmap info, but notice that bitmap is null now
Bitmap bitmap = BitmapFactory.decodeFile(imgPath,newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 想要缩放的目标尺寸
float hh = pixelH;// 设置高度为240f时,可以明显看到图片缩小了
float ww = pixelW;// 设置宽度为120f,可以明显看到图片缩小了
// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0) be = 1;
newOpts.inSampleSize = be;//设置缩放比例
// 开始压缩图片,注意此时已经把options.inJustDecodeBounds 设回false了
bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
return bitmap ;
}
上面的压缩是根据指定的图片尺寸进行采样率的压缩,是把本地图片转到Bitmap,也即图片从本地到内存,同时本地的图片转Bitmap的过程中,因为不同的Bitmap类型,所占的内存就会有很大的差异,另外,不同的采样率会影响像素的个数,在每个像素占据内存大小一定的情况下,像素的个数直接决定了Bitmap图片占据内存的大小。所以,在使用场景当中,经常我们会出现OOM的问题,这个时候,我们就可以通过改变Bitmap的类型和采样率来降低内存的大小,从而避免出现图片的OOM问题。
总结:本文只是通过对图片大小的介绍以及两种压缩方式的对比,来进一步说明我们使用图片压缩的过程中,对内存所产生的 影响,以及这两种Bitmap图片压缩方式的正确使用场景。