Review knowledge points

reading

OOM Series

Android app OutOfMemory — 1.OOM

ANR series

Android application ANR source code analysis –1.ANR trigger mechanism understanding

How much memory does a 1200 * 799, 341.41 KB local image load into memory?

Have you started to calculate in your mind: Width * Height * clarity =1200 * 799 * 4=3835200 ~ 3.66Mb

Let’s look at the demo test

private void testBitmap2() { Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_340k); Log.e("oom", "getAllocationByteCount..." + bitmap.getAllocationByteCount() + "... getByteCount.." + bitmap.getByteCount()); list.add(bitmap); }Copy the code

Test machine: Mium-8 Android9.0

getAllocationByteCount... 3220800... getByteCount.. 3220800..Copy the code

Ha ha, is that not quite what you expected? Why is that?

Let’s start with Bitmap creation.

The Bitmap code varies greatly between different Android versions:

  • Android 2.3 (API10) and before – pixel data is stored in the native heap
  • Android 3.0 to Android 7.1 (API11-26) – Pixel data is stored in the Java heap
  • Android 8.0 and later – Pixel data is stored in the Native heap

Let’s go straight to Android 8.0

BitmapFactory.decodeResource()
    -> BitmapFactory.decodeResourceStream()
        ->BitmapFactory.decodeStream()
            ->BitmapFactory.nativeDecodeAsset()
Copy the code
//BitmapFactory.java public static Bitmap decodeResource(Resources res, int id, Options opts) { ... bm = decodeResourceStream(res, value, is, null, opts); . return bm; }Copy the code
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { validate(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); }Copy the code

The decodeResourceStream method is to calculate opts.intargetDensity and opts.indensity. TargetDensity refers to the DPI of our mobile phone, and dPI is calculated by screen width square * height square/screen size; Opts.indensity refers to the directory in which your resources are placed:

directory The scope of
mdpi 120dpi-160dpi
hdpi 160dpi~240dpi
xhdpi 240-320dpi
xxhdpi 320-480dpi
xxxhdpi 480-640dpi
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); }... return bm; }Copy the code

Native layer code

/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

First, find the native registered gMethod method

static const JNINativeMethod gMethods[] = { { "nativeDecodeAsset", "(JLandroid/graphics/Rect; Landroid/graphics/BitmapFactory$Options;) Landroid/graphics/Bitmap;" , (void*)nativeDecodeAsset } }Copy the code
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset, jobject padding, jobject options) { Asset* asset = reinterpret_cast<Asset*>(native_asset); // since we know we'll be done with the asset when we return, we can // just use a simple wrapper std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset)); Return doDecode(env, stream.release(), padding, options); }Copy the code

NativeDecodeAsset method content is very simple, its content is mainly called doDecode method, and the construction of bitmap object core logic is in the ‘doDecode’ method. Here is a description of the source of upper-level resources:

  • File
  • Resource
  • ByteArray
  • Stream
  • FileDescriptor

Bitmaps are eventually created by doDecode. Due to the long doDecode method, we first decomposed its key steps roughly as follows:

  • Update with options supplied by the client.
  • Create the codec.
  • Handle sampleSize. (withBitmapFactory.Options.inSampleSizeParameter correlation)
  • Set the decode colorType.
  • Handle scale. (withBitmapFactory.Options.inScaledParameter correlation)
  • Choose decodeAllocator
  • DecodingBitmap AllocPixels (Decoding raw image allocation memory)
  • OutputBitmap AllocPixels (Create real output memory address, not necessarily go)
  • Use SkAndroidCodec to perform the decode
  • Create the Java bitmap
//http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    // This function takes ownership of the input stream.  Since the SkAndroidCodec
    // will take ownership of the stream, we don't necessarily need to take ownership
    // here.  This is a precaution - if we were to return before creating the codec,
    // we need to make sure that we delete the stream.
    std::unique_ptr<SkStreamRewindable> streamDeleter(stream);

    // Set default values for the options parameters.
    //采样比例
    int sampleSize = 1;
    //是否只是获取图片的大小
    bool onlyDecodeSize = false;
    SkColorType prefColorType = kN32_SkColorType;
    //硬件加速
    bool isHardware = false;
    // 是否复用
    bool isMutable = false;
    //缩放 
    float scale = 1.0f;
    bool requireUnpremultiplied = false;
    jobject javaBitmap = NULL;
    sk_sp<SkColorSpace> prefColorSpace = nullptr;

    // Update with options supplied by the client.
    //java层构建好过
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        // Correct a non-positive sampleSize.  sampleSize defaults to zero within the
        // options object, which is strange.
        if (sampleSize <= 0) {
            sampleSize = 1;
        }

        if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
            onlyDecodeSize = true;
        }

        // initialize these, in case we fail later on
        env->SetIntField(options, gOptions_widthFieldID, -1);
        env->SetIntField(options, gOptions_heightFieldID, -1);
        env->SetObjectField(options, gOptions_mimeFieldID, 0);
        env->SetObjectField(options, gOptions_outConfigFieldID, 0);
        env->SetObjectField(options, gOptions_outColorSpaceFieldID, 0);

        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
        jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
        prefColorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
        isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
        //计算缩放比例
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
             //获取我们的dpi,之前我们传入xxhdpi下图片的dpi=480
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
             //scale=当前设备的dpi/文件目录下的dpi
             //scale= opts.inTargetDensity/opts.inDensity=440/480
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;
            }
        }
    }

    ...
    // Determine the output size.
    SkISize size = codec->getSampledDimensions(sampleSize);
   //我们图片实际的宽高 300*400 
    int scaledWidth = size.width();
    int scaledHeight = size.height();
    bool willScale = false;

    // Apply a fine scaling step if necessary.
     //如果java设置了insampleSize=2,则实际会缩放四倍
    if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
        willScale = true;
        scaledWidth = codec->getInfo().width() / sampleSize;
        scaledHeight = codec->getInfo().height() / sampleSize;
    }

    // Set the decode colorType
    SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
    sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(
            decodeColorType, prefColorSpace);

    // Set the options and return if the client only wants the size.
    if (options != NULL) {
        jstring mimeType = encodedFormatToString(
                env, (SkEncodedImageFormat)codec->getEncodedFormat());
        if (env->ExceptionCheck()) {
            return nullObjectReturn("OOM in encodedFormatToString()");
        }
        env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
        env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
        env->SetObjectField(options, gOptions_mimeFieldID, mimeType);

        SkColorType outColorType = decodeColorType;
        // Scaling can affect the output color type
        if (willScale || scale != 1.0f) {
            outColorType = colorTypeForScaledOutput(outColorType);
        }

        jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType);
        if (isHardware) {
            configID = GraphicsJNI::kHardware_LegacyBitmapConfig;
        }
        //
        jobject config = env->CallStaticObjectMethod(gBitmapConfig_class,
                gBitmapConfig_nativeToConfigMethodID, configID);
        env->SetObjectField(options, gOptions_outConfigFieldID, config);

        env->SetObjectField(options, gOptions_outColorSpaceFieldID,
                GraphicsJNI::getColorSpace(env, decodeColorSpace, decodeColorType));
        //java中如果调用 options.inJustDecodeBounds=true,则返回null但是会返回原始宽高
        if (onlyDecodeSize) {
            return nullptr;
        }
    }

    // Scale is necessary due to density differences.
    //刚刚我们计算=440/480
    if (scale != 1.0f) {
        willScale = true;
        //由C++primer中知道static_cast<int> 显示转换 会丢掉浮点后的部分
        //scaledWidth=(1200*440/480+0.5f)=1100.5f=1100
        scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
        //实际结果=(799*440/480+0.5f)=732.9f=732
        scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
    }

    android::Bitmap* reuseBitmap = nullptr;
    unsigned int existingBufferSize = 0;
     // 判断是否有复用的 Bitmap 一般是没有的
    if (javaBitmap != NULL) {
        reuseBitmap = &bitmap::toBitmap(env, javaBitmap);
        if (reuseBitmap->isImmutable()) {
            ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
            javaBitmap = NULL;
            reuseBitmap = nullptr;
        } else {
            existingBufferSize = bitmap::getBitmapAllocationByteCount(env, javaBitmap);
        }
    }
    HeapAllocator defaultAllocator;
    RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
    //需要缩放的时候
    ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
    //根据情况选择不同的内存分配器
    SkBitmap::HeapAllocator heapAllocator;
    SkBitmap::Allocator* decodeAllocator;
    if (javaBitmap != nullptr && willScale) {
        // This will allocate pixels using a HeapAllocator, since there will be an extra
        // scaling step that copies these pixels into Java memory.  This allocator
        // also checks that the recycled javaBitmap is large enough.
        decodeAllocator = &scaleCheckingAllocator;
    } else if (javaBitmap != nullptr) {
        decodeAllocator = &recyclingAllocator;
    } else if (willScale || isHardware) {
        // This will allocate pixels using a HeapAllocator,
        // for scale case: there will be an extra scaling step.
        // for hardware case: there will be extra swizzling & upload to gralloc step.
        decodeAllocator = &heapAllocator;
    } else {
        decodeAllocator = &defaultAllocator;
    }

    // Construct a color table for the decode if necessary
    sk_sp<SkColorTable> colorTable(nullptr);
    SkPMColor* colorPtr = nullptr;
    int* colorCount = nullptr;
    int maxColors = 256;
    SkPMColor colors[256];
    if (kIndex_8_SkColorType == decodeColorType) {
        colorTable.reset(new SkColorTable(colors, maxColors));

        // SkColorTable expects us to initialize all of the colors before creating an
        // SkColorTable.  However, we are using SkBitmap with an Allocator to allocate
        // memory for the decode, so we need to create the SkColorTable before decoding.
        // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
        // not being used elsewhere.
        colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
        colorCount = &maxColors;
    }

    SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);

    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
            decodeColorType, alphaType, decodeColorSpace);

    // For wide gamut images, we will leave the color space on the SkBitmap.  Otherwise,
    // use the default.
    SkImageInfo bitmapInfo = decodeInfo;
    if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
        bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
    }

    if (decodeColorType == kGray_8_SkColorType) {
        // The legacy implementation of BitmapFactory used kAlpha8 for
        // grayscale images (before kGray8 existed).  While the codec
        // recognizes kGray8, we need to decode into a kAlpha8 bitmap
        // in order to avoid a behavior change.
        bitmapInfo =
                bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType);
    }
    SkBitmap decodingBitmap;
    //解析原始图片不一定是输出结果 分配内存 可能失败 由于Java heap or native heap
    if (!decodingBitmap.setInfo(bitmapInfo) ||
            !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable.get())) {
        // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
        // should only only fail if the calculated value for rowBytes is too
        // large.
        // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
        // native heap, or the recycled javaBitmap being too small to reuse.
        return nullptr;
    }

       ...
        if (javaBitmap != NULL) {
            env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
        }
    }
    //真正要返回的图片对象 关键看是否有放缩
    //如果是mipmap资源 取决于scale = (float) targetDensity / density; 不为1.0f的时候;
    //如果是sd卡或者网络图片取决于 是否有采样 opts.insampleSizes>1的情况
    SkBitmap outputBitmap;
    if (willScale) {
        // This is weird so let me explain: we could use the scale parameter
        // directly, but for historical reasons this is how the corresponding
        // Dalvik code has always behaved. We simply recreate the behavior here.
        // The result is slightly different from simply using scale because of
        // the 0.5f rounding bias applied when computing the target image size
        //计算转化比例
        const float sx = scaledWidth / float(decodingBitmap.width());
        const float sy = scaledHeight / float(decodingBitmap.height());

        // Set the allocator for the outputBitmap.
        SkBitmap::Allocator* outputAllocator;
        if (javaBitmap != nullptr) {
            outputAllocator = &recyclingAllocator;
        } else {
            outputAllocator = &defaultAllocator;
        }

        SkColorType scaledColorType = 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.
         //开辟真正使用的bitmap 内存
        outputBitmap.setInfo(
                bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
        if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
            // This should only fail on OOM.  The recyclingAllocator should have
            // enough memory since we check this before decoding using the
            // scaleCheckingAllocator.
            return nullObjectReturn("allocation failed for scaled bitmap");
        }

        SkPaint paint;
        // kSrc_Mode instructs us to overwrite the uninitialized pixels in
        // outputBitmap.  Otherwise we would blend by default, which is not
        // what we want.
        paint.setBlendMode(SkBlendMode::kSrc);
        paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
        //创建画板
        SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
        //设置放缩比
        canvas.scale(sx, sy);
        //绘制
        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
    } else {
        outputBitmap.swap(decodingBitmap);
    }

   ...
    //创建java层的bitmap对象 像素数据并没有回传。如果是7.0版本代码会将像素数据byte[]交给java层Bitmap对象保存
    if (isHardware) {
        sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap);
        return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
                ninePatchChunk, ninePatchInsets, -1);
    }

    // now create the java bitmap
    return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
Copy the code

The key steps of the whole approach have been disassembled earlier, and here are a few:

  • 1. If the Java layer set the options. InJustDecodeBounds = true, will not be loaded into memory images, but returns null, directly from the options you can get to the original width.
    1. Whether to return the original image depends on two points, whether the sampling rate is insampleSize>1, whether to scale=1.f;

      The sampling rate is generally set according to the target size when loading local or network pictures, of course, other scenes can also be set;

      The scaling ratio is determined according to the resource directory and model when loading images in the resource directory.

    1. Os3.0-7.0 re-creates Java layer Bitmap objects, passing the decoded pixel data byte[] to the Java layer for storage, while OS8.0 and above does not.

Through the above source code analysis, it can be calculated:

  1. Scale = opts. InTargetDensity/opts. InDensity = 440/480 scale. = 1.0 f

  2. scaledWidth=1100,scaledHeight=732

    //scaledWidth=(1200*440/480+0.5f)=1100.5f=1100 scaledWidth= Static_cast <int>(scaledWidth * scale + 0.5f); ScaledHeight = static_cast<int>(scaledHeight * scale +0.5f);Copy the code

So let’s see if the actual situation is consistent with the analysis, as shown in the figure below;

We get mWidth * mHeight=1100 * 732; The actual memory usage is 1100 x 732 x 4= 3220800 =3.07MB, but the original width and height =1200 x 799 x 4=3835200 =3.66 MB.

How is Bitmap memory allocated

How exactly is Bitmap memory allocated?

The first is to select decodeAllocator(see Choose decodeAllocator above). When selecting Allocator, consider the following factors: Whether to reuse the existing Bitmap, whether to scale the Bitmap, and whether to Hardware Bitmap. The selection strategy is summarized as follows:

Whether to reuse an existing Bitmap Whether the Bitmap will be scaled Is it a Hardware Bitmap Allocator type
is is ScaleCheckingAllocator
is no RecyclingPixelAllocator
no is is SkBitmap::HeapAllocator
HeapAllocator(Default Allocator)
BitmapFactory.doDecode() 
    -> SkBitmap.tryAllocPixels() 
        -> Allocator.allocPixelRef()
Copy the code

The following code

Isolated from the doDecode method... SkBitmap::HeapAllocator heapAllocator; SkBitmap::Allocator* decodeAllocator; if (javaBitmap ! = nullptr && willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. This allocator // also checks that the recycled javaBitmap is large enough. decodeAllocator = &scaleCheckingAllocator; } else if (javaBitmap ! = nullptr) { decodeAllocator = &recyclingAllocator; } else if (willScale || isHardware) { // This will allocate pixels using a HeapAllocator, // for scale case: there will be an extra scaling step. // for hardware case: there will be extra swizzling & upload to gralloc step. decodeAllocator = &heapAllocator; } else { decodeAllocator = &defaultAllocator; }...Copy the code
/ / http://androidxref.com/8.0.0_r4/xref/external/skia/src/core/SkBitmap.cpp bool SkBitmap: : tryAllocPixels (Allocator * allocator, SkColorTable* ctable) { HeapAllocator stdalloc; if (nullptr == allocator) { allocator = &stdalloc; } return allocator->allocPixelRef(this, ctable); }Copy the code

There are four types of Allocator, and we’ll look at only two of them.

1. The first lookSkBitmap::HeapAllocatorMemory allocation process as decodeAllocator.

  • SkBitmap::tryAllocPixels
  • SkBitmap::HeapAllocator::allocPixelRef
  • SkMallocPixelRef::NewAllocate
  • [SkMemory_malloc::sk_malloc_flags](/SkMemory_malloc

The core is that malloc() is eventually called to allocate the specified size of memory

/ / http://androidxref.com/8.0.0_r4/xref/external/skia/src/core/SkBitmap.cpp bool SkBitmap: : tryAllocPixels (Allocator * allocator, SkColorTable* ctable) { HeapAllocator stdalloc; if (nullptr == allocator) { allocator = &stdalloc; } return allocator->allocPixelRef(this, ctable); }Copy the code
/ / http://androidxref.com/8.0.0_r4/xref/external/skia/src/core/SkBitmap.cpp / * * We explicitly use the same allocator for our pixels that SkMask does, so that we can freely assign memory allocated by one class to the other. */ bool SkBitmap::HeapAllocator::allocPixelRef(SkBitmap* dst, SkColorTable* ctable) { const SkImageInfo info = dst->info(); if (kUnknown_SkColorType == info.colorType()) { // SkDebugf("unsupported config for info %d\n", dst->config()); return false; } sk_sp<SkPixelRef> pr(SkMallocPixelRef::NewAllocate(info, dst->rowBytes(), ctable)); if (! pr) { return false; } dst->setPixelRef(std::move(pr), 0, 0); // since we're already allocated, we lockPixels right away dst->lockPixels(); return true; }Copy the code
/ / http://androidxref.com/8.0.0_r4/xref/external/skia/src/core/SkMallocPixelRef.cpp SkMallocPixelRef * SkMallocPixelRef::NewAllocate(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { auto sk_malloc_nothrow = [](size_t size) { return sk_malloc_flags(size, 0); }; return NewUsing(sk_malloc_nothrow, info, rowBytes, ctable); }Copy the code
/ / http://androidxref.com/8.0.0_r4/xref/external/skia/src/ports/SkMemory_malloc.cpp void * sk_malloc_flags (size_t size, unsigned flags) { void* p = malloc(size); if (flags & SK_MALLOC_THROW) { return throw_on_failure(size, p); } else { return p; }}Copy the code

2. Look atHeapAllocatorMemory allocation process as decodeAllocator.

  • SkBitmap::tryAllocPixels
  • HeapAllocator::allocPixelRef
  • android::Bitmap::allocateHeapBitmap
/ / http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/jni/android/graphics/Graphics.cpp bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable); return !! mStorage; }Copy the code
/ / http://androidxref.com/8.0.0_r4/xref/frameworks/base/libs/hwui/hwui/Bitmap.cpp static sk_sp < Bitmap > allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { void* addr = calloc(size, 1); if (! addr) { return nullptr; } return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, ctable)); }Copy the code

Similarly for RecyclingPixelAllocator and ScaleCheckingAllocator, malloc()/ calloc() allocates memory. I’m not going to do further analysis here.

Bitmap overall structure

Finally, the overall structure of Bitmap is sorted out

  • These images in the App are actually BitmapDrawable

  • BitmapDrawable is a wrapper around a Bitmap

  • A Bitmap is a wrapper around SkBitmap. Specifically, the implementation of Bitmap includes the Java layer and the JNI layer,

The JNI layer depends on Skia.

  • SkBitmap can be understood simply as an array of bytes in memory

Reference article:

androidxref.com–BitmapFactory.cpp

Bitmap load memory footprint thoroughly analyzed

Android system source code analysis -Bitmap series