演变:
关于Bitmap
内存分配的变化
On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.
Android 3.0之前 Bitmap对象存在Java堆 像素数据存在Native内存中
不手动调用recycle
Bitmap Native内存的回收完全依赖finalize
时机不太可控Android 3.0~Android 7.0 Bitmap对象和像素数据统一放到Java堆中 不调用
recycle
Bitmap内存也会是随对象一起被回收
不过Bitmap消耗内存太大 还是不合适Android 8.0 利用
NativeAllocationRegistry
实现像素数据存放到Native中 并且新增了硬件位图Hardware Bitmap
可以减少图片内存并且提升绘制效率
NativeAllocationRegistry:
一种实现 可以将Bitmap
内存放到Native
中 也可以做到和对象一起快速释放 同时GC的时候也能考虑到这些内存防止被滥用
位图在Android中的存储策略被划分为了两个部分,一个是基本信息相关的数据结构,存放在Dalvik heap中,而像素相关的数据结构则存放在另外一个内存空间,而Android版本演变改变的,则是这个内存空间的位置,在native层中同样会有一个位图的数据结构SkBitmap(是不是瞬间想到了Android使用的图形渲染引擎Skia),同义于java层的Bitmap:
我们现在直接分析Android O以后的Bitmap存储策略:
首先我们需要在Java层创建一个Bitmap,Bitmap.createBitmap()
,最终所有的重载方法都会走向下面的JNI调用:
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
if (config == Config.HARDWARE) {
throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE");
}
if (colorSpace == null) {
throw new IllegalArgumentException("can't create bitmap without a color space");
}
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 {
if (!(colorSpace instanceof ColorSpace.Rgb)) {
throw new IllegalArgumentException("colorSpace must be an RGB color space");
}
ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
if (parameters == null) {
throw new IllegalArgumentException("colorSpace must use an ICC "
+ "parametric transfer function");
}
ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
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;
}
最终会走到JNI的nativeCreate()
方法,无论是Android O以前还是以后,其实在java层的代码都没有变化,那么改变一定就出现在了native层的调用上:
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层是怎么处理Bitmap的?
这里的nativeCreate()指向了方法Bitmap_creator
,我们继续往下看这个方法:
Android O版本以下的native调用:
xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
706 jint offset, jint stride, jint width, jint height,
707 jint configHandle, jboolean isMutable) {
708 SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
709 if (NULL != jColors) {
710 size_t n = env->GetArrayLength(jColors);
711 if (n < SkAbs32(stride) * (size_t)height) {
712 doThrowAIOOBE(env);
713 return NULL;
714 }
715 }
716
717 // ARGB_4444 is a deprecated format, convert automatically to 8888
718 if (colorType == kARGB_4444_SkColorType) {
719 colorType = kN32_SkColorType;
720 }
721
722 SkBitmap bitmap;
723 bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
724
725 Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
726 if (!nativeBitmap) {
727 return NULL;
728 }
729
730 if (jColors != NULL) {
731 GraphicsJNI::SetPixels(env, jColors, offset, stride,
732 0, 0, width, height, bitmap);
733 }
734
735 return GraphicsJNI::createBitmap(env, nativeBitmap,
736 getPremulBitmapCreateFlags(isMutable));
737}
大家注意到nativeBitmap的创建方式是利用GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL)
来进行构建,最后返回给上层的位图也是以nativeBitmap为基准来调用GraphicsJNI图像渲染引擎接口进行图片绘制,很显然,我们需要知道在allocateJavaPixelRef
中,接下来我们点进去看一看:
xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
486 android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
487 SkColorTable* ctable) {
488 const SkImageInfo& info = bitmap->info();
489 if (info.colorType() == kUnknown_SkColorType) {
490 doThrowIAE(env, "unknown bitmap configuration");
491 return NULL;
492 }
493
494 size_t size;
495 if (!computeAllocationSize(*bitmap, &size)) {
496 return NULL;
497 }
498
499 // we must respect the rowBytes value already set on the bitmap instead of
500 // attempting to compute our own.
501 const size_t rowBytes = bitmap->rowBytes();
502
503 jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
504 gVMRuntime_newNonMovableArray,
505 gByte_class, size);
506 if (env->ExceptionCheck() != 0) {
507 return NULL;
508 }
509 SkASSERT(arrayObj);
510 jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
511 if (env->ExceptionCheck() != 0) {
512 return NULL;
513 }
514 SkASSERT(addr);
515 android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
516 info, rowBytes, ctable);
517 wrapper->getSkBitmap(bitmap);
518 // since we're already allocated, we lockPixels right away
519 // HeapAllocator behaves this way too
520 bitmap->lockPixels();
521
522 return wrapper;
523}
wrapper顾名思义,就是返回的一个位图包装,它通过JNI创建并弱引用jweakRef
了一个java层的位图对象,并且申明了一个nullPtr(空)的强引用jstrongRef
,Bitmap``::Bitmap
对应代码如下:
xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp
Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
: mPixelStorageType(PixelStorageType::Java) {
env->GetJavaVM(&mPixelStorage.java.jvm);
mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);//创建对Java层对象的弱引用
mPixelStorage.java.jstrongRef = nullptr;
mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
// Note: this will trigger a call to onStrongRefDestroyed(), but
// we want the pixel ref to have a ref count of 0 at this point
mPixelRef->unref();
}
利用wrapper->getSkBitmap(bitmap)
来进行强引用和弱引用的控制切换:
xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp
353void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
354 assertValid();
355 android::AutoMutex _lock(mLock);
356 // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
357 // would require locking the pixels first.
358 outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
359 outBitmap->setPixelRef(refPixelRefLocked())->unref();
360 outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
361}
240 SkPixelRef* Bitmap::refPixelRefLocked() {
241 mPixelRef->ref();
242 if (mPixelRef->unique()) {
243 // We just restored this from 0, pin the pixels and inc the strong count
244 // Note that there *might be* an incoming onStrongRefDestroyed from whatever
245 // last unref'd
246 pinPixelsLocked();
247 mPinnedRefCount++;
248 }
249 return mPixelRef.get();
250}
!!!在refPixelRefLocked中会调用pinPixelsLocked
310 void Bitmap::pinPixelsLocked() {
311 switch (mPixelStorageType) {
312 case PixelStorageType::Invalid:
313 LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");
314 break;
315 case PixelStorageType::External:
316 case PixelStorageType::Ashmem:
317 // Nothing to do
318 break;
319 case PixelStorageType::Java: {
320 JNIEnv* env = jniEnv();
321 if (!mPixelStorage.java.jstrongRef) {
322 mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>(
323 env->NewGlobalRef(mPixelStorage.java.jweakRef));
324 if (!mPixelStorage.java.jstrongRef) {
325 LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels");
326 }
327 }
328 break;
329 }
330 }
331}
在native层随时添加删除一个强引用,这样有利于更好地配合Java堆的垃圾回收。
最后调用:
GraphicsJNI::createBitmap(env, nativeBitmap,getPremulBitmapCreateFlags(isMutable));
Android O版本以上的native调用:
首先同样看一看在native层的Bitmap_creator
是如何在底层构建一个Bitmap:
652 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
653 jint offset, jint stride, jint width, jint height,
654 jint configHandle, jboolean isMutable,
655 jfloatArray xyzD50, jobject transferParameters) {
656 SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
657 if (NULL != jColors) {
658 size_t n = env->GetArrayLength(jColors);
659 if (n < SkAbs32(stride) * (size_t)height) {
660 doThrowAIOOBE(env);
661 return NULL;
662 }
663 }
664
665 // ARGB_4444 is a deprecated format, convert automatically to 8888
666 if (colorType == kARGB_4444_SkColorType) {
667 colorType = kN32_SkColorType;
668 }
669
670 SkBitmap bitmap;
671 sk_sp<SkColorSpace> colorSpace;
672
673 if (colorType != kN32_SkColorType || xyzD50 == nullptr || transferParameters == nullptr) {
674 colorSpace = GraphicsJNI::colorSpaceForType(colorType);
675 } else {
676 SkColorSpaceTransferFn p = GraphicsJNI::getNativeTransferParameters(env, transferParameters);
677 SkMatrix44 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50);
678 colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix);
679 }
680
681 bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace));
682
683 sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
684 if (!nativeBitmap) {
685 ALOGE("OOM allocating Bitmap with dimensions %i x %i", width, height);
686 doThrowOOME(env);
687 return NULL;
688 }
689
690 if (jColors != NULL) {
691 GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap);
692 }
693
694 return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
695}
咋眼一看,和Android O以下的构建方式大同小异,但是眼尖的同学可能已经发现了最主要的区别,那就是nativeBitmap的构建方式发生了变化Bitmap::allocateHeapBitmap(&bitmap)
,让我们点进去一探究竟:
91sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) {
92 return allocateBitmap(bitmap, &android::allocateHeapBitmap);
93}
我们继续点进allocateBitmap(bitmap, &android::allocateHeapBitmap)
50 typedef sk_sp<Bitmap> (*AllocPixelRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes);
52 static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
53 const SkImageInfo& info = bitmap->info();
54 if (info.colorType() == kUnknown_SkColorType) {
55 LOG_ALWAYS_FATAL("unknown bitmap configuration");
56 return nullptr;
57 }
58
59 size_t size;
60
61 // we must respect the rowBytes value already set on the bitmap instead of
62 // attempting to compute our own.
63 const size_t rowBytes = bitmap->rowBytes();
64 if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
65 return nullptr;
66 }
67
68 auto wrapper = alloc(size, info, rowBytes);
69 if (wrapper) {
70 wrapper->getSkBitmap(bitmap);
71 }
72 return wrapper;
73}
注意!!!!
大家有没有发现,warpper的引用相对于Android O发生了变化,auto wrapper = alloc(size, info, rowBytes)
,warpper是上面typedefine的一个SkBitmap结构体,因此Android O以上和以下的版本在Bitmap的构建过程中的主要区别就在于此,当将结构体封装好数据并传递给wrapper以后,wrapper会通过wrapper->getSkBitmap(bitmap)
来设置位图:
288 void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
289 outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
290 if (isHardware()) {
291 if (uirenderer::Properties::isSkiaEnabled()) {
292 outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(),
293 info().colorType(), info().alphaType(),
294 nullptr));
295 } else {
296 outBitmap->allocPixels(info());
297 }
298 uirenderer::renderthread::RenderProxy::copyGraphicBufferInto(graphicBuffer(), outBitmap);
299 if (mInfo.colorSpace()) {
300 sk_sp<SkPixelRef> pixelRef = sk_ref_sp(outBitmap->pixelRef());
301 outBitmap->setInfo(mInfo);
302 outBitmap->setPixelRef(std::move(pixelRef), 0, 0);
303 }
304 return;
305 }
306 outBitmap->setInfo(mInfo, rowBytes());
307 outBitmap->setPixelRef(sk_ref_sp(this), 0, 0);
308}
在这个方法中,就在native层分配了内存来存储像素信息以及传入了像素数组引用指针sk_ref_sp
,后面就会调用
690 if (jColors != NULL) {
691 GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap);
692 }
去在引擎中设置像素和位图的相关信息,并返回位图给java上层。