Android Bitmap理解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Bitmap理解相关的知识,希望对你有一定的参考价值。
参考技术A 参考:
android Bitmap 详解:关于 Bitamp 你所要知道的一切
Android Bitmap(位图)详解
图片是由大量且有限个数的像素点组成。把一张图片通过bitmap的方式创建到内存中,实际上就是在内存中创建了一个叫做Bitmap的对象,然后把 图片所有像素 解码后的数据存放在Bitmap对象里面,Bitmap就拥有了图片的宽高,透明度,颜色值等数据。所以Bitmap的创建是通过BitmapFactory.decodeXxx()。
Config是Bitmap类中的枚举类。像素由ARGB四个颜色通道组成。Config描述位图中像素的存储方式。 这里的存储方式,无非就是对颜色通道和用多大的容器(bit)来存储的排列组合。所以config会影响图片透明度,占用内存大小,保存成文件的大小,图片质量。
Config的字母表示该配置存储的像素的颜色通道,数字表示对应通道的数据用多少位来存储。
ALPHA_8:表示只存储alpha通道,使用8bit(1字节)的内存(容器)来存储一个像素。
RGB_565:表示存储RGB三个通道,分别使用5bit,6bit,5bit的内存(容器)来存储一个像素。
ARGB_4444:表示存储ARGB四个通道,每个通道都是以4bit的内存(容器)来存储一个像素。
ARGB_8888:表示存储ARGB四个通道,每个通道都是以8bit的内存(容器)来存储一个像素。
所以,ARGB_8888配置占用内存最大,图片质量最高。
图片压缩的一个思路就是降低图片的配置。
总内存 = 宽的像素数 × 高的像素数 × 每个像素点占用的大小
注:
1 byte = 8 bit
1 KB = 1024 byte
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层还没有提供将图片保存在匿名共享内存里面。
以上是关于Android Bitmap理解的主要内容,如果未能解决你的问题,请参考以下文章
Android自学日记关于Bitmap的理解和使用-不完整版
Android6.0 Bitmap存储以及Parcel传输源码分析