I am an android software developer. At work I am developing a 3D GUI framework called Tiffany. OpenGL is used at the core of Tiffany. Like any other OpenGL application, our product uses float type values to define the location(position) of the vertices that consist 3D objects. Therefore, FloatBuffers are used frequently.

Tiffany has been working great until now with all Android versions. However, I recently got a report that Tiffany behaves a bit weird on Honeycomb, that is Android 3.0 which is an Android version for tablet devices. I was able to track down the cause of the problem and found out what was going on with the help of Mr. Shin, whom we think of as a genius. The unexpected behavior was originating from ByteBuffer/FloatBuffer.

In a portion of our code there was something going on like the following.
ByteBuffer byteBuffer =
ByteBuffer.allocateDirect(n*4).order(ByteOrder.nativeOrder())
FloatBuffer buffer = byteBuffer.asFloatBuffer();

//...

//We put some float values in the buffer

//...

FloatBuffer copiedBuffer = buffer.asReadOnlyBuffer();

//...

//use the values in copiedBuffer

//... 


As I was debugging the code line by line, I found out that the values retrieved from copiedBuffer were interpreted incorrectly in Honeycomb. This was a very unexpected behavior as this was working perfectly on previous Android versions.

Here is what was happening. Buffers in Android has a property called Order. This property indicates whether the buffer uses big endian or little endian. In other words it defines how the bytes in the buffer will be interpreted. It turns out that this property is altered in the copied version of the buffer when using asReadOnlyBuffer(). And what is more interesting is that this problematic phenomenon is only spotted when the ByteOrder of the ByteBuffer is specified using the method order(ByteOrder byteOrder) from ByteBuffer.

Here is a simple example which illustrates this problem.

ByteBuffer byteBuffer0 =

ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());

FloatBuffer buffer0 = byteBuffer0.asFloatBuffer();

buffer0.put(0.1f);

FloatBuffer copiedBuffer0 = buffer0.asReadOnlyBuffer();

Log.d(TAG, "buffer0 endian: " + buffer0.order());

Log.d(TAG, "buffer0[0]: "  + buffer0.get(0));

Log.d(TAG, "copiedBuffer0 endian: " + copiedBuffer0.order());

Log.d(TAG, "copiedBuffer0[0]: " + copiedBuffer0.get(0));


ByteBuffer byteBuffer1 =

ByteBuffer.allocate(4).order(ByteOrder.nativeOrder());

FloatBuffer buffer1 = byteBuffer1.asFloatBuffer();

buffer1.put(0.1f);

FloatBuffer copiedBuffer1 = buffer1.asReadOnlyBuffer();

Log.d(TAG, "buffer1 endian: " + buffer1.order());

Log.d(TAG, "buffer1[0]: "  + buffer1.get(0));

Log.d(TAG, "copiedBuffer1 endian: " + copiedBuffer1.order());

Log.d(TAG, "copiedBuffer1[0]: " + copiedBuffer1.get(0));


FloatBuffer buffer2 = ByteBuffer.allocateDirect(4).asFloatBuffer();

buffer2.put(0.1f);

FloatBuffer copiedBuffer2 = buffer2.asReadOnlyBuffer();

Log.d(TAG, "buffer2 endian: " + buffer2.order());

Log.d(TAG, "buffer2[0]: "  + buffer2.get(0));

Log.d(TAG, "copiedBuffer2 endian: " + copiedBuffer2.order());

Log.d(TAG, "copiedBuffer2[0]: " + copiedBuffer2.get(0));


FloatBuffer buffer3 = ByteBuffer.allocate(4).asFloatBuffer();

buffer3.put(0.1f);

FloatBuffer copiedBuffer3 = buffer3.asReadOnlyBuffer();

Log.d(TAG, "buffer3 endian: " + buffer3.order());

Log.d(TAG, "buffer3[0]: "  + buffer3.get(0));

Log.d(TAG, "copiedBuffer3 endian: " + copiedBuffer3.order());

Log.d(TAG, "copiedBuffer3[0]: " + copiedBuffer3.get(0));


The result in Honeycomb(Android 3.0) AVD would look like the following.

buffer0 endian: LITTLE_ENDIAN

buffer0[0]: 0.1

copiedBuffer0 endian: BIG_ENDIAN

copiedBuffer0[0]: -4.2949213E8


buffer1 endian: LITTLE_ENDIAN

buffer1[0]: 0.1

copiedBuffer1 endian: BIG_ENDIAN

copiedBuffer1[0]: -4.2949213E8


buffer2 endian: BIG_ENDIAN

buffer2[0]: 0.1

copiedBuffer2 endian: BIG_ENDIAN

copiedBuffer2[0]: 0.1


buffer3 endian: BIG_ENDIAN

buffer3[0]: 0.1

copiedBuffer3 endian: BIG_ENDIAN

copiedBuffer3[0]: 0.1


The result in Android 2.X AVD would look like the following.

buffer0 endian: LITTLE_ENDIAN

buffer0[0]: 0.1

copiedBuffer0 endian: LITTLE_ENDIAN

copiedBuffer0[0]: 0.1


buffer1 endian: LITTLE_ENDIAN

buffer1[0]: 0.1

copiedBuffer1 endian: LITTLE_ENDIAN

copiedBuffer1[0]: 0.1


buffer2 endian: BIG_ENDIAN

buffer2[0]: 0.1

copiedBuffer2 endian: BIG_ENDIAN

copiedBuffer2[0]: 0.1


buffer3 endian: BIG_ENDIAN

buffer3[0]: 0.1

copiedBuffer3 endian: BIG_ENDIAN

copiedBuffer3[0]: 0.1

 
So, here is my conclusion. Dalvik uses big endian and Linux which is the operating system I am using at work uses little endian. As a result, ByteBuffers are created to use big endian by default. However, when the ByteOrder is specified to be little endian, the Order property isn't properly copied to the new Buffer in Honeycomb. I suspect that this is a bug in Honeycomb because Honeycomb is the only Android version working differently and also it doesn't logically make sense to use a different endian system to interpret a copied buffer from the original buffer. Moreover, the Order property not being properly copied seems much like a mistake since you cannot set the Order property for FloatBuffers.

I must admit that specifying the ByteOrder of the buffer is an unnecessary step, still Honeycomb's behavior of handling Buffers doesn't make much sense.
Posted by Dansoonie