Android Bitmap的大小计算、有效加载以及回收

Bitmap内存占用大小的计算

    一个BitMap位图占用的内存=图片长度*图片宽度*单位像素占用的字节数。使用BitmapFactory来decode一张bitmap时,其单位像素占用的字节数由其参数BitmapFactory.Options的inPreferredConfig变量决定。(注:drawable目录下有的png图使用Bitmap.Config.RGB_565和ARGB_8888decode出来的大小一样,未解)
  • ALPHA_8:只有alpha值,没有RGB值,占一个字节。计算:size=w*h
  • ARGB_4444:一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites,这种格式的图片,看起来质量太差,已经不推荐使用。计算:size=wh2
  • ARGB_8888:一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。这是一种高质量的图片格式,电脑上普通采用的格式。android2.3开始的默认格式。计算:size=wh4
  • RGB_565:一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节。对于没有透明和半透明颜色的图片并且不需要颜色鲜艳的来说,该格式的图片能够达到比较的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销,因此它是一个不错的选择。计算:size=wh2

实际开发中通过代码获取bitmap大小

     ` 
     @TargetApi(Build.VERSION_CODES.KITKAT)
     public static int getBitmapSize(BitmapDrawable value) {
         Bitmap bitmap = value.getBitmap();
    if (VersionUtils.hasKitKat()) {
        return bitmap.getAllocationByteCount();
    }   if (VersionUtils.hasHoneycombMR1()) {
        return bitmap.getByteCount();
    }
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
    `

大图片的有效加载

    手机拍出来的图片分辨率通常比手机屏幕的分辨率高的多,对于Galaxy Nexus手机拍的2592x1936图片,采用ARGB_8888为2592x1936x4=19M,可能会超出在某些设备上每个应用的内存限制(16m)引发OOM。

针对超出显示区域(通常是imageview或其他view)的大图片的加载,通常做法如下:

    `
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {   
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
    }
    `

上面代码先通过设置Options.inJustDecodeBounds属性为true,然后执行BitmapFactory.decodeXXX()方法,这一步不会真正加载图片到内存,仅仅是得到图片尺寸信息,存在Options.outHeight和outWidth和outMimeType中,这样就得到了图片在宽和高,再根据目标显示区域的大小计算出缩放比例,并赋值给Options.inSampleSize。如inSampleSize == 4,返回一个原来宽高的1/4的图片,是原来像素数的1/16。比如我们有一张20481536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512384像素。原本加载这张图片需要占用13M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型)。

计算inSampleSize的方法如下:

 `  
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;   final int width = options.outWidth;
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth) {
        // Calculate ratios of height and width to requested height and width,计算出实际宽高和目标宽高的比率
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高一定都会大于等于目标的宽和高。
        final int widthRatio = Math.round((float) width / (float) reqWidth);
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        // Anything more than 2x the requested pixels we'll sample down
        final float totalPixels = width * height;
        // further
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;
        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++;
        }
    }   return inSampleSize;
}
 `

bitmap的回收

  • Android3.0之前,Bitmap的像素级数据是存储在native内存上,而Bitmap对象本身是存储在Java虚拟机堆中。在native内存中的像素数据的释放是不可预知的,native内存的增加也会算在堆上,从而容易使用程序崩溃。对于一张确定不再使用的bitmap,要调用recycle()方法并把对象设置为null值。所以对于Android3.0之前的手机,通过DDMS观看Heap信息的时候不显示native部分分配的内存大小,如图所示,加载了一张7M多的图片,但是显示分配Allocated才2M多。但是native分配的内存大小是算在heap上的,所以当heap大小显示的不是HeapMaxSize的时候,也有可能OOM。

  • Android3.0之后,Bitmap像素数据和它对象本身都存储在Java虚拟机的堆内存中,受GC管理的内存,可以通过GC回收。因此调用recycle()并不会加速bitmap的像素级内存的回收。

  • 为了更有效的利用内存,Android3.0起引入BitmapFactory.Options.inBitmap,如果设置了该属性,那么当使用了带有该 Options 参数的 decode 方法在加载内容时,decode 方法会尝试重用一个已经存在的位图。这就意味着位图内存已经被重用了,从而性能得到了改善,并且移除了内存的分配和解除分配。在Android4.4之前,可重用bitmap的条件是宽,高相等,并且计算出来的inSampleSize为1。到了Android4.4,可重用的条件变为新bitmap的大小应该小于或等于被重用bitmap的getAllocationByteCount值。

你可能感兴趣的:(Android Bitmap的大小计算、有效加载以及回收)