Android应用OutOfMemory -- 2.你对Bitmap了解有多少?

2,090 阅读10分钟

复习知识点

相关阅读

OOM系列文章

Android应用OutOfMemory -- 1.OOM机制了解

ANR系列文章

Android应用ANR源码分析--1.ANR触发机制了解

尺寸1200 * 799,体积341.41kb的本地图片加载到内存会占用多少内存呢?

image.png

是不是脑海里已经开始计算: 宽 * 高 * 清晰度 =1200 * 799 * 4=3835200约=3.66Mb

下面来看demo测试

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

测试机:XiaoMi-8 Android9.0

 getAllocationByteCount...3220800...getByteCount..3220800..

哈哈,是不是又不太符合预期? 为什么呢?

咱们就要从Bitmap创建说起了。

介于不同Android版本Bitmap相关代码差异大:

  • Android 2.3 (API10) 以及之前 - 像素数据保存在 native heap
  • Android 3.0 到 Android 7.1 (API11-26) - 像素数据保存在 java heap
  • Android 8.0 以及之后 - 像素数据保存在 native heap

这里我们直接入手Android 8.0来分析

BitmapFactory.decodeResource()
    -> BitmapFactory.decodeResourceStream()
        ->BitmapFactory.decodeStream()
            ->BitmapFactory.nativeDecodeAsset()
//BitmapFactory.java
public static Bitmap decodeResource(Resources res, int id, Options opts) {
        ...
        bm = decodeResourceStream(res, value, is, null, opts);
    ...
    return bm;
}
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) {
    validate(opts);
    if (opts == null) {
        opts = new Options();
    }
    //这里主要计算density,iTargetDensity,重要参数后面有用
    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);
}

decodeResourceStream方法主要是计算opts.inTargetDensity和opts.inDensity。 其中TargetDensity指的是我们手机的dpi,而dpi计算方式 屏幕宽平方 * 高平方/屏幕尺寸;
而opts.inDensity指的是你资源放置的目录:

目录范围
mdpi120dpi-160dpi
hdpi160dpi~240dpi
xhdpi240-320dpi
xxhdpi320-480dpi
xxxhdpi480-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;
}

Native层代码

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

首先找native注册的gMethod方法

static const JNINativeMethod gMethods[] = {
    {   "nativeDecodeAsset",
        "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeAsset
    }
 }
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));
    //主要是doDecode方法
    return doDecode(env, stream.release(), padding, options);
}

nativeDecodeAsset方法内容很简单,其内容主要是调用了doDecode方法,而构建bitmap对象核心逻辑都在‘doDecode’方法内。 这里说一下无论上层资源来源于:

  • File
  • Resource
  • ByteArray
  • Stream
  • FileDescriptor 最终都会通过doDecode来创建Bitmap。由于doDecode方法比较长,我们先对其关键步骤进行分解大致为:
  • Update with options supplied by the client.(更新opts参数)
  • Create the codec.(创建编解码器)
  • Handle sampleSize. (跟 BitmapFactory.Options.inSampleSize 参数相关)
  • Set the decode colorType.(设置解码颜色类型)
  • Handle scale. (跟 BitmapFactory.Options.inScaled 参数相关)
  • Choose decodeAllocator(选择内存分配器)
  • decodingBitmap AllocPixels (解码原始图片分配内存)
  • outputBitmap AllocPixels (创建真正输出内存地址,不一定走)
  • Use SkAndroidCodec to perform the decode(解码图片)
  • Create the java bitmap(创建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);
}

前面已经对整个方法的关键步骤进行了拆解,这里在提几点:

  • 1 .如果java层设置了options.inJustDecodeBounds=true,并不会加载图片到内存,而是直接返回null,从options可以获取到原始宽高。
    1. 关于是否返回原始图片取决于两点,采样率是否insampleSize>1,是否需要放缩scale=1.f

      采样率一般是加载本地或者网络图片时根据目标尺寸去设置,当然其他场景也可设置;

      放缩比例则是由加载resource目录下图片时根据资源存放目录及机型来判断。

    1. OS3.0-7.0再创建Java层Bitmap对象时会将解码的像素数据byte[]交给java层来保存,而OS8.0及以上则不会。

通过上面的源码解析,可以计算得到:

  1. scale= opts.inTargetDensity/opts.inDensity=440/480,则scale!=1.0f

  2. scaledWidth=1100,scaledHeight=732

     由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);
    

那么我们再看下实际情况是否与分析一致,见下图; wecom-temp-2e3bd530e38f7d14ba2058dad7ce5466.png

我们得到的mWidth * mHeight=1100 * 732;计算实际占用内存为:1100 * 732 * 4 =3220800约=3.07MB,而不是原始宽高 =1200 * 799 * 4=3835200约=3.66Mb

Bitmap内存如何分配

到这里我们忍不住好奇,Bitmap内存到底是如何分配的呢?

首先是选择 decodeAllocator (见上文提到的 Choose decodeAllocator(选择内存分配器))。 选择 Allocator 时考虑的因素包括:是否复用已有 Bitmap,是否会缩放 Bitmap,是否是 Hardware Bitmap。选择策略总结如下:

是否复用已有 Bitmap是否会缩放 Bitmap是否是 Hardware BitmapAllocator类型
-ScaleCheckingAllocator
-RecyclingPixelAllocator
SkBitmap::HeapAllocator
---HeapAllocator (缺省的Allocator)
BitmapFactory.doDecode() 
    -> SkBitmap.tryAllocPixels() 
        -> Allocator.allocPixelRef()

代码如下

  从doDecode方法中单独摘取出来
  ...
 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;
    }
    ...
// 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);
}

Allocator 的类型有四种,我们只看其中的两种。

1. 先看 SkBitmap::HeapAllocator 作为 decodeAllocator 进行内存分配的流程。

核心是最终会调用 malloc() 分配指定大小的内存

//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);
}
//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;
}
//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);
}
//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;
    }
}

2.再来看 HeapAllocator 作为 decodeAllocator 进行内存分配的流程。

//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;
}
//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));
}

对于 RecyclingPixelAllocator 和 ScaleCheckingAllocator 的情况类似,还是由 malloc()/ calloc() 分配内存。这里不做继续分析。

Bitmap整体结构

最后来梳理下Bitmap整体结构

1641646340(1).png

  • App 中这些图片实际上是 BitmapDrawable

  • BitmapDrawable 是对 Bitmap 的包装

  • Bitmap 是对 SkBitmap 的包装。具体说来, Bitmap 的具体实现包括 Java 层和 JNI 层, JNI 层依赖 Skia

  • SkBitmap 可简单理解为内存中的一个字节数组

参考文章:

androidxref.com--BitmapFactory.cpp

Bitmap加载内存占用彻底分析

Android系统源码分析-Bitmap系列