在Android開發(fā)中吴藻,我們經(jīng)常會(huì)是用到Bitmap,但是這個(gè)是消耗內(nèi)存的主,因此我們?cè)谑褂脮r(shí),要弄清楚他到底占用多少內(nèi)存,今天就來研究下怎么算出Bitmap占用多少內(nèi)存丧肴。
首先我找了一張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());
運(yùn)行結(jié)果
MainActivity.onCreate: hdpi -> 2149156
MainActivity.onCreate: xhdpi -> 1210000
下面我就來看下bitmap.getByteCount的方法實(shí)現(xiàn).
public final int getByteCount() {
return getRowBytes() * getHeight();
}
每行占用的字節(jié)數(shù)*高度
public final int getRowBytes() {
...省略代碼
return nativeRowBytes(mNativePtr);
}
這里的nativeRowBytes是一個(gè)native方法,我們可以在/frameworks/base/core/jni/android/graphics/Bitmap.cpp中找到對(duì)應(yīng)的實(shí)現(xiàn)尊沸。
static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) {
return bitmap->rowBytes();
}
我們?cè)倏聪耂kBitmap是個(gè)什么鬼威沫?
size_t rowBytes() const { return fRowBytes; }
在SkBitmap.cpp中發(fā)現(xiàn)fRowBytes是通過ComputeRowBytes計(jì)算的
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;
}
這里就是這對(duì)圖片格式進(jìn)行的計(jì)算了,ARGB_8888格式圖片就是寬度 * 4贤惯,整個(gè)Bitmap占用的就是寬度 * 高度 * 4 byte;
問題來了,圖片的高度寬度是怎么得到棒掠?通過上面demo我們可以明顯看出孵构,圖片在內(nèi)存中的大小并不是原始大小,而是發(fā)生了變化烟很。
看下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);
}
首先實(shí)例化了一個(gè)Options類颈墅,并設(shè)置了inDensity、inTargetDensity屬性雾袱,inDensity與圖片所在文件夾相關(guān)恤筛,inTargetDensity就是設(shè)備的屏幕密度。
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;
}
中間會(huì)調(diào)用nativeDecodeAsset或者decodeStreamInternal方法芹橡,但是最終都會(huì)調(diào)用到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()));
}
}
省略一堆代碼毒坛,看重點(diǎn)。中間計(jì)算scale就是targetDensity / density,圖片最終的寬度和高度就是scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f);
OK林说,現(xiàn)在我們就知道一個(gè)圖片占多大內(nèi)存了煎殷,我們通過自己的計(jì)算看看是不是符合上邊的結(jié)果.
我的手機(jī)是屏幕密度是440
圖片位置:drawable-hdpi
圖片格式:ARGB-8888
圖片大小:400 * 400
寬度:int(400 * (440 / 240) + 0.5)
高度:int(400 * (440 / 240) + 0.5)
內(nèi)存大型嚷帷:2149156
圖片位置:drawable-xhdpi
圖片格式:ARGB-8888
圖片大泻乐薄:400 * 400
寬度:int(400 * (440 / 320) + 0.5)
高度:int(400 * (440 / 320) + 0.5)
內(nèi)存大小:1210000
總結(jié):通過上面的分析我們可以發(fā)現(xiàn)珠移,圖片占用內(nèi)存大小和手機(jī)的密度成正比弓乙,所在文件夾密度成反比