什么是位图的Parcelable相关大小?

时间:2015-07-14 20:00:41

标签: android

在API级别19+设备上,我们有getByteCount()getAllocationByteCount(),每个设备返回Bitmap的字节大小。后者考虑到Bitmap实际上可以表示比其字​​节数更小的图像(例如,Bitmap最初拥有更大的图像,但随后与BitmapFactory.Options一起使用的事实inBitmap保留较小的图片。)

在大多数Android IPC场景中,特别是那些涉及Parcelable的场景,我们有1MB"活页夹交易限制"。

为了确定给定的Bitmap是否足够小于IPC,我们是使用getByteCount()还是getAllocationByteCount()

我的直觉说我们使用getByteCount(),因为它应该是Bitmap当前图像占用的字节数,但我希望有人有一个更权威的答案。

1 个答案:

答案 0 :(得分:5)

写入地块的图像数据的大小为getByteCount()加上Bitmap的颜色表的大小(如果有)。还有大约48个字节的Bitmap属性写入包裹。以下代码分析和测试为这些陈述提供了基础。

Bitmap写入Parcel的本机函数从this file的第620行开始。此功能包含在此处,并添加了解释:

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle,
                                     jboolean isMutable, jint density,
                                     jobject parcel) {
    const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
    if (parcel == NULL) {
        SkDebugf("------- writeToParcel null parcel\n");
        return JNI_FALSE;
    }

    android::Parcel* p = android::parcelForJavaObject(env, parcel);

以下七个int是写入包裹的第一个数据。在下面描述的测试#2中,从样本包中读取这些值以确认为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->lockColors(), count * sizeof(SkPMColor));
        ctable->unlockColors();
    } else {
        p->writeInt32(0);   // indicate no ctable
    }
}

现在我们来看问题的核心:为位图图像写入了多少数据?该金额由此bitmap->getSize()的来电确定。该功能分析如下。请注意,该值存储在size中,后面的代码用于为图像数据写入blob,并将数据复制到blob指向的内存中。

size_t size = bitmap->getSize();

使用blob将可变大小的数据块写入parcel。如果块小于40K,则写入&#34;就位#34;在包裹里。使用ashmem将大于40K的块写入共享内存,并将ashmem区域的属性写入宗地。 blob本身只是一个小的描述符,包含一些成员,包括指向块的指针,它的长度,以及指示块是就地还是共享内存的标志。 WriteableBlob的类定义位于this file的第262行。 writeBlob()的定义位于this file的第747行。 writeBlob()确定数据块是否足够小以便就地编写。如果是这样,它会扩展宗地缓冲区以腾出空间。如果不是,则创建并配置ashmem区域。在这两种情况下,都会定义blob(指针,大小,标志)的成员,以便在复制块时供以后使用。请注意,对于这两种情况,size定义将要复制的数据的大小,就地或共享内存。当writeBlob()完成时,目标数据缓冲区在blob中定义,写入包的值描述图像数据块的存储方式(就地或共享内存),对于共享内存,属性ashmem区域。

android::Parcel::WritableBlob blob;
android::status_t status = p->writeBlob(size, &blob);
if (status) {
    doThrowRE(env, "Could not write bitmap to parcel blob.");
    return JNI_FALSE;
}

现在设置块的目标缓冲区,可以使用blob中的指针复制数据。请注意,size定义了复制的数据量。另请注意,只有一个size。相同的值用于就地和共享内存目标。

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;
}

完成了对Bitmap_writeToParcel的分析。现在很清楚,虽然小的(&lt; 40K)图像被就地写入并且较大的图像被写入共享存储器,但是对于两种情况,写入的数据的大小是相同的。查看该大小的最简单,最直接的方法是使用图像&lt; 40K创建一个测试用例,以便它就地编写。然后,生成的宗地的大小显示图像数据的大小。

确定size是什么的第二种方法需要了解SkBitmap::getSize()。这是上面分析的代码中使用的函数,用于获取图像块的大小。

SkBitmap::getSize()this file的第130行定义。它是:

size_t getSize() const { return fHeight * fRowBytes; }

与此解释相关的同一文件中的另外两个函数是height(),在第98行定义:

int height() const { return fHeight; }

rowBytes(),在第101行定义:

int rowBytes() const { return fRowBytes; }

当位图的属性写入宗地时,我们在Bitmap_writeToParcel中看到了这些函数:

p->writeInt32(bitmap->height());
p->writeInt32(bitmap->rowBytes());

通过对这些功能的理解,我们现在看到通过在包中转储前几个整数,我们可以看到fHeightfRowBytes的值,我们可以从中推断返回的值getSize()

下面的第二个代码段执行此操作,并进一步确认写入Parcel的数据大小与getByteCount()返回的值相对应。

测试#1

此测试创建小于40K的位图,以生成图像数据的就地存储。然后检查宗地数据大小以显示getByteCount()确定存储在宗地中的图像数据的大小。

以下代码中的前几个语句是产生小于40K的Bitmap

logcat输出确认写入Parcel的数据大小与getByteCount()返回的值相对应。

byteCount=38400 allocatedByteCount=38400 parcelDataSize=38428
byteCount=7680 allocatedByteCount=38400 parcelDataSize=7708

产生输出的代码:

    // Setup to get a mutable bitmap less than 40 Kbytes
    String path = "someSmallImage.jpg";
    Bitmap bm0 = BitmapFactory.decodeFile(path);
    // Need it mutable to change height
    Bitmap bm1 = bm0.copy(bm0.getConfig(), true);
    // Chop it to get a size less than 40K
    bm1.setHeight(bm1.getHeight() / 32);

    // Now we have a BitMap with size < 40K for the test
    Bitmap bm2 = bm1.copy(bm0.getConfig(), true);

    // What's the parcel size?
    Parcel p1 = Parcel.obtain();
    bm2.writeToParcel(p1, 0);

    // Expect byteCount and allocatedByteCount to be the same
    Log.i("Demo", String.format("byteCount=%d allocatedByteCount=%d parcelDataSize=%d",
            bm2.getByteCount(), bm2.getAllocationByteCount(), p1.dataSize()));

    // Resize to make byteCount and allocatedByteCount different
    bm2.setHeight(bm2.getHeight() / 4);

    // What's the parcel size?
    Parcel p2 = Parcel.obtain();
    bm2.writeToParcel(p2, 0);

    // Show that byteCount determines size of data written to parcel
    Log.i("Demo", String.format("byteCount=%d allocatedByteCount=%d parcelDataSize=%d",
            bm2.getByteCount(), bm2.getAllocationByteCount(), p2.dataSize()));

    p1.recycle();
    p2.recycle();

测试#2

此测试将位图存储到一个宗地,然后转储前几个整数以获取可以推断出图像数据大小的值。

添加了评论的logcat输出:

//位图属性

bc=12000000 abc=12000000 hgt=1500 wid=2000 rbyt=8000 dens=213

//高度变化后的属性。 byteCount已更改,allocateByteCount未更改。

bc=744000 abc=12000000 hgt=93 wid=2000 rbyt=8000 dens=213

//转储宗地数据。宗地数据大小为48.图像太大而无法就地。

pds=48 mut=1 ctyp=4 atyp=1 hgt=93 wid=2000 rbyt=8000 dens=213

//显示从宗地数据派生的getSize()值。它等于getByteCount()。

bitmap->getSize()= 744000 getByteCount()=744000

产生此输出的代码:

String path = "someImage.jpg";
Bitmap bm0 = BitmapFactory.decodeFile(path);
// Need it mutable to change height
Bitmap bm = bm0.copy(bm0.getConfig(), true);

// For reference, and to provide confidence that the parcel data dump is
// correct, log the bitmap attributes.
Log.i("Demo", String.format("bc=%d abc=%d hgt=%d wid=%d rbyt=%d dens=%d",
        bm.getByteCount(), bm.getAllocationByteCount(),
        bm.getHeight(), bm.getWidth(), bm.getRowBytes(), bm.getDensity()));
// Change size
bm.setHeight(bm.getHeight() / 16);
Log.i("Demo", String.format("bc=%d abc=%d hgt=%d wid=%d rbyt=%d dens=%d",
        bm.getByteCount(), bm.getAllocationByteCount(),
        bm.getHeight(), bm.getWidth(), bm.getRowBytes(), bm.getDensity()));

// Get a parcel and write the bitmap to it.
Parcel p = Parcel.obtain();
bm.writeToParcel(p, 0);

// When the image is too large to be written in-place,
// the parcel data size will be ~48 bytes (when there is no color map).
int parcelSize = p.dataSize();

// What are the first few ints in the parcel?
p.setDataPosition(0);
int mutable   = p.readInt(); //1
int colorType = p.readInt(); //2
int alphaType = p.readInt(); //3
int width     = p.readInt(); //4
int height    = p.readInt(); //5 bitmap->height()
int rowBytes  = p.readInt(); //6 bitmap->rowBytes()
int density   = p.readInt(); //7

Log.i("Demo", String.format("pds=%d mut=%d ctyp=%d atyp=%d hgt=%d wid=%d rbyt=%d dens=%d",
        parcelSize,  mutable, colorType, alphaType, height, width, rowBytes, density));

// From code analysis, we know that the value returned
// by SkBitmap::getSize() is the size of the image data written.
// We also know that the value of getSize() is height()*rowBytes().
// These are the values in ints 5 and 6.
int imageSize = height * rowBytes;

// Show that the size of image data stored is Bitmap.getByteCount()
Log.i("Demo", String.format("bitmap->getSize()= %d getByteCount()=%d", imageSize, bm.getByteCount()));

p.recycle();