Android中Bitmap占用内存计算
在Android开发中,我们经常会是用到Bitmap,但是这个是消耗内存的主,因此我们在使用时,要弄清楚他到底占用多少内存,今天就来研究下怎么算出Bitmap占用多少内存。
首先我找了一张400 * 400的图片,然后放在drawable-hdpi、drawable-xhdpi文件夹中
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.header_image_hdpi);
Log.d("fishpan_log", "MainActivity.onCreate: hdpi -> " + bitmap.getByteCount());
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.header_image_xhdpi);
Log.d("fishpan_log", "MainActivity.onCreate: xhdpi -> " + bitmap.getByteCount());
运行结果
MainActivity.onCreate: hdpi -> 2149156
MainActivity.onCreate: xhdpi -> 1210000
下面我就来看下bitmap.getByteCount的方法实现.
public final int getByteCount() {
return getRowBytes() * getHeight();
}
每行占用的字节数*高度
public final int getRowBytes() {
...省略代码
return nativeRowBytes(mNativePtr);
}
这里的nativeRowBytes是一个native方法,我们可以在/frameworks/base/core/jni/android/graphics/Bitmap.cpp中找到对应的实现。
static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) {
return bitmap->rowBytes();
}
我们再看下SkBitmap是个什么鬼?
size_t rowBytes() const { return fRowBytes; }
在SkBitmap.cpp中发现fRowBytes是通过ComputeRowBytes计算的
int SkBitmap::ComputeRowBytes(Config c, int width) {
if (width < 0) {
return 0;
}
Sk64 rowBytes;
rowBytes.setZero();
switch (c) {
case kNo_Config:
case kRLE_Index8_Config:
break;
case kA1_Config:
rowBytes.set(width);
rowBytes.add(7);
rowBytes.shiftRight(3);
break;
case kA8_Config:
case kIndex8_Config:
rowBytes.set(width);
break;
case kRGB_565_Config:
case kARGB_4444_Config:
rowBytes.set(width);
rowBytes.shiftLeft(1);
break;
case kARGB_8888_Config:
rowBytes.set(width);
rowBytes.shiftLeft(2);
break;
default:
SkASSERT(!"unknown config");
break;
}
return isPos32Bits(rowBytes) ? rowBytes.get32() : 0;
}
这里就是这对图片格式进行的计算了,ARGB_8888格式图片就是宽度 * 4,整个Bitmap占用的就是宽度 * 高度 * 4 byte;
问题来了,图片的高度宽度是怎么得到?通过上面demo我们可以明显看出,图片在内存中的大小并不是原始大小,而是发生了变化。
看下BitmapFactory.decodeResource方法
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
首先实例化了一个Options类,并设置了inDensity、inTargetDensity属性,inDensity与图片所在文件夹相关,inTargetDensity就是设备的屏幕密度。
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
...省略部分代码
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
...省略部分代码
setDensityFromOptions(bm, opts);
return bm;
}
中间会调用nativeDecodeAsset或者decodeStreamInternal方法,但是最终都会调用到BitmapFactory.cpp中的doDecode方法
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
jobject options, bool allowPurgeable, bool forcePurgeable = false) {
if (options != NULL) {
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
scale = (float) targetDensity / density;
}
}
}
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
// update options (if any)
if (options != NULL) {
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
env->SetObjectField(options, gOptions_mimeFieldID,
getMimeTypeString(env, decoder->getFormat()));
}
}
省略一堆代码,看重点。中间计算scale就是targetDensity / density,图片最终的宽度和高度就是scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f);
OK,现在我们就知道一个图片占多大内存了,我们通过自己的计算看看是不是符合上边的结果.
我的手机是屏幕密度是440
图片位置:drawable-hdpi
图片格式:ARGB-8888
图片大小:400 * 400
宽度:int(400 * (440 / 240) + 0.5)
高度:int(400 * (440 / 240) + 0.5)
内存大小:2149156
图片位置:drawable-xhdpi
图片格式:ARGB-8888
图片大小:400 * 400
宽度:int(400 * (440 / 320) + 0.5)
高度:int(400 * (440 / 320) + 0.5)
内存大小:1210000
总结:通过上面的分析我们可以发现,图片占用内存大小和手机的密度成正比,所在文件夹密度成反比