Android8.1 Bitmap对象的内存分配解析

在Android3.0以下版本的系统中,Bitmap对象的内存都是在native层分配的,它不会占用Java堆内存的空间。Android3.0之后,Bitmap内存的分配统一交给了Java堆进行分配,方便了内存的管理。而Android 8.0(Android O)之后的版本中,Bitmap内存分配又回到了native层,它是在native堆空间进行分配的。

我们接下来分析下Android8.1上的Bitmap对象的创建及内存分配过程。

常用的创建Bitmap对象的方法

使用Bitmap直接创建:

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

BitmapFactory创建:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);

Bitmap bitmap =BitmapFactory.decodeStream(getResources().openRawResource(R.drawable.img));

Bitmap bitmap = BitmapFactory.decodeFile("mnt/sdcard/img.jpg");

……

以上两类创建图像的方式都是我们在开发中比较常用到的,那么它们的内部是如何实现Bitmap对象的创建及内存空间分配的呢?

Java层的Bitmap类

我们首先从Java层的Bitmap对象开始,APP从上层调用相关接口,最直接接触的对象就是Java层的Bitmap类了,我们看下重点的方法:

Bitmap的构造方法(/frameworks/base/graphics/java/android/graphics/Bitmap.java):

    private final long mNativePtr;//保存ntive层bitmap对象的指针
    /**
     * Private constructor that must received an already allocated native bitmap
     * int (pointer).
     */
    // called from JNI
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }

        mWidth = width;
        mHeight = height;
        mIsMutable = isMutable;
        mRequestPremultiplied = requestPremultiplied;

        mNinePatchChunk = ninePatchChunk;
        mNinePatchInsets = ninePatchInsets;
        if (density >= 0) {
            mDensity = density;
        }

        mNativePtr = nativeBitmap;
        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
            Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
        registry.registerNativeAllocation(this, nativeBitmap);

        if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
            sPreloadTracingNumInstantiatedBitmaps++;
            sPreloadTracingTotalBitmapsSize += nativeSize;
        }
    }

可以看到,该构造方法是私有的,而且调用它时,必须要有一个已经在native层分配好内存的native bitmap的指针。从后文分析中可以知道,该构造方法是在native层调用的。

Bitmap类的createBitmap方法:

    public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
            @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
        ……
        Bitmap bm;
        // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
        // (we also avoid the unnecessary extra work of the else branch)
        if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
            bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
        } else {
            ……
            bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
                    d50.getTransform(), parameters);
        }

        if (display != null) {
            bm.mDensity = display.densityDpi;
        }
        bm.setHasAlpha(hasAlpha);
        if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) {
            nativeErase(bm.mNativePtr, 0xff000000);
        }
        // No need to initialize the bitmap to zeroes with other configs;
        // it is backed by a VM byte array which is by definition preinitialized
        // to all zeroes.
        return bm;
    }

这里的Bitmap是由native方法nativeCreate()创建的:

    private static native Bitmap nativeCreate(int[] colors, int offset,
                                              int stride, int width, int height,
                                              int nativeConfig, boolean mutable,
                                              @Nullable @Size(9) float[] xyzD50,
                                              @Nullable ColorSpace.Rgb.TransferParameters p);

它所对应的native方法,我们在后面详细分析。

Java层的BitmapFactory类

BitmapFactory类的decodeResource方法、decodeFile方法、decodeStream方法、decodeByteArray方法等,都可以创建Bitmap对象。我们具体来看它们的实现。

decodeResource方法(/frameworks/base/graphics/java/android/graphics/BitmapFactory.java):

    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        validate(opts);
        Bitmap bm = null;
        InputStream is = null; 
        
        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            ……
        } finally {
            ……
        }

        ……
        return bm;
    }
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {
        ……
        return decodeStream(is, pad, opts);
    }    
    

decodeFile方法:

    public static Bitmap decodeFile(String pathName, Options opts) {
        ……
        Bitmap bm = null;
        InputStream stream = null;
        try {
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
        } catch (Exception e) {
            ……
        } finally {
            ……
        }
        return bm;
    }

decodeStream方法:

    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
        ……
        Bitmap bm = null;

        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
        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;
    }

decodeByteArray方法:

    public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
        ……
        Bitmap bm;
        ……
            bm = nativeDecodeByteArray(data, offset, length, opts);
        ……
        return bm;
    }

这些方法最终调用的都是native方法来完成Bitmap对象创建的:

    private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
    private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
            int length, Options opts);

既然Bitmap和BitmapFactory都是通过相应的native方法来创建Bitmap对象的,那么我们接下来看下它们所对应的native层方法的实现。

BitmapFactory native层实现

我们以native方法nativeDecodeStream为例进行分析。

native方法nativeDecodeStream所对应的native层的实现是BitmapFactory.cpp中的nativeDecodeStream方法。

{   "nativeDecodeStream",
        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeStream
    }

nativeDecodeStream方法(/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp):

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    std::unique_ptr stream(CreateJavaInputStreamAdaptor(env, is, storage));

    if (stream.get()) {
        std::unique_ptr bufferedStream(
                SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
        SkASSERT(bufferedStream.get() != NULL);
        bitmap = doDecode(env, bufferedStream.release(), padding, options);
    }
    return bitmap;
}

其实其他几个native方法(nativeDecodeFileDescriptor、nativeDecodeAsset、nativeDecodeByteArray)所对应的native的代码实现,最终都是调用doDecode方法来创建Bitmap对象的。

我们来看doDecode方法(/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp):

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    ……
    if (javaBitmap != nullptr) {//如果设置Bitmap对象内存重用
        bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
        outputBitmap.notifyPixelsChanged();
        // If a java bitmap was passed in for reuse, pass it back
        return javaBitmap;
    }
    ……
    if (isHardware) {
        sk_sp hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap);
        if (!hardwareBitmap.get()) {
            return nullObjectReturn("Failed to allocate a hardware bitmap");
        }
        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);
}

这里,最终调用了Bitmap.cpp中的createBitmap方法(后面继续分析)。我们暂时分析到这,接下来看Bitmap的native层实现。

Bitmap native层实现

Bitmap的native方法nativeCreate,所对应的native实现是Bitmap.cpp中的Bitmap_creator方法。

{   "nativeCreate",             "([IIIIIIZ[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
        (void*)Bitmap_creator }

Bitmap_creator方法(/frameworks/base/core/jni/android/graphics/Bitmap.cpp):

static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                              jint offset, jint stride, jint width, jint height,
                              jint configHandle, jboolean isMutable,
                              jfloatArray xyzD50, jobject transferParameters) {
    ……
    SkBitmap bitmap;
    ……
    sk_sp nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
    ……
    return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}

最终这里和BitmapFactory.cpp的doDecode方法一样,也调用到了createBitmap方法!

createBitmap方法(/frameworks/base/core/jni/android/graphics/Bitmap.cpp):

jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
        int density) {
    bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
    bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
    // The caller needs to have already set the alpha type properly, so the
    // native SkBitmap stays in sync with the Java Bitmap.
    assert_premultiplied(bitmap->info(), isPremultiplied);
    BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
            reinterpret_cast(bitmapWrapper), bitmap->width(), bitmap->height(), density,
            isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);

    if (env->ExceptionCheck() != 0) {
        ALOGE("*** Uncaught exception returned from Java call!\n");
        env->ExceptionDescribe();
    }
    return obj;
}

在这里会从native层,调用Java层的Bitmap构造函数,创建Java层的Bitmap对象。

    gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
    gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "", "(JIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");

到了这里,Bitmap对象的内存已经在native层分配完成了,并且native层通过JNI调用,创建了Java层的Bitmap对象,通过函数的返回值,将该对象返回到Java层。Java层拿到该对象之后,进行处理,最终返回给客户。至此,Bitmap创建过程也就分析完了。但是是不是少了些什么呢?对了,内存是从哪分配的呢?我们接下来单独进行分析。

Bitmap对象的内存的分配阶段

Bitmap.cpp中的Bitmap_creator方法(/frameworks/base/core/jni/android/graphics/Bitmap.cpp)中,有一行代码:

sk_sp nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);

内存分配是通过调用Bitmap::allocateHeapBitmap方法来进行的,参数是SkBitmap的实例对象。该方法位置在/frameworks/base/libs/hwui/hwui/Bitmap.cpp中。

我们来看allocateHeapBitmap方法(/frameworks/base/libs/hwui/hwui/Bitmap.cpp):

sk_sp Bitmap::allocateHeapBitmap(SkBitmap* bitmap) {
   return allocateBitmap(bitmap, &android::allocateHeapBitmap);
}

调用了allocateBitmap方法(/frameworks/base/libs/hwui/hwui/Bitmap.cpp):

static sk_sp allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
    const SkImageInfo& info = bitmap->info();
    ……
    size_t size;

    // we must respect the rowBytes value already set on the bitmap instead of
    // attempting to compute our own.
    const size_t rowBytes = bitmap->rowBytes();
    if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
        return nullptr;
    }

    auto wrapper = alloc(size, info, rowBytes);//分配内存
    if (wrapper) {
        wrapper->getSkBitmap(bitmap);
    }
    return wrapper;
}

这里调用alloc(size, info, rowBytes)来进行内存分配。alloc是通过参数传递进来的,其实它是一个函数指针,我们来看它的定义。

代码位置:/frameworks/base/libs/hwui/hwui/Bitmap.cpp:

typedef sk_sp (*AllocPixelRef)(size_t allocSize, const SkImageInfo& info,
        size_t rowBytes);

我们通过allocateHeapBitmap方法的调用处,找到真正的实现是allocateHeapBitmap函数(/frameworks/base/libs/hwui/hwui/Bitmap.cpp):

static sk_sp allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
    void* addr = calloc(size, 1); //分配内存
    if (!addr) {
        return nullptr;
    }
    return sk_sp(new Bitmap(addr, size, info, rowBytes));
}

最终是通过调用calloc函数进行内存分配的。

扩展——netive的内存分配函数

C/C++为了管理内存,提供了几个用于内存管理的方法。

下面我们来简单了解一下。

函数名称(定义) 描述
void *calloc(int num, int size) 该函数接受2个参数:num代表分配内存单位的数量,size代表了每个单位的字节数;所以,分配内存量=numsize。调用该函数的结果就是,分配了numsize字节的内存,calloc分配的内存都是经过初始化的,每个字节内存的初始值都是0。
void *malloc(int num) 分配指定大小的内存块,并且该内存块是未经过初始化的。
void *realloc(void *address, int newsize) 重新分配内存,把address指向的内存,扩展到newsize。
void free(void *address) 释放address所指向的内存块。释放的是动态分配的内存。
free() 释放分配的内存。

void * 表示未确定类型的指针。C/C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

我们就现在明白了,在Bitmap分配native内存时,void* addr = calloc(size, 1);表示分配了size*1字节大小的内存。

小结

  1. Bitmap对象的创建,可以使用Bitmap.createBitmap方法,也可以使用BitmapFactory的相关方法来创建。

  2. Java层的Bitmap类的构造函数是私有的,Bitmap对象的创建,实际上是通过native层通过JNI调用来实现的。

  3. 在native层,Bitmap.cpp和BitmapFactory.cpp在Bitmap对象创建过程中,都调用到了Bitmap.cpp中的createBitmap方法。

  4. Bitmap.cpp中的createBitmap方法会从native层,调用Java层的Bitmap构造函数,创建Java层的Bitmap对象。

  5. Bitmap对象的内存,最终使用的是calloc函数进行分配的。

你可能感兴趣的:(Android技术实现原理解析)