Android Bitmap变迁与原理解析(4.x-8.x)


Java 层的创建 Bitmap 的所有 API 进入到 Native 层后,全都会走如下这四个步骤。






NativeAllocationRegistry 用于将 native 内存跟 Java 对象关联,并将它们注册到 Java 运行时。注册 Java 对象关联的 native 内存有几个好处:
当 Java 层 Bitmap 对象不可达后关联的 native 内存会由 nativeGetNativeFinalizer() 指定的方法来回收
static void Bitmap_destruct(BitmapWrapper* bitmap) {
delete bitmap;
}
static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
}
//we must ensure to not leak java Bitmap Object, this will recycle bitmap memory in native around GC, while it cannot be reclaim if the java bitmap is leak.
//下图流程稍有问题,实测为ReferenceQueueDaemon便利enqueue过程会直接调用Cleaner.clean开启流程,没有使用到VMRuntime和CleanerRuner,具体流程见BitmapSource


通过Bitmap的成员列表,就能看出一点眉目,Bitmap中有个byte[] mBuffer,其实就是用来存储像素数据的,很明显它位于java heap中:
public final class Bitmap implements Parcelable {
private static final String TAG = "Bitmap";
...
private byte[] mBuffer;
...
}

Java层Bitmap的创建最终还是会走向native层:Bitmap.cpp
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable) {
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
...
SkBitmap Bitmap;
Bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
<!--关键点1 像素内存分配-->
Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &Bitmap, NULL);
if (!nativeBitmap) {
return NULL;
}
...
<!--获取分配地址-->
jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
...
<!--创建Bitmap-->
android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
info, rowBytes, ctable);
wrapper->getSkBitmap(Bitmap);
Bitmap->lockPixels();
return wrapper;
}
这里只看关键点1,像素内存的分配:GraphicsJNI::allocateJavaPixelRef从这个函数名可以就可以看出,是在Java层分配,跟进去,也确实如此
由于只关心内存分配里其实就是在native层创建Java层byte[],并将这个byte[]作为像素存储结构,之后再通过在native层构建Java Bitmap对象的方式,将生成的byte[]传递给Bitmap.java对象:
jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
...<!--关键点1,构建java Bitmap对象,并设置byte[] mBuffer-->
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}
其实从8.0的Bitmap.java类也能看出区别,之前的 private byte[] mBuffer成员不见了,取而代之的是private final long mNativePtr,也就说,Bitmap.java只剩下一个壳了,具体如下:
public final class Bitmap implements Parcelable {
...
// Convenience for JNI access
private final long mNativePtr;
...
}
之前说过8.0之后的内存分配是在native,具体到代码是怎么样的表现呢?流程与8.0之前基本类似,区别在native分配时:

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) {
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
...
<!--关键点1 ,native层创建bitmap,并分配native内存-->
sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&Bitmap);
if (!nativeBitmap) {
return NULL;
}
...
return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
看一下allocateHeapBitmap如何分配内存
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
<!--关键点1 直接calloc分配内存-->
void* addr = calloc(size, 1);
if (!addr) {
return nullptr;
}
<!--关键点2 创建native Bitmap-->
return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes));
}
可以看出,8.0之后,Bitmap像素内存的分配是在native层直接调用calloc,所以其像素分配的是在native heap上, 这也是为什么8.0之后的Bitmap消耗内存可以无限增长,直到耗尽系统内存,也不会提示Java OOM的原因。
NativeAllocationRegistry是Android 8.0引入的一种辅助自动回收native内存的一种机制,==当Java对象因为GC被回收后,NativeAllocationRegistry可以辅助回收Java对象所申请的native内存==,拿Bitmap为例,入下:
Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
...
mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
<!--辅助回收native内存-->
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
sPreloadTracingNumInstantiatedBitmaps++;
sPreloadTracingTotalBitmapsSize += nativeSize;
}
}
当然这个功能也要Java虚拟机的支持,有机会再分析。