前言
Android
中图片以位图(Bitmap
)的形式存在,位图常见的格式有.png
、.jgp
、.bmp
、.gif
。在加载图片的过程中常见的就是OOM(Out of Memory)
内存溢出。
内存溢出
是系统会给APP分配内存也就是Heap Size
值。当APP
占用的内存加上我们申请的内存资源超过了Dalvik
虚拟机的最大内存时就会抛出的Out Of Memory
异常。
为什么在加载大图片时会出现内存溢出
?首先我们要了解Bitmap
位图占用内存大小的计算方式。
一、颜色格式与内存大小计算
常用的颜色格式有ALPHA_8
、ARGB_4444
、ARGB_8888
、RGB_565
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。
ALPHA_8
表示8位Alpha
位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
ARGB_4444
表示16位ARGB
位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
ARGB_8888
表示32位ARGB
位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
RGB_565
表示16位RGB
位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
占用内存 = 图片宽度(像素) * 图片高度(像素) * 单个像素所占内存空间(单位:字节)
例如像素为3840 * 2160 ,颜色格式为ARGB8888
,每个像素占8位,总共32位,4个字节。
占用内存大小 = 3840 * 2160 * 4 / 1024 / 1024 = 31.64M
计算得出,一张4K图片在内存中约占32M内存,加载更高分辨率的图片是很容易造成OOM
,所以在加载大图前对图片进行压缩,下面将介绍Android
中常用的4种压缩方式。
二、图片压缩
下面将介绍4种压缩方式,分别为质量压缩、采样率压缩、缩放压缩、颜色格式压缩。
1、质量压缩
质量压缩适合于分辨率不大,但是图片大小教大的情况,在Android
中提供了相关方法,Bitmap
中的compress
方法。
/**
* @param[quality]:0-100 ,数值越小代表压缩率越高
*/
fun compressByQuality(quality:Int,ctx: Context){
val bitmap = BitmapFactory.decodeResource(ctx.resources,R.mipmap.wallpaper_00)
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG,quality,bos)
val compressBitmap = BitmapFactory.decodeByteArray(bos.toByteArray(),0,bos.toByteArray().size)
}
质量压缩有以下缺点:
- 改变图片的位深和透明度,不改变图片的分辨率,所以不改变在内存中的大小。
- 质量压缩对
png
格式图片压缩无效,png
图片是无损压缩格式。
2、采样率压缩
采样率压缩是相对于质量压缩的优点是可以改变图片在内存中的大小,在进行采样率压缩前,我们先了解下对应的属性。
val dm = ctx.resources.displayMetrics
val option = BitmapFactory.Options().apply {
inJustDecodeBounds = false
//可以复用之前用过的bitmap
inBitmap = null
//是该bitmap缓存是否可变,如果设置为true,将可被inBitmap复用
inMutable = true
//表示这个bitmap的像素密度,当inDensity为0时,系统默认赋值为屏幕当前像素密度
inDensity = dm.density.toInt()
//表示要被画出来时的目标像素密度,当inTargetDensity为0时,系统默认赋值为屏幕当前像素密度
inTargetDensity = inDensity
//表示实际设备的像素密度
inScreenDensity = 0
//这个参数可以改变bitmap分辨率大小,inSampleSize >= 1。
//当inSampleSize < 1时,inSampleSize就默认是1。
//假如:图片的宽和高分别是width、height,那么图片解码生成的bitmap的宽度是:width / inSampleSize,高度是:height / inSampleSize
//inSampleSize影响bitmap的分辨率,从而影响bitmap占用内存的大小。
inSampleSize = 2
//表示图片是否可以被缩放
inScaled = true
//A R G B 四个颜色通道 每个通道占8位
inPreferredConfig = Bitmap.Config.ARGB_8888
}
采样率压缩主要用到BitmapFactory.Options
中的属性inSampleSize
,这个参数设置为大于或等于1。等于1默认为不压缩。
下面介绍一个比较重要的参数inJustDecodeBounds
。当设置inJustDecodeBounds = true
时,表示仅仅解码图片的图片的大小,不将图片加载进内存中。
val option = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.mipmap.wallpaper_00,option)
val imgW = option.outWidth
val imgH = option.outHeight
Log.i(TAG, "inJustDecodeBounds = ${option.inJustDecodeBounds} ,bitmap = $bitmap ,imgW = $imgW ,imgH = $imgH")
可以看到返回的Bitmap
对象为空,一般当我们要获取加载图片的宽和高来选择最优的采样率压缩比时,可以设置此属性来获取图片的相关信息。
当设置inJustDecodeBounds = false
时,来设置inSampleSize = 4
后看下压缩效果。
val option = BitmapFactory.Options()
option.inJustDecodeBounds = true
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.mipmap.wallpaper_00,option)
val preImgW = option.outWidth
val preImgH = option.outHeight
Log.i(TAG, "inJustDecodeBounds = ${option.inJustDecodeBounds} ,bitmap = $bitmap ,preImgW = $preImgW ,preImgH = $preImgH")
option.inJustDecodeBounds = false
option.inSampleSize = 4
val afterBitmap = BitmapFactory.decodeResource(ctx.resources, R.mipmap.wallpaper_00,option)
val afterW = option.outWidth
val afterH = option.outHeight
Log.i(TAG, "inJustDecodeBounds = ${option.inJustDecodeBounds} ,bitmap = $afterBitmap ,inSampleSize = ${option.inSampleSize} ,afterW = $afterW ,afterH = $afterH")
在设置inSampleSize = 4
,图片的宽和高的分辨率都缩小到了原来的1/4。此时再计算图片在内存的大小为
内存大小 = 960 * 540 * 4 / 1024 / 1024 = 1.98M
与未压缩的图片在内存中所在31.64M相比,缩小了约16倍,这也达到了在压缩图片所占内存大小的目的。
3、缩放压缩
此方法是将图片缩放到指定分辨率,其实是和采样率压缩是相同效果的不同方法,通过Matrix
矩阵进行缩放变换。
val srcBitmap = BitmapFactory.decodeResource(ctx.resources,R.mipmap.wallpaper_00)
val srcW = srcBitmap.width
val srcH = srcBitmap.height
val newW = 960
val newH = 540
val scaleW = newW / srcW.toFloat()
val scaleH = newH / srcH.toFloat()
val matrix = Matrix()
matrix.setScale(scaleW,scaleH)
val newBitmap = Bitmap.createBitmap(srcBitmap,0,0,srcW,srcH,matrix,true)
4、颜色格式压缩
上面介绍了4种不同的颜色格式,Android
中默认使用的是ARGB_8888
,4个字节,可以将此格式设置成RGB_565
。对应的是BitmapFactory.Options
中的inPreferredConfig
属性。
val option = BitmapFactory.Options()
option.inPreferredConfig = Bitmap.Config.RGB_565
val newBitmap = BitmapFactory.decodeResource(ctx.resources,R.mipmap.wallpaper_00,option)
结尾
为了防止应用OOM
发生,上述介绍了Android
中加载大图的几种压缩方法,可根据项目中具体的需求来使用不同的压缩方式。