Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android6.0 Bitmap存儲以及Parcel傳輸

Android6.0 Bitmap存儲以及Parcel傳輸

編輯:關於Android編程

如果想要對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(
                    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會針對不同的存儲類型,做不同的處理。

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 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層還沒有提供將圖片保存在匿名共享內存裡面。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved