Android6.0 Bitmap存储以及Parcel传输
Posted _houzhi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android6.0 Bitmap存储以及Parcel传输相关的知识,希望对你有一定的参考价值。
如果想要对android Bitmap进行更多的操作,理解好Bitmap的实现将会有非常大的帮助,另外Android在6.0中增加了asm存储图片。这篇文章就通过源码来分析Android6.0中的Bitmap。本文主要分析Java层与native层的Bitmap,以及Bitmap的储存和Parcel传输。源码基于6.0,所以会有一些新的特性。
Bitmap存储方式以及包含的属性
计算机里面图片都是作为数组来存储的,而在Android中Bitmap也是一样。在Java层的Bitmap数组保存为mBuffer。而在native层,Bitmap有四种保存方式,在Bitmap.h文件中有个枚举类:
enum class PixelStorageType {
Invalid,
External,
Java,
Ashmem,
};
Invalid表示图片已经失效了,一般图片free掉之后就会是这种状态。External是外部存储。Java是表示这个Bitmap对应着Java的Bitmap,此时Bitmap会保存着Java层Bitmap的存储数组的弱引用。而Ashmem则是对应着匿名共享内存,表示图片是存储在匿名共享内存当中。后三种类型在Bitmap中对应着一个union类型:
union {
struct {
void* address;
void* context;
FreeFunc freeFunc;
} external;
struct {
void* address;
int fd;
size_t size;
} ashmem;
struct {
JavaVM* jvm;
jweak jweakRef;
jbyteArray jstrongRef;
} java;
} mPixelStorage;
另外因为图片是直接保存在一片内存区域,那么它也可以保存在匿名共享内存当中,这就是Fresco在5.0之前干的事情,而将图片放到匿名共享内存当中,不会自动GC,应用会更加流畅,因为不在Java堆,也不用关心Java堆大小的限制而导致OOM。
另外还包含几种属性:
width, height: 图片宽度和高度
mDensity: 设备密度
colorType: 图片颜色类型,RGB或者gray等,图片通道数量
rowBytes: 用来表示图片像素的字节数
alphaType: 图像透明度类型,是否有透明度或者没有透明度
isMutable: 是否易变的
这些属性在进行Parcel传输的时候,都会通过Parcel传递,另外也是为了方便图片操作。
Java层与native层Bitmap
Bitmap的主要实现是在native层,Java层的Bitmap相当于是native层的接口。
Java层Bitmap
Bitmap实际上分为Java层和native层的,Java层包含了一个mBuffer数组用来存储像素,但总的来说Java层只是一个方便Java层应用访问的接口,最终还是通过native层来保存图片内容。在Java层中,我们常用的接口可能是createBitmap,getPixel,setPixel等,但实际上这些函数最终都是调用native层接口实现的,下面是Java层Bitmap的创建函数:
private static Bitmap createBitmap(DisplayMetrics display, int width, int height,
Config config, boolean hasAlpha) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true); // 这!!!
if (display != null) {
bm.mDensity = display.densityDpi;
}
bm.setHasAlpha(hasAlpha);
if (config == Config.ARGB_8888 && !hasAlpha) {
nativeErase(bm.mFinalizer.mNativeBitmap, 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方法,具体可以看Bitmap native 方法。我们重点看createBitmap。
另外在Java层与native层对应的标记是mNativeBitmap变量,它保存的是native层Bitmap的指针地址。这样在native层通过reinterpret_cast即可得到具体的对象。关于这个,可以看Binder机制的实现Android源码代理模式—Binder。
native层
既然Bitmap的具体实现都是在native,那么看一下native层的Bitmap,native层的Bitmap在frameworks/base/core/jni/android/graphics/Bitmap.cpp中,对应的jni注册部分也在该文件下。看一下native层Bitmap的创建nativeCreate对应的Bitmap_creator函数:
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);
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
if (n < SkAbs32(stride) * (size_t)height) {
doThrowAIOOBE(env);
return NULL;
}
}
// ARGB_4444 is a deprecated format, convert automatically to 8888
if (colorType == kARGB_4444_SkColorType) {
colorType = kN32_SkColorType;
}
SkBitmap bitmap;
bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
if (!nativeBitmap) {
return NULL;
}
if (jColors != NULL) {
GraphicsJNI::SetPixels(env, jColors, offset, stride,
0, 0, width, height, bitmap);
}
return GraphicsJNI::createBitmap(env, nativeBitmap,
getPremulBitmapCreateFlags(isMutable));
}
看看Bitmap的创建函数,从一创建开始,Bitmap就是先出现在native层的,Android中2D绘图是由skia框架实现的,在上述代码中就对应着SkBitmap。
而对于Java存储类型的Bitmap的创建是由GraphicsJNI的allocateJavaPixelRef完成的,allocateJavaPixelRef是从Java层分配像素数组,看看allocateJavaPixelRef的源码
android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();
if (info.fColorType == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return NULL;
}
size_t size;
if (!computeAllocationSize(*bitmap, &size)) {
return NULL;
}
// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
// 在这里分配
jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
gVMRuntime_newNonMovableArray,
gByte_class, size); //在这创建Java层Array
if (env->ExceptionCheck() != 0) {
return NULL;
}
SkASSERT(arrayObj);
jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj); //获取地址
if (env->ExceptionCheck() != 0) {
return NULL;
}
SkASSERT(addr);
android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
info, rowBytes, ctable); //创建native层对象, 在Bitmap构造函数中mPixelStorage中存储了jweak引用。
wrapper->getSkBitmap(bitmap); // 在这里会将mPixelStorage的弱引用转换为强引用
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();
return wrapper;
}
可以看到,native层是通过JNI方法,在Java层创建一个数组对象的,这个数组是对应在Java层的Bitmap对象的buffer数组,所以图像还是保存在Java堆的。而在native层这里它是通过weak指针来引用的,在需要的时候会转换为strong指针,用完之后又去掉strong指针,这样这个数组对象还是能够被Java堆自动回收。可以看一下native层的Bitmap构造函数:
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();
}
里面jstrongRef一开始是赋值为null的,但是在bitmap的getSkBitmap方法会使用weakRef给他赋值:
void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
assertValid();
android::AutoMutex _lock(mLock);
// Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
// would require locking the pixels first.
outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
outBitmap->setPixelRef(refPixelRefLocked())->unref(); //refPixelRefLocked
outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}
void Bitmap::pinPixelsLocked() { //refPixelRefLocked会调用这个方法
switch (mPixelStorageType) {
case PixelStorageType::Invalid:
LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");
break;
case PixelStorageType::External:
case PixelStorageType::Ashmem:
// Nothing to do
break;
case PixelStorageType::Java: {
JNIEnv* env = jniEnv();
if (!mPixelStorage.java.jstrongRef) {
mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>(
env->NewGlobalRef(mPixelStorage.java.jweakRef));//赋值
if (!mPixelStorage.java.jstrongRef) {
LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels");
}
}
break;
}
}
}
在native层随时添加删除一个强引用,这样有利于更好地配合Java堆的垃圾回收。图片的数组可能会是非常耗内存的。
在创建了native层的Bitmap后,再用GraphicsJNI的createBitmap创建Java层的Bitmap对象:
jobject GraphicsJNI::createBitmap(JNIEnv* env, android::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);
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninePatchChunk, ninePatchInsets);//创建Java层Bitmap对象
hasException(env); // For the side effect of logging.
return obj;
}
在创建过程中,将刚刚创建的Java层Array和native层的bitmap指针也都会传给Java层Bitmap的构造函数。
另外对于External存储类型的Bitmap,它的创建如下:
Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
: mPixelStorageType(PixelStorageType::External) {
mPixelStorage.external.address = address;
mPixelStorage.external.context = context;
mPixelStorage.external.freeFunc = freeFunc;
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();
}
而Ashmem则是保存一个fd,以及asm地址和大小:
Bitmap::Bitmap(void* address, int fd,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
: mPixelStorageType(PixelStorageType::Ashmem) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = ashmem_get_size_region(fd);
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();
}
native层Bitmap会针对不同的存储类型,做不同的处理。
Parcel传递
首先在Java层Bitmap实现了Parcelable接口,所以他是能够通过Parcel来传递的,看看Bitmap的parcelable部分的源码:
public final class Bitmap implements Parcelable {
...
/**
* Write the bitmap and its pixels to the parcel. The bitmap can be
* rebuilt from the parcel by calling CREATOR.createFromParcel().
* @param p Parcel object to write the bitmap data into
*/
public void writeToParcel(Parcel p, int flags) {
checkRecycled("Can't parcel a recycled bitmap");
if (!nativeWriteToParcel(mFinalizer.mNativeBitmap, mIsMutable, mDensity, p)) {
throw new RuntimeException("native writeToParcel failed");
}
}
public static final Parcelable.Creator<Bitmap> CREATOR
= new Parcelable.Creator<Bitmap>() {
public Bitmap More ...createFromParcel(Parcel p) {
Bitmap bm = nativeCreateFromParcel(p);
if (bm == null) {
throw new RuntimeException("Failed to unparcel Bitmap");
}
return bm;
}
public Bitmap[] More ...newArray(int size) {
return new Bitmap[size];
}
};
...
}
写入和读取分别调用了nativeWriteToParcel,nativeCreateFromParcel。先看看nativeWriteToParcel对应的native层方法Bitmap_writeToParcel:
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
jlong bitmapHandle,
jboolean isMutable, jint density,
jobject parcel) {
//根据handle创建native层图片,写入图片相关的一些附加信息,width,height,colorType,density等等。
if (parcel == NULL) {
SkDebugf("------- writeToParcel null parcel\\n");
return JNI_FALSE;
}
android::Parcel* p = android::parcelForJavaObject(env, parcel);
SkBitmap bitmap;
android::Bitmap* androidBitmap = reinterpret_cast<Bitmap*>(bitmapHandle);
androidBitmap->getSkBitmap(&bitmap);
p->writeInt32(isMutable);
p->writeInt32(bitmap.colorType());
p->writeInt32(bitmap.alphaType());
p->writeInt32(bitmap.width());
p->writeInt32(bitmap.height());
p->writeInt32(bitmap.rowBytes());
p->writeInt32(density);
if (bitmap.colorType() == kIndex_8_SkColorType) {
SkColorTable* ctable = bitmap.getColorTable();
if (ctable != NULL) {
int count = ctable->count();
p->writeInt32(count);
memcpy(p->writeInplace(count * sizeof(SkPMColor)),
ctable->readColors(), count * sizeof(SkPMColor));
} else {
p->writeInt32(0); // indicate no ctable
}
}
// 关键看这部分传输代码!!!!
// Transfer the underlying ashmem region if we have one and it's immutable.
android::status_t status;
int fd = androidBitmap->getAshmemFd(); //获取匿名共享内存,如果是图片是在匿名共享内存
if (fd >= 0 && !isMutable && p->allowFds()) { //如果成功获取,并且图片不是mutable,同时允许fd(mAllowFds默认为True)
status = p->writeDupImmutableBlobFileDescriptor(fd); //最终会直接把文件fd传过去
if (status) {
doThrowRE(env, "Could not write bitmap blob file descriptor.");
return JNI_FALSE;
}
return JNI_TRUE;
}
// 如果不能通过fd传递,则传输Blob数据,也就是相当于直接把像素数据传递过去。
// Copy the bitmap to a new blob.
bool mutableCopy = isMutable;
size_t size = bitmap.getSize();
android::Parcel::WritableBlob blob;
status = p->writeBlob(size, mutableCopy, &blob);
if (status) {
doThrowRE(env, "Could not copy bitmap to parcel blob.");
return JNI_FALSE;
}
bitmap.lockPixels();
const void* pSrc = bitmap.getPixels();
if (pSrc == NULL) {
memset(blob.data(), 0, size);
} else {
memcpy(blob.data(), pSrc, size);
}
bitmap.unlockPixels();
blob.release();
return JNI_TRUE;
}
从源码可以知道,如果是匿名共享内存存储,那么writeToParcel会通过匿名共享内存的方式将匿名共享文件传递过去,看看writeDupFileDescriptor方法:
status_t Parcel::writeDupFileDescriptor(int fd)
{
int dupFd = dup(fd);
if (dupFd < 0) {
return -errno;
}
status_t err = writeFileDescriptor(dupFd, true /*takeOwnership*/);
if (err) {
close(dupFd);
}
return err;
}
如果是保存的数组数据,那么会直接将像素数据转换为Blob来传递。这是在6.0的源码中是如此的,在5.0的源码中,还没有增加这些东西,5.0的源码中只有普通的将像素存储区域memcopy来传。Android在3.0中增加了inBitmap,在4.4增加了不同大小的图片使用inBitmap。
而nativeCreateFromParcel对应了native层的Bitmap_createFromParcel,在6.0的源码里面源码如下(去掉了DEBUG_PARCEL):
static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
......
// 一开始读取图片相关的一些信息,比如说width, height, density, colorType等等,并存于SkImageInfo中。并且对ColorType的相关处理,这些占用的内存都很小,关键看像素的传递
SkColorTable* ctable = NULL;
if (colorType == kIndex_8_SkColorType) {
int count = p->readInt32();
if (count < 0 || count > 256) {
// The data is corrupt, since SkColorTable enforces a value between 0 and 256,
// inclusive.
return NULL;
}
if (count > 0) {
size_t size = count * sizeof(SkPMColor);
const SkPMColor* src = (const SkPMColor*)p->readInplace(size);
if (src == NULL) {
return NULL;
}
ctable = new SkColorTable(src, count);
}
}
// Read the bitmap blob.
size_t size = bitmap->getSize();
android::Parcel::ReadableBlob blob;
android::status_t status = p->readBlob(size, &blob); //这里对应writeDupFileDescriptor
if (status) {
SkSafeUnref(ctable);
doThrowRE(env, "Could not read bitmap blob.");
return NULL;
}
// 关键看这部分传输代码!!!!
// Map the bitmap in place from the ashmem region if possible otherwise copy.
Bitmap* nativeBitmap;
if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
// Dup the file descriptor so we can keep a reference to it after the Parcel
// is disposed.
int dupFd = dup(blob.fd());
if (dupFd < 0) {
blob.release();
SkSafeUnref(ctable);
doThrowRE(env, "Could not allocate dup blob fd.");
return NULL;
}
// Map the pixels in place and take ownership of the ashmem region.
nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
ctable, dupFd, const_cast<void*>(blob.data()), !isMutable);
SkSafeUnref(ctable);
if (!nativeBitmap) {
close(dupFd);
blob.release();
doThrowRE(env, "Could not allocate ashmem pixel ref.");
return NULL;
}
// Clear the blob handle, don't release it.
blob.clear();
} else {
// Copy the pixels into a new buffer.
nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
SkSafeUnref(ctable);
if (!nativeBitmap) {
blob.release();
doThrowRE(env, "Could not allocate java pixel ref.");
return NULL;
}
bitmap->lockPixels();
memcpy(bitmap->getPixels(), blob.data(), size);
bitmap->unlockPixels();
// Release the blob handle.
blob.release();
}
return GraphicsJNI::createBitmap(env, nativeBitmap,
getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}
这个是与writeToParcel相互对应的,如果是asm则直接读取文件fd,如果是数据,则传对应数据。
总结
上面就是Bitmap在Java层与native的表现,Bitmap的操作基本都是在native层,Java层与native层通过一个handle相互对应。在6.0Bitmap总共有四种存储形式,也增加了asm的存储。在进行Parcel传输的时候,针对asm,Parcel传输的fd,这样能够减少很多内存的消耗。在Android6.0内部,很多图片也开始存储在asm里面了。不过在Java层还没有提供将图片保存在匿名共享内存里面。
以上是关于Android6.0 Bitmap存储以及Parcel传输的主要内容,如果未能解决你的问题,请参考以下文章
Android拍照,相册选择图片以及Android6.0权限管理