Android加载大图策略,防止OOM

前言


Android中图片以位图(Bitmap)的形式存在,位图常见的格式有.png.jgp.bmp.gif。在加载图片的过程中常见的就是OOM(Out of Memory)内存溢出。

内存溢出是系统会给APP分配内存也就是Heap Size值。当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的Out Of Memory异常。

为什么在加载大图片时会出现内存溢出?首先我们要了解Bitmap位图占用内存大小的计算方式。


一、颜色格式与内存大小计算


常用的颜色格式有ALPHA_8ARGB_4444ARGB_8888RGB_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中加载大图的几种压缩方法,可根据项目中具体的需求来使用不同的压缩方式。

你可能感兴趣的:(Android,Android,Android,Bitmap,Android,OOM,Android加载大图)