图片加载
在了解图片压缩前,先简单介绍常用的几种图片加载 方式,在BitmapFactory
类中提供了decodeFile
、decodeResource
、decodeStream
、decodeByteArray
、decodeFileDescriptor
几种静态方法,通过使用这些静态方法可以将具体的文件、流、比特数组、文件描述符加载成为Bitmap
对象。
图片压缩
老实说“图片压缩”这个词,太过于含糊;
-
在运行过程中考虑到Bitmap内存问题,则需要在对Bitmap(位图对象)是进行压缩(通常通过设置色阶深度或
Bitmap
高宽来实现)。 -
在传输或存储过程中为了降低图片文件(最终的
.jpg
或.jpeg
)大小,会通过舍弃部分图片信息进行图片的压缩(通常指质量压缩),也就是有损压缩,然后减少文件的大小。
前一种压缩和后一种压缩往往被混淆一谈。
Bitmap(位图)的压缩
在介绍Bitmap的压缩前,应该了解Bitmap的内存占用,这样才能更好地去理解Bitmap的压缩。
色位深度
首先,需要了解色阶深度。
屏幕上的每个像素点,在内存中都有一块空间用来描述该像素点的颜色信息。同样每张显示在屏幕上的图像,都有一个对应的Bitmap对象用来存储图像上每个像素点的信息,所以在Bitmap中有一块大小为:(图像像素高 x 图像像素宽 x 描述单个像素点需要的位)
的空间去存储图像中所有像素点的信息。描述单个像素点需要的位数空间
指的就是色位深度。Bitmap
中使用Bitmap.Config
来决定色位深度,目前Config有四个值:ALPHA_8、RGB_565、ARGB_4444、ARGB_8888。
(A:透明度 、R:红、G:绿、B:蓝)。
Config | 每个像素占用的字节 | 说明 |
---|---|---|
ALPHA_8 | 1 bytes | 每个像素仅仅储存透明度通道 |
RGB_565 | 2 bytes | 每个像素的RGB通道会保存,透明度不会保存,红色通道5位,有25=32种表现形式;绿色通道6位,有26=64种表现形式;蓝色通道5位,有2^5=32种表现形式 |
ARGB_4444 | 2 bytes | 每个像素的ARGB通道都会保存,透明度/红色/绿色/蓝色通道4位,有2^4=16种表现形式 |
ARGB_8888 | 4 bytes | 每个像素的ARGB通道都会保存,透明度/红色/绿色/蓝色通道8位,有2^8=256种表现形式 |
图片文件与Bitmap
Android中常用的图片文件通常以.png
、.jpeg
、 .jpg
格式进行存储。png
文件是一种无损压缩的位图图像存储格式, 它利用特殊的编码方法标记重复出现的数据,而jpg
、jpeg
则是有损压缩的位图图像存储格式。在每个像素点的描述上png图片要多一个alpa透明通道,当时所以相同高宽和清晰度的图像,保存为png
文件的文件大小不一定要比jpg
、jpeg
文件要大。
此处强烈推荐《你的bitmap究竟有多大》
通过调用BitmapFactory
的图片文件解析加载函数可将图片文件加载进入内存当中成为Bitmap
对象,由于png
、jpg
、jpeg
图片文件均是Bitmap
压缩以后写入的文件,所以通常Bitmap
对象的大小大于文件大小。
压缩–位图读取
由于Bitmap
所占内存的大小主要取决于图片宽度
、图片高度
、色位深度
、图片所在文件夹
、设备像素密度
,故而在加载图片时为避免OOM的产生,可通过缩减宽高与减少色位深度的bit位数来达到压缩Bitmap
所需空间。
- 通过设置加载选项
BitmapFactory.Options
的inSampleSize
实现高宽等比缩小达到压缩效果,在第一次解析码时,并不需要将文件全部解码成Bitmap
对象,这里只需要Bitmap
的原始高宽,所以Options.isJustDecodeBounds
设置为true
。在计算得到合适的比例后,再设置采样率参数inSampleSize
来得到适当大小的bitmap
对象;/** * 取样(尺寸等比)压缩,由于reqWidth和reqHeight通常取 * ImageView的大小,为了使图片将ImageView填充满避免出现 * 黑色空白区域,故而选取小比例作为压缩比值 * * @param resources * @param resId * @param reqWidth request width * @param reqHeight request height * */ public static Bitmap compressInSampleSize(Resources resources, int resId, int >reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeResource(resources, resId, options); options.inSampleSize = getInSampleSize(bitmap, reqWidth, reqHeight); options.inJustDecodeBounds = false; bitmap.recycle(); Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options); return resultBitmap; } private static int getInSampleSize(Bitmap bitmap, int reqWidth, int reqHeight){ int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int inSampleSize = 1; //若希望更改比例选择策略,将'&&'改为'||'即可 while (bitmapWidth/inSampleSize > reqWidth && bitmapHeight/inSampleSize > reqHeight){ inSampleSize *= 2; } return inSampleSize; }
- 通过设置色位深度减少
Bitmap
的占用内存/** * 选择色阶深度加载图片 * * @param resources * @param resId * @param config Color order depth * */ public static Bitmap compressInPixColorBit(Resources resources, int resId, Bitmap.Config config) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = config == null? Bitmap.Config.RGB_565 : config; Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options); return resultBitmap; }
高宽等比缩小与设置色位深度实现内存占用降低可以联合使用,使
Bitmap
占用内存更低,但同时也意味着更多信息的缺失,例如:如果采用RGB_565则图片将没有透明度通道。
压缩–位图写入
网上很多博客均将质量压缩作为图片加载防止OOM的一种解决方案,然而这是对“质量压缩”的误解。他们给出的质量压缩方案,通常如下:
/**
* 质量压缩
*
* @param bitmap
* @param targetSize target size equal to targetSize(kb)
* */
public static Bitmap compressInQuality(Bitmap bitmap, int targetSize) {
ByteArrayOutputStream resultBitmapStream = new ByteArrayOutputStream();
int quality = 100;
do{
resultBitmapStream.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, resultBitmapStream);
quality -= 10;
}while (resultBitmapStream.toByteArray().length/1024 > targetSize && quality > 0);
bitmap.recycle();
return BitmapFactory.decodeStream(new ByteArrayInputStream(resultBitmapStream.toByteArray()));
}
然后,如果把这个理解成“可以按质量quality
压缩bitmap对象”便是大错特错。直接使用这样的方案来试图降低Bitmap
非但达不到效果,还会使内存提前用完。
对compress的使用反思
- 为何
compress
这样处理得到的OutputStream
,再经过解析后不能达到降低内存的效果内?
compress
是按照压缩算法(算法根据第一个参数而定,PNG则是采用无损压缩算法,JPEG则是采用有损压缩算法)对bitmap对象中的信息进行压缩然后写入OutputStream
,具体压缩率则取决于quality
(0~100,当采用无损压缩PNG时,此参数被忽略),但是此时的bitmap
对象任然是原来的bitmap
对象,当使用decodeStream
对compress
产生的OutputStream
进行解析时,解析得到的新Bitmap
高宽与原来的Bitmap
高宽相等,不选择色位深度的话,则时默认色位深度,新的Bitmap
的内存占用甚至更高,所以这种处理方式会在内存中存在两个bitmap
对象,还多申请了一份ByteArrayOutputStream
空间。 - 那质量压缩
compress
什么时候用呢?
compress
只是将一个bitmap
对象的信息压缩进一个OutputStream
,如果选择有损压缩则在压缩的过程中还会选择性放弃一些图片信息,无损压缩则会保留图片的全部信息,但是无损压缩后的文件任然会比Bitmap
在内存中占用的内存大小要小。所以,在网络传输或者在存储时使用compress
显然是可行的。同时,如果Bitmap
是再加载过程中使用了压缩,在compress
使用完成以后,由于Bitmap
所含信息减少,质量压缩得到的数据流将会变得更小。对数据流进行解析的得到的Bitmap
的内存占用也随之降低。
注意事项
- 图片加载需要考虑是否需要缓存
- 在对文件流进行解析时,应考虑是否一步解析,如果需要多步读取流,应考虑到文件流的有序性,经过读取后,流的起始位置发生变化,导致再次读取为
null
,此时建议使用文件描述符进行操作 - 位图压缩读取与压缩写入为IO操作,不应在主线程中进行