Parcelable

General Description

An object that implements the Parcelable interface can be “flattened” for inter-process communication or, as is the case most of the time when I use it, for placing in a Bundle (which is a Java associative array or dictionary).

The documentation makes this seem a lot harder than it is. The sample code from the documentation pretty much covers it.

Implementation

1. Implement the Parcelable interface

public class MyClass implements Parcelable
    ...
    @Override
    public int describeContents()
        {
        // From what I've read on the Web, this function should always 
        // return this value. The documentation is very, very vague. I 
        // think this value is actually used only when the parcel really 
        // is a file descriptor.

        // return CONTENTS_FILE_DESCRIPTOR;

        // The sample in the documentation for a general object like mine
        // just returns 0 here.

        return 0;
        }

    @Override
    public void writeToParcel(Parcel dest, int flags)
        {
        // Order is important. The order here should be the same as the
        // is expected in the constructor

	// It sometimes comes in handy to write arrays of values. The 
        // syntax looks like this:

        dest.writeIntArray(new int[] {publisherID, bookID, section});
        dest.writeStringArray(new String[] {verseReference, anchor, TOCPath});
        dest.writeBooleanArray(new boolean[] {wasHyperlink, noHistory});
        }

2. Implement the CREATOR field

Again, the documentation is weirdly vague, but here’s what it looks like. Yours can look identical to this with the exception of “MyClass”, which is the name of your class:

    public static final Parcelable.Creator CREATOR = 
        new Parcelable.Creator() 
        {
        public MyClass createFromParcel(Parcel in) 
            {
            // Calls our constructor that takes a Parcel

            return new MyClass(in); 
            }

        public MyClass[] newArray(int size) 
            {
            return new MyClass[size];
            }
        };

3. Implement a constructor that takes a Parcel

The parcel will have been created with your writeToParcel() method, so it should read values back from the parcel in the same order that you wrote them out:

    public MyClass(Parcel in)
        {
        // Since we used some arrays to demonstrate the writeToParcel()
        // method, read them back here in the same order.

        int [] intData = new int[3];
        in.readIntArray(intData);
        publisherID = intData[0];
        bookID = intData[1];
        section = intData[2];

        String [] stringData = new String[3];
        in.readStringArray(stringData);
        verseReference = stringData[0];
        anchor = stringData[1];
        TOCPath = stringData[2];

        boolean [] booleanData = new boolean[2];
        in.readBooleanArray(booleanData);
        wasHyperlink = booleanData[0];
        noHistory = booleanData[1];
        }

Putting a Parcel in a Bundle

I often use Parcelable objects as parameters to an Intent:

    // Create the parameter object

    MyClass param = new MyClass();

    // Load it up

    param.publisherID = 3;
    param.bookID = 4;
    param.verseReference = "John 3:16";
    // etc.

    // Create the intent

    Intent intent = new Intent("com.laridian.pocketbible.DOSOMETHING");

    // Put the flattened parameter in the Intent and broadcast it

    intent.putExtra(getString(R.string.someparam), (Parcelable) param);
    sendBroadcast(intent);

Retrieving a Parcel from a Bundle

Retrieving the flattened object is no different than retrieving any other object from the Bundle. It will be un-parceled by the cast:

    MyClass reconstructedObject =
        (MyClass) intent.getExtras().getParcelable(context.getString(R.string.someparam));

Don’t store Parcels!

Parcels are not meant to be persistent. They are meant to have short lives, since the format could change between releases of the API/SDK/JRE. Don’t store them anywhere.

Externalizable

Introduction

Java supports two techniques for object permanence: Serializable and Externalizable. These two are related but different. Serializable is more automatic and less flexible. Externalizable is less automatic but more flexible. Serializable is rumored to be slower in some implementations than Externalizable. Because Externalizable gives me a lot more flexibility, I choose to use it exclusively.

Making a class Externalizable is relatively easy. It amounts to implementing two methods: One for writing the state of your object and one for reading it. It also requires that you provide a no-parameter constructor so the system can automatically instantiate your object prior to asking you to read its fields from the file.

By default, any change you make to your class (such as adding a field) will cause any previously serialized data to be incompatible with the class. To resolve this, I take over responsibility for all versioning, as detailed below.

Details

1. Implement Externalizable

Your class needs to implement Externalizable, which requires two functions:

    @Override
    public void writeExternal(ObjectOutput output) throws IOException
        {
        // Write the version number to output using writeInt().
        // Write the fields from your object to output.
        }

    @Override
    public void readExternal(
        ObjectInput input) 
        throws IOException, ClassNotFoundException
        {
        // Read and test the version number.
        // Read the fields of this object in the same order that you wrote them.
        }

2. Add a public constructor with no parameters

You need to add a no-parameter constructor to your class if there isn’t one already. It must be declared “public”. When reading your serialized objects, your object will be constructed using this constructor, then readExternal() will be called.

3. Define serialVersionUID

Add the following field to your class. This identifies the class so the serialization code knows what kind of object to instantiate in its readObject() method:

    private static final long serialVersionUID = 91089626578087866L;

The value of this UID is irrelevant, but it should be unique among the other serialized classes in your document. I use random.org to generate a couple random numbers less than or equal to a million, then concatenate them to create a random long. If you omit this step, the compiler will generate a UID for you, but since it’s based on the class signature it will change if you change just about anything in the class. This will cause an exception to be thrown when you try to deserialize a previously created file. So it’s best to define your own.

4. Manage changes to your class fields

Since you’re defining your own serialVersionUID, you need to completely manage version changes in your serialized file. I create another constant for this purpose:

    private static final int serializationVersion = 1;

In writeExternal() this is the first value I write to the file. When reading, I read this value and test it to make sure I understand the version of the file being read, then make decisions based on the version of the file I’m reading. I may have to supply default values for new fields added to the class since the file was written.

If I don’t recognize the version, I throw a new IOException with a description of the problem:

    throw new IOException("Unrecognized archive version");

5. Notes on writeExternal()

In writeExternal(), use writeInt(), writeBoolean(), etc. to write field values. For enums, use writeObject(). For Strings, use writeUTF(). For your own classes, use writeObject(), then implement Externalizable in those classes.