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值。