在API级别19+设备上,我们有getByteCount()
和getAllocationByteCount()
,每个设备返回Bitmap
的字节大小。后者考虑到Bitmap
实际上可以表示比其字节数更小的图像(例如,Bitmap
最初拥有更大的图像,但随后与BitmapFactory.Options
一起使用的事实inBitmap
保留较小的图片。)
在大多数Android IPC场景中,特别是那些涉及Parcelable
的场景,我们有1MB"活页夹交易限制"。
为了确定给定的Bitmap
是否足够小于IPC,我们是使用getByteCount()
还是getAllocationByteCount()
?
我的直觉说我们使用getByteCount()
,因为它应该是Bitmap
中当前图像占用的字节数,但我希望有人有一个更权威的答案。
答案 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());
通过对这些功能的理解,我们现在看到通过在包中转储前几个整数,我们可以看到fHeight
和fRowBytes
的值,我们可以从中推断返回的值getSize()
。
下面的第二个代码段执行此操作,并进一步确认写入Parcel
的数据大小与getByteCount()
返回的值相对应。
此测试创建小于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();
此测试将位图存储到一个宗地,然后转储前几个整数以获取可以推断出图像数据大小的值。
添加了评论的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();