¿Cuál es el tamaño Parcelable-Relevant de un mapa de bits?

En los dispositivos de nivel API 19+, tenemos getByteCount() y getAllocationByteCount() , cada uno de los cuales devuelve un tamaño del Bitmap de Bitmap en bytes. Este último tiene en cuenta el hecho de que un Bitmap puede representar una imagen más pequeña que su cuenta de bytes (por ejemplo, el Bitmap tenía originalmente una imagen más grande, pero luego se utilizó con BitmapFactory.Options y inBitmap para mantener una imagen más pequeña).

En la mayoría de los escenarios IPC de Android, en particular los relacionados con Parcelable , tenemos un límite de transacción de carpeta de 1 MB.

Para los propósitos de determinar si un Bitmap dado es bastante pequeño para IPC, utilizamos getByteCount() o getAllocationByteCount() ?

Mi instinto instintivo dice que usamos getByteCount() , ya que debería ser el número de bytes que la imagen actual en el Bitmap está tomando, pero esperaba que alguien tuviera una respuesta más autorizada.

El tamaño de los datos de imagen escritos en el paquete es getByteCount() más el tamaño de la tabla de colores del Bitmap , si tiene uno. También hay aproximadamente 48 bytes de atributos Bitmap escritos en la parcela. Los siguientes análisis de código y pruebas proporcionan la base para estas declaraciones.

La función nativa para escribir un Bitmap en una Parcel comienza en la línea 620 de este archivo . La función se incluye aquí con la explicación añadida:

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

Los siguientes siete int s son los primeros datos escritos en la parcela. En la Prueba # 2, descrita a continuación, estos valores se leen desde un paquete de muestra para confirmar el tamaño de los datos escritos para el 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); 

Si el mapa de bits tiene un mapa de color, se escribe en el paquete. Una determinación precisa del tamaño del paquete de un mapa de bits debe abordar esto también.

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

Ahora llegamos al núcleo de la pregunta: ¿Cuántos datos se escriben para la imagen de mapa de bits? La cantidad es determinada por esta llamada a bitmap->getSize() . Esta función se analiza a continuación. Tenga en cuenta que el valor se almacena en size , que se utiliza en el siguiente código para escribir el blob para los datos de imagen y copiar los datos en la memoria apuntada por el blob.

 size_t size = bitmap->getSize(); 

Un bloque de tamaño variable de datos se escribe en un paquete utilizando un blob. Si el bloque es menos de 40K, se escribe "en su lugar" en el paquete. Los bloques mayores de 40K se escriben en memoria compartida usando ashmem y los atributos de la región ashmem se escriben en la parcela. El propio blob es sólo un pequeño descriptor que contiene algunos miembros que incluyen un puntero al bloque, su longitud y un indicador que indica si el bloque está en el lugar o en la memoria compartida. La definición de clase para WriteableBlob está en la línea 262 de este archivo . La definición de writeBlob( ) está en la línea 747 de este archivo . writeBlob() determina si el bloque de datos es lo suficientemente pequeño como para ser escrito en su lugar. Si es así, se expande el buffer de paquetería para hacer espacio. Si no es así, se crea y configura una región ashmem . En ambos casos, los miembros del blob (puntero, tamaño, indicador) se definen para uso posterior cuando se copia el bloque. Tenga en cuenta que en ambos casos, size define el tamaño de los datos que se copiarán, ya sea en el lugar o en la memoria compartida. Cuando writeBlob() finaliza, el buffer de datos de destino se define en blob y los valores escritos en la parcela describen cómo se almacena el bloque de datos de imagen (memoria in-place o shared) y, para memoria compartida, los atributos de la región 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; } 

Con el buffer de destino para el bloque ahora configurado, los datos se pueden copiar usando el puntero en el blob. Tenga en cuenta que el size define la cantidad de datos copiados. También tenga en cuenta que sólo hay un size . El mismo valor se utiliza para objetivos de memoria compartida y en contexto.

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

Esto completa el análisis de Bitmap_writeToParcel . Ahora está claro que mientras las imágenes pequeñas (<40K) se escriben en el lugar y las imágenes mayores se escriben en memoria compartida, el tamaño de los datos escritos es el mismo para ambos casos. La manera más fácil y directa de ver qué es ese tamaño es crear un caso de prueba usando una imagen <40K, para que se escriba en el lugar. El tamaño de la parcela resultante revela el tamaño de los datos de la imagen.

Un segundo método para determinar qué size es requiere una comprensión de SkBitmap::getSize() . Esta es la función utilizada en el código analizado anteriormente para obtener el tamaño del bloque de imagen.

SkBitmap::getSize() se define en la línea 130 de este archivo . Es:

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

Otras dos funciones en el mismo archivo relevantes para esta explicación son height() , definida en la línea 98:

 int height() const { return fHeight; } 

Y rowBytes() , definidos en la línea 101:

 int rowBytes() const { return fRowBytes; } 

Vimos estas funciones utilizadas en Bitmap_writeToParcel cuando los atributos de un mapa de bits se escriben en un paquete:

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

Con esta comprensión de estas funciones, vemos ahora que al vaciar los primeros ints en un paquete, podemos ver los valores de fHeight y fRowBytes , de los cuales podemos inferir el valor devuelto por getSize() .

El segundo fragmento de código a continuación hace esto y proporciona una confirmación adicional de que el tamaño de los datos escritos en la Parcel corresponde al valor devuelto por getByteCount() .

Prueba # 1

Esta prueba crea un mapa de bits inferior a 40K para producir almacenamiento en el lugar de los datos de imagen. A continuación, se examina el tamaño de datos de parcela para mostrar que getByteCount() determina el tamaño de los datos de imagen almacenados en la parcela.

Las primeras declaraciones en el siguiente código son producir un Bitmap menor que 40K.

La salida logcat confirma que el tamaño de los datos escritos en la Parcel corresponde al valor devuelto por getByteCount() .

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

Código que produjo la salida mostrada:

  // 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(); 

Prueba # 2

Esta prueba almacena un mapa de bits en un paquete, luego descarga los primeros ints para obtener los valores a partir de los cuales se puede deducir el tamaño de los datos de la imagen.

La salida logcat con comentarios añadidos:

// Atributos de mapa de bits

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

// Atributos después del cambio de altura. ByteCount cambiado, distributedByteCount no.

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

// Descarga de datos de parcela. El tamaño de los datos de la parcela es 48. La imagen es demasiado grande para estar en el lugar.

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

// Mostrar el valor de getSize () derivado de los datos del paquete. Es igual a getByteCount ().

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

Código que produjo este resultado:

 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(); 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.