編輯:關於Android編程
如果想要對Android Bitmap進行更多的操作,理解好Bitmap的實現將會有非常大的幫助,另外Android在6.0中增加了asm存儲圖片。這篇文章就通過源碼來分析Android6.0中的Bitmap。本文主要分析Java層與native層的Bitmap,以及Bitmap的儲存和Parcel傳輸。源碼基於6.0,所以會有一些新的特性。
計算機裡面圖片都是作為數組來存儲的,而在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傳遞,另外也是為了方便圖片操作。
Bitmap的主要實現是在native層,Java層的Bitmap相當於是native層的接口。
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。
既然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(
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(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會針對不同的存儲類型,做不同的處理。
首先在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 CREATOR
= new Parcelable.Creator() {
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(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(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層還沒有提供將圖片保存在匿名共享內存裡面。
這篇博客我們介紹一下中介者模式(Mediator Pattern),也是行為型模式之一,中介者模式也稱為調解者模式或者調停者模式,顧名思義,它的作用是在若干類或者若干模塊
我們將使用微信公眾賬號方倍工作室作為講解的例子,二維碼見底部。本系列教程將引導你完成如下任務:創建新浪雲計算平台應用啟用微信公眾平台開發模式基礎接口消息及事件微信公眾平台
前言Android自定義控件經常會用到Canvas繪制2D圖形,在優化自己自定義控件技能之前,必須熟練掌握Canvas繪圖機制。本文從以下三個方面對Canvas繪圖機制進
我不知道大家有沒有這樣問題,項目做多了,就容易忽略最最基礎的知識,其實我也是在最近發現了自己也存在這樣的問題。因此打算做一些最基礎的知識的調研來重新學習和回顧這些容易被忽