How much memory does a picture take up in Android

Front Concept - Screen Density

Figure out the two variables of DisplayMetrics.
density is the logical density of the display, which is the scaling factor between density and independent pixel units.
densityDpi is how many dots per inch of the screen

For more details about DisplayMetrics click here

The calculation principle of how much memory is occupied by pictures

Find the number of bytes occupied by each pixel * the total number of pixels

Android API has a convenient method to get the occupied memory size

public final int getByteCount() {
    
    
    // int result permits bitmaps up to 46,340 x 46,340
    return getRowBytes() * getHeight();
} 

getHeight is the height of the picture (unit: px)
, so what about getrowBytes()

public final int getrowBytes() {
    
    
   if (mRecycled) {
    
    
          Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
   }
   return nativeRowBytes(mFinalizer.mNativeBitmap);
}
#Bitmap.cpp
static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) {
    
    
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle)
     return static_cast<jint>(bitmap->rowBytes());
}
#SkBitmap.h
/** Return the number of bytes between subsequent rows of the bitmap. */
size_t rowBytes() const {
    
     return fRowBytes; }
# SkBitmap.cpp
size_t SkBitmap::ComputeRowBytes(Config c, int width) {
    
    
    return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
 }
 
# SkImageInfo.h
static int SkColorTypeBytesPerPixel(SkColorType ct) {
    
    
   static const uint8_t gSize[] = {
    
    
    0,  // Unknown
    1,  // Alpha_8
    2,  // RGB_565
    2,  // ARGB_4444
    4,  // RGBA_8888
    4,  // BGRA_8888
    1,  // kIndex_8
  };
  SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
                size_mismatch_with_SkColorType_enum);

   SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
   return gSize[ct];
}

static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
    
    
    return width * SkColorTypeBytesPerPixel(ct);
}

One pixel of ARGB_8888 (which is our most commonly used Bitmap format) occupies 4bytes, and rowBytes is actually 4*width bytes.

Bitmap memory occupied by ARGB_8888 is calculated as bitmap I n R am = bitmap W idth ∗ bitmap Height ∗ 4 bytes bitmapInRam = bitmapWidth*bitmapHeight *4 bytesbitmapInRam=bitmapWidthbitmapHeight4bytes

A 522*686 PNG picture, put it in the drawable-xxhdpi directory, load it on the Samsung s6, occupying 2547360B of memory, you can use this method to get it.

However, the formula calculates 1432368B

density affects memory usage

The size of the space occupied by the Bitmap is not only related to the width and height of the picture, but also related to the density factor.

What is read is the picture under the drawable directory, using the decodeResource method, which is essentially two steps:

  • To read the raw resource, this calls the Resource.openRawResource method. After the method is called, it will assign a value to TypedValue, which contains information such as the density of the raw resource;

  • Call decodeResourceStream to decode and adapt the original resource. This process is actually a mapping from the density of the original resource to the density of the screen.

The density of the original resource actually depends on the directory where the resource is stored (for example, xxhdpi corresponds to 480), and the assignment of screen density,

### BitmapFactory.java

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
    InputStream is, Rect pad, Options opts) {
    
    

//实际上,我们这里的opts是null的,所以在这里初始化。
if (opts == null) {
    
    
    opts = new Options();
}

if (opts.inDensity == 0 && value != null) {
    
    
	//密度等于TypedValue.DENSITY_NONE,那么就没有与资源相关的密度,它不应该被缩放
    final int density = value.density;
    if (density == TypedValue.DENSITY_DEFAULT) {
    
    
    	//密度等于这个值,那么这个密度应该被视为系统的默认密度值
        opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;//默认密度160
    } else if (density != TypedValue.DENSITY_NONE) {
    
    //不等于此值需要缩放
        opts.inDensity = density; //这里density的值如果对应资源目录为hdpi的话,就是240
    }
}

if (opts.inTargetDensity == 0 && res != null) {
    
    
	//inTargetDensity就是当前的手机的密度,比如三星s6时就是640
    opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}

return decodeStream(is, pad, opts);
}

We focus on two values ​​inDensity and inTargetDensity, which correspond to the density and targetDensity in the BitmapFactory.cpp file.
InDensity is the density of the original resource, and inTargetDensity is the density of the screen.
Next, the nativeDecodeStream method is used, the code of the most critical doDecode function:

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    
    

......
    if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
    
    
        const int density = env->GetIntField(options, gOptions_densityFieldID);//对应hdpi的时候,是240
        const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640
        const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
        if (density != 0 && targetDensity != 0 && density != screenDensity) {
    
    
            scale = (float) targetDensity / density;
        }
    }
}

const bool willScale = scale != 1.0f;
......
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
    
    
   return nullObjectReturn("decoder->decode returned false");
}
//这里这个decodingBitmap就是解码出来的bitmap,大小是图片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    
    
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (willScale) {
    
    
    const float sx = scaledWidth / float(decodingBitmap.width());
    const float sy = scaledHeight / float(decodingBitmap.height());

    // TODO: avoid copying when scaled size equals decodingBitmap size
    SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
    // FIXME: If the alphaType is kUnpremul and the image has alpha, the
    // colors may not be correct, since Skia does not yet support drawing
    // to/from unpremultiplied bitmaps.
    outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));
    if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
    
    
        return nullObjectReturn("allocation failed for scaled bitmap");
    }

    // If outputBitmap's pixels are newly allocated by Java, there is no need
    // to erase to 0, since the pixels were initialized to 0.
    if (outputAllocator != &javaAllocator) {
    
    
        outputBitmap->eraseColor(0);
    }

    SkPaint paint;
    paint.setFilterLevel(SkPaint::kLow_FilterLevel);

    SkCanvas canvas(*outputBitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
......
}

density is actually the densityDpi of decodingBitmap, which is related to the directory where the picture is placed (for example, hdpi is 240, xxhdpi is 480),
targetDensity is actually the target densityDpi of our loaded picture, Samsung s6 is 640. sx and sy are actually approximately equal to scale, because scaledWidth and scaledHeight are obtained by multiplying width and height by scale. We see that the Canvas is magnified by the scale, and then draw the bitmap read into the memory, which is equivalent to enlarging the bitmap by the scale.

So back to the above
, a 522*686 PNG picture, I put it in the drawable-xxhdpi directory, load it on the Samsung s6, occupying 2547360B of memory, where density corresponds to xxhdpi is 480, and targetDensity corresponds to the density of Samsung s6 is 640:

522/480 * 640 * 686/480 * 640 * 4 = 2546432B

value is still different

Precision affects memory usage

outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));

The final output outputBitmap size is scaledWidth*scaledHeight,

if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    
    
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}

In our example,

scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696

scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

915 * 696 * 4 = 2547360

Factors affecting the size of Bitmap occupied in memory

  • Color format, if it is ARGB8888, then it is 4 bytes per pixel, if it is RGB565, it is 2 bytes

  • The resource directory where the original file is stored

  • The density of the target screen

how to optimize

Knowing the reason, you can optimize memory usage accordingly.
For details, please see Optimizing Bitmap Memory Occupancy

Guess you like

Origin blog.csdn.net/rd_w_csdn/article/details/115130530