How to Marshall and Unmarshall a Parcelable to a Byte Array with Help of Parcel

How to marshall and unmarshall a Parcelable to a byte array with help of Parcel?

First create a helper class ParcelableUtil.java:

public class ParcelableUtil {    
public static byte[] marshall(Parcelable parceable) {
Parcel parcel = Parcel.obtain();
parceable.writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.recycle();
return bytes;
}

public static Parcel unmarshall(byte[] bytes) {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0); // This is extremely important!
return parcel;
}

public static <T> T unmarshall(byte[] bytes, Parcelable.Creator<T> creator) {
Parcel parcel = unmarshall(bytes);
T result = creator.createFromParcel(parcel);
parcel.recycle();
return result;
}
}

With the help of the util class above, you can marshall/unmarshall instances of your class MyClass implements Parcelable like so:

Unmarshalling (with CREATOR)

byte[] bytes = …
MyClass myclass = ParcelableUtil.unmarshall(bytes, MyClass.CREATOR);

Unmarshalling (without CREATOR)

byte[] bytes = …
Parcel parcel = ParcelableUtil.unmarshall(bytes);
MyClass myclass = new MyClass(parcel); // Or MyClass.CREATOR.createFromParcel(parcel).

Marshalling

MyClass myclass = …
byte[] bytes = ParcelableUtil.marshall(myclass);

Unmarshall Parcelable created by @Parcelize

For now, the best generic solution that I've found is achieved using reflection:

internal inline fun <reified T : Parcelable> ByteArray.deserializeParcelable(): T {
val parcel = Parcel
.obtain()
.apply {
unmarshall(this@deserializeParcelable, 0, size)
setDataPosition(0)
}

return parcelableCreator<T>()
.createFromParcel(parcel)
.also {
parcel.recycle()
}
}

internal inline fun <reified T : Parcelable> parcelableCreator(): Parcelable.Creator<T> {
val creator = T::class.java.getField("CREATOR").get(null)
@Suppress("UNCHECKED_CAST")
return creator as Parcelable.Creator<T>
}

We have to use reflection because the CREATOR generated by @Parcelize is not accessible in such methods.

Unmarshalling unknown type code, List Byte & List String

In the super class, this looks wrong:

public  Message(Parcel in)
{
...
if (this.imageData!=null) {
in.readByteArray(this.imageData);
}
}

@Override
public void writeToParcel(Parcel dest, int flags) {
...
dest.writeByteArray(imageData);
}

I am relatively certain that this.imageData will always be null at that point, which means that you will not have a matching "read" to go with the "write" in writeToParcel(). Instead, you should use createByteArray():

public  Message(Parcel in)
{
...
this.imageData = in.createByteArray();
}

Then we have to consider the subclass:

public  Emergency(Parcel in)
{
...
in.readList(extrasFormat,Formats.ExtrasFormat.class.getClassLoader());
}

@Override
public void writeToParcel(Parcel dest, int flags) {
...
dest.writeList(extrasFormat);
}

Does Formats.ExtrasFormat implement Parcelable or Serializable? If it doesn't, I don't believe this will work. If it does, then probably fixing the superclass was all that was needed.

How to use Parcel in Android?

Ah, I finally found the problem. There were two in fact.

  1. CREATOR must be public, not protected. But more importantly,
  2. You must call setDataPosition(0) after unmarshalling your data.

Here is the revised, working code:

public void testFoo() {
final Foo orig = new Foo("blah blah");
final Parcel p1 = Parcel.obtain();
final Parcel p2 = Parcel.obtain();
final byte[] bytes;
final Foo result;

try {
p1.writeValue(orig);
bytes = p1.marshall();

// Check to make sure that the byte stream seems to contain a Parcelable
assertEquals(4, bytes[0]); // Parcel.VAL_PARCELABLE

p2.unmarshall(bytes, 0, bytes.length);
p2.setDataPosition(0);
result = (Foo) p2.readValue(Foo.class.getClassLoader());

} finally {
p1.recycle();
p2.recycle();
}


assertNotNull(result);
assertEquals( orig.str, result.str );

}

protected static class Foo implements Parcelable {
public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() {
public Foo createFromParcel(Parcel source) {
final Foo f = new Foo();
f.str = (String) source.readValue(Foo.class.getClassLoader());
return f;
}

public Foo[] newArray(int size) {
throw new UnsupportedOperationException();
}

};


public String str;

public Foo() {
}

public Foo( String s ) {
str = s;
}

public int describeContents() {
return 0;
}

public void writeToParcel(Parcel dest, int ignored) {
dest.writeValue(str);
}


}

Using both Parcelable class and Serializable in one class

There are no issues as you don't introduce any new properties for the Parcelable implementation. Standard implementation merely provides you some helper methods describeContents and writeToParcel and a static CREATOR object, which will not be part of the Serialized content.

example:

 class MyParcelable private constructor(`in`: Parcel) : Parcelable {
private val mData: Int = `in`.readInt()

override fun describeContents(): Int {
return 0
}

override fun writeToParcel(out: Parcel, flags: Int) {
out.writeInt(mData)
}

companion object {
val CREATOR: Parcelable.Creator<MyParcelable?>
= object : Parcelable.Creator<MyParcelable?> {
override fun createFromParcel(`in`: Parcel): MyParcelable? {
return MyParcelable(`in`)
}

override fun newArray(size: Int): Array<MyParcelable?> {
return arrayOfNulls(size)
}
}
}
}

Parcel through socket

A good example can be found here.

In general, you will need to make sure that you have the classes available to reconstruct (create from parcel) the objects.



Related Topics



Leave a reply



Submit