在Android3.0以下版本的系统中,Bitmap对象的内存都是在native层分配的,它不会占用Java堆内存的空间。Android3.0之后,Bitmap内存的分配统一交给了Java堆进行分配,方便了内存的管理。而Android 8.0(Android O)之后的版本中,Bitmap内存分配又回到了native层,它是在native堆空间进行分配的。
我们接下来分析下Android8.1上的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对象开始,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方法,我们在后面详细分析。
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层方法的实现。
我们以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方法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.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函数进行内存分配的。
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字节大小的内存。
Bitmap对象的创建,可以使用Bitmap.createBitmap方法,也可以使用BitmapFactory的相关方法来创建。
Java层的Bitmap类的构造函数是私有的,Bitmap对象的创建,实际上是通过native层通过JNI调用来实现的。
在native层,Bitmap.cpp和BitmapFactory.cpp在Bitmap对象创建过程中,都调用到了Bitmap.cpp中的createBitmap方法。
Bitmap.cpp中的createBitmap方法会从native层,调用Java层的Bitmap构造函数,创建Java层的Bitmap对象。
Bitmap对象的内存,最终使用的是calloc函数进行分配的。