Writing and Using Parcelables

Parcelable is a marker interface, reminiscent of Serializable, that shows up in many places in the Android SDK. Parcelable objects can be put into Intent extras or Bundle objects, for example. Making your own custom classes implement Parcelable greatly increases their flexibility.

At the same time, Parcelable is something that can be overused. In most Android apps, few if any custom classes really need to have Parcelable capabilities.

In this chapter, we will review how to modify classes to implement Parcelable and what the limitations are on using Parcelable.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book.

The Role of Parcelable

A Parcelable object is one that can be placed into a Parcel. A Parcel is the primary vehicle for passing data between processes in Android’s inter-process communication (IPC) framework.

IPC abounds in Android, even in places where you may not expect it. Every time you call startActivity(), for example, IPC occurs, even if the activity that calls startActivity() and the activity to be started are in the same process. A core OS process is the one that is responsible for identifying the activity to be started and routing control to it, so startActivity() performs IPC from the original activity’s process to a core OS process. The core OS process then eventually performs IPC to the target process for the activity to be started.

If you see an Intent or a Bundle in the Android SDK, odds are that those objects are involved in IPC. That is not always the case — LocalBroadcastManager, for example, uses Intent objects purely in-process — but it is a reasonable rule of thumb. Hence, there is keen interest in being able to implement Parcelable on specific classes, either to pass to other components via Intent extras, or to become part of the saved instance state Bundle.

Parcelable objects are also important for use with remote services via the binding pattern.

Writing a Parcelable

You have three major approaches for adding Parcelable capabilities to your classes in Android:

  1. Use an annotation processor that will add in the appropriate bits of magic for you
  2. Use a code generator site or tool that will take your existing class as input and give you the Parcelable-enabled rendition as output
  3. Just do it yourself

By Annotations

Enterprising developers have created annotation processing libraries that can be used to add Parcelable capabilities to a Java class in an Android app.

One approach is used by Parceler. Here, you just add a @Parcel annotation to the Java class, and it code generates what is needed. However, it does not actually make the Java class Parcelable. Rather, it creates a runtime wrapper class that is Parcelable and that knows how to convert instances of your own Java class to and from the wrapper. You wind up calling static wrap() and unwrap() methods on a Parcels class to handle the conversion between your class and the generated Parcelable class.

AutoParcel takes a slightly different approach. In this case, you need to:

AutoParcel then code-generates a Java class that implements the getters, data members, and Parcelable logic, along with other niceties like equals() and hashCode(). That Java class will be named AutoParcel_, followed by the name of the class with the @AutoParcel annotation (e.g., annotating a Foo class gives you an AutoParcel_Foo class). The AutoParcel-generated class is a concrete subclass of the abstract base class, and so you can just work with the abstract class’ public API and let AutoParcel handle the details.

However, neither of these give you classes that play well with the other children. Other code that expects to work with your classes — whether that is passing a Parceler-defined Parcelable to a third-party app or using something like Gson to handle JSON parsing — will not like either Parceler or AutoParcel that much.

By Code Generator Sites and Tools

The Parcelabler Web site is a code generator. You paste in a simple Java class, with the class declaration and data members:


class Book {
    String isbn;
    String title;
    int pubYear;
}

and it gives you an output class that adds the Parcelable logic:


class Book implements Parcelable {
    String isbn;
    String title;
    int pubYear;

    protected Book(Parcel in) {
        isbn = in.readString();
        title = in.readString();
        pubYear = in.readInt();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(isbn);
        dest.writeString(title);
        dest.writeInt(pubYear);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

We will see in the next section what all of that code does for us, as part of understanding how to build it by hand.

However, the Parcelabler Web site has some limitations in its Java parsing, and so the more complex your Java class, the more likely it is that the Parcelabler site will have difficulty understanding it and blending in the Parceable logic.

The ParcelableCodeGenerator project implements a command-line code generator that takes a JSON schema and gives you a Java class that, among other things, has the Parcelable implementation.

By Hand

Adding Parcelable support yourself is not especially difficult, though it is a bit tedious.

The Parcelable Interface

The first steps is to add implements Parcelable to the class. Immediately, your IDE should start complaining that you need to implement two methods to satisfy the Parcelable interface.

The easier of the two methods is describeContents(), where you will return 0, most likely.

The other method you will need to implement is writeToParcel(). You are passed in two parameters: a very important Parcel, and a usually-ignored int named flags.

Your job, in writeToParcel(), is to call a series of write...() methods on the Parcel to write out all data members of this object that should be considered part of the object as it is passed across process boundaries. There are dozens of type-safe methods for writing data into the Parcel:

If, in writeToParcel(), you called writeFileDescriptor(), you will want to have describeContents() return CONTENTS_FILE_DESCRIPTOR instead of 0, as apparently the Parcelable support logic needs to know that a file descriptor is in the Parcel.

In the case of the generated Book code shown earlier in this chapter, writeToParcel() writes out the two String and one int data member:


@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(isbn);
    dest.writeString(title);
    dest.writeInt(pubYear);
}

The CREATOR

When Android tries reading objects in from a Parcel, and it encounters an instance of your Parcelable class, it will retrieve a static CREATOR object that must be defined on that class. The CREATOR is an instance of Parcelable.Creator, using generics to tie it to the type of your class:


@SuppressWarnings("unused")
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
    @Override
    public Book createFromParcel(Parcel in) {
        return new Book(in);
    }

    @Override
    public Book[] newArray(int size) {
        return new Book[size];
    }
};

The @SuppressWarnings("unused") annotation is because the IDE will think that this CREATOR instance is not referred to anywhere. That is because it will only be used via Java reflection.

The CREATOR will need two methods. createFromParcel(), given a Parcel, needs to return an instance of your class populated from that Parcel. newArray(), given a size, needs to return a type-safe array of your class.

The typical implementation of createFromParcel() will delegate the actual work to a protected or private constructor on your class that takes the Parcel as input:


protected Book(Parcel in) {
    isbn = in.readString();
    title = in.readString();
    pubYear = in.readInt();
}

You need to read in the same values that you wrote out to the Parcel, and in the same order.

By Hand, With a Little Bit of Help

Android Studio 1.3 and higher have a template for a new Parcelable class. Right-click over your desired Java package and choose New > Other > New Parcelable Type from the context menu. Fill in your class and the template will create a new standalone Java class, akin to this one:


import android.os.Parcel;
import android.os.Parcelable;

public class Item implements Parcelable {

  // TODO declare your real class members 
  // Members must be either primitives, primitive arrays or parcelables
  private int mFoo;
  private String mBar;

  // TODO implement your constructors, getters & setters, methods

  private Item(Parcel in) {
    // TODO read your class members from the parcel
    // Note: order is important - you must read in the same order
    // you write in writeToParcel!
    mFoo=in.readInt();
    mBar=in.readString();
  }

  @Override
  public void writeToParcel(Parcel out, int flags) {
    // TODO write your class members to the parcel
    // Note: order is important - you must write in the same order
    // you read in your private parcelable constructor!
    out.writeInt(mFoo);
    out.writeString(mBar);
  }

  @Override
  public int describeContents() {
    // TODO return Parcelable.CONTENTS_FILE_DESCRIPTOR if your class members 
    // include a FileDescriptor, otherwise you can simply return 0
    return 0;
  }

  public static final Parcelable.Creator<Item> CREATOR=new Parcelable.Creator<Item>() {
    public Item createFromParcel(Parcel in) {
      return new Item(in);
    }

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

}

You would have to adjust the stock fields (mFoo, mBar) to be what you need, and adjust the writeToParcel() and private constructor to match.

However, this template is designed for starting from scratch; it is not that useful when you have an existing class and wish to now make it be Parcelable.

The ParcelablePlease library saves you from having to do all of the reading and writing to and from the Parcel yourself. Putting the @ParceablePlease annotation on the class generates a class for you (your class name followed by ParcelablePlease, so FooParcelablePlease for a Foo class). This class only marshals your data members to and from a Parcel, via static readFromParcel() and writeToParcel() methods. You still have to have the rest of the Parcelable boilerplate. Hence, this library is not as powerful as the annotation processors mentioned earlier in this chapter, but you wind up with a “real” complete Java class that can work better with other annotation-based libraries like Gson. On the other hand, it still makes it difficult for you to distribute your code to third parties, as they will need to also have this annotation processing library in their project builds.

The Limitations of Parcelable

While the mechanics of writing a Parcelable are not hard, this does not mean that every model object or other POJO in your app should be made Parcelable. Overuse of Parcelable is a bit of a code smell, as it suggests that the developer is not necessarily considering all of the limitations and effects of the use of Parcelable.

The 1MB Limit

The biggest one (pun lightly intended) is the size limitation. A Parcel – the IPC structure that is used to pass Parcelable objects across process boundaries — has a 1MB size limit. If you get over this limit, you will likely crash with a “Failed Binder Transaction” message as part of the exception’s stack trace.

There are two main ways you can reach this limit:

  1. Have a Parcelable that individually is too large. A common case for this is wrapping a Bitmap or other large byte array in some Parcelable object.
  2. Have too many Parcelable objects. For example, you might have performed a database query, converted the results into a collection of model objects, then tried to pass that collection to another activity via an Intent extra. Syntactically, this can work fine, if the collection and its model objects are all Parcelable. But now your risk of hitting the 1MB limit is determined by how many rows there are in the query’s result set, and that can vary by user.

Large data like this need to be managed by singletons or other static data members and shared among your application components, rather than passed via Parcelable objects.

Pass-By-Value

Suppose we have two activities, A and B. Activity A calls startActivity(), identifying activity B in the Intent. The Intent also includes a custom Parcelable object, one that takes up 1KB of space.

Question: how much system RAM is taken up by that Parcelable?

Wrong Answer: 1KB.

Right Answer: At least 3KB, as there are at least three copies of the Parcelable data:

Parcelable is, in effect “pass-by-value”, as the Parceable object is copied as part of getting it across the process boundary twice, once from your process to the core OS, and once from the core OS back to your process.

This means that modifications that Activity B makes to the Parcelable object will not be seen by Activity A, as they are working on separate copies of the object. Similarly, changes that Activity B makes to the Parcelable will not affect the copy held by the core OS process and re-delivered to Activity B on a configuration change.

The safest way to help defend against mistakes related to this is to consider a Parcelable object to be an immutable object. Only configure it through a constructor (possibly with the assistance of some Builder if you want a cleaner API). Offer getters for the values in the Parcelable, but do not offer any setters, so once the instance is created, it cannot be changed.

Also note that these copies magnify the effects of having a large Parcelable object, or too many Parcelable objects in a Parcel. A 900KB Parcel might fit within the 1MB size limit, but it would consume at least 2.7MB if the Parcel is part of some IPC.

Conversely, there are cases where Intent objects are not passed across process boundaries, such as LocalBroadcastManager. In those cases, neither the 1MB limit nor the pass-by-value effect are an issue. Only if the Intent is “flattened” into a Parcel, and later converted back into an Intent, do these extra copies and the 1MB limit come into play.

The ClassLoader Conundrum

Sometimes, weird stuff happens, particularly when trying to read in other Parcelable objects that you wrote to the Parcel. In this case, the Parcel system needs to use Java reflection to find the Java class associated with the Parcelable objects, and sometimes it gets a bit lost.

When you use readParcelable() to read in the Parcelable objects out of the Parcel, you may need to supply the ClassLoader that you know has those Parcelable classes:


Foo(Parcel in) {
  this.someField=in.readParcelable(getClass().getClassLoader());
  this.anotherField=in.readParcelable(getClass().getClassLoader());
}

Here, we are using the same ClassLoader that has this Foo class.

Sharing Between Apps

Parcelable objects need to read and write the same values to and from the Parcel. This sounds simple, but it gets into some nasty issues when multiple code bases need to work with the Parcelable.

For example, suppose your app offers an SDK, such as a remote service. You have some custom Parcelable objects that you can either give to third-party clients of your app or get as input from those clients. Now, your SDK needs to ship implementations of the Parcelable classes; without them, clients cannot use you exposed service API.

What happens now, if you change the definition of the Parcelable? Bear in mind that:

As a result, it is reasonably likely that your Parcelable implementations will be out of sync on a user’s device, with your app having one implementation and a third-party app having another implementation. The results of this may not be pretty.

This is not a problem for purely internal uses of Parcelable, such as for holding onto data across a configuration change.

Beware the PendingIntent

Custom Parcelable objects are fine for use as extras in Intent objects used with LocalBroadcastManager or otherwise limited to your own process. Custom Parcelable objects should work when placed in the saved instance state Bundle.

The further you get from these scenarios, though, the more likely it is that you will run into cases where your custom Parcelable will cause problems. That is because other apps — and core OS processes — have no access to your Parcelable class. Any attempt to work with Intent extras will result in a crash in that other process, probably interrupting whatever it was that you were trying to do.

One example of this is using a custom Parcelable in an extra for an Intent, wrapped in a PendingIntent. The party that executes the PendingIntent has the ability to add extras to the Intent inside the PendingIntent. That, in turn, causes problems, as Android does not have access to your Parcelable class. You get a stack trace like this one:


E/Parcel: Class not found when unmarshalling: com.commonsware.android.parcelable.marshall.Thingy
   java.lang.ClassNotFoundException: com.commonsware.android.parcelable.marshall.Thingy
     at java.lang.Class.classForName(Native Method)
     at java.lang.Class.forName(Class.java:400)
     at android.os.Parcel.readParcelableCreator(Parcel.java:2507)
     at android.os.Parcel.readParcelable(Parcel.java:2461)
     at android.os.Parcel.readValue(Parcel.java:2364)
     at android.os.Parcel.readArrayMapInternal(Parcel.java:2717)
     at android.os.BaseBundle.unparcel(BaseBundle.java:269)
     at android.os.Bundle.putAll(Bundle.java:226)
     at android.content.Intent.fillIn(Intent.java:8171)
     at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:255)
     at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:216)
     at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:7151)
     at android.app.PendingIntent.send(PendingIntent.java:836)
     at com.android.server.AlarmManagerService$DeliveryTracker.deliverLocked(AlarmManagerService.java:2984)
     at com.android.server.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:2424)
     at com.android.server.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:2543)

One workaround for cases like this is to still use a custom Parcelable, but instead of putting it directly as an Intent extra, use the Parcel system to convert it into a byte array, and store that as the extra. Foreign processes have no idea what the byte array is for and will not try to convert it into anything. When you get the byte array, you can then use the Parcel system to get your Parcelable back.

The Parcelable/Marshall sample project demonstrates this technique. It is a clone of the EventBus/GreenRobot3 sample app, discussed in the chapter on event buses. The app uses AlarmManager to get control every minute, posting an event on a greenrobot EventBus. That event adds a row in a ListView if the UI is in the foreground; otherwise, the WakefulIntentService triggered by the alarm event will show a Notification.

This sample app does not really need a custom Parcelable. However, lots of programs have lots of things that they do not really need. So, the Parcelable/Marshall project adds a custom Parcelable class, named Thingy:

package com.commonsware.android.parcelable.marshall;

import android.os.Parcel;
import android.os.Parcelable;

public class Thingy implements Parcelable {
  final String something;
  final int anotherThing;

  public Thingy(String something, int anotherThing) {
    this.something=something;
    this.anotherThing=anotherThing;
  }

  protected Thingy(Parcel in) {
    something=in.readString();
    anotherThing=in.readInt();
  }

  @Override
  public int describeContents() {
    return(0);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(something);
    dest.writeInt(anotherThing);
  }

  @SuppressWarnings("unused")
  public static final Parcelable.Creator<Thingy> CREATOR=
    new Parcelable.Creator<Thingy>() {
    @Override
    public Thingy createFromParcel(Parcel in) {
      return(new Thingy(in));
    }

    @Override
    public Thingy[] newArray(int size) {
      return(new Thingy[size]);
    }
  };
}
(from Parcelable/Marshall/app/src/main/java/com/commonsware/android/parcelable/marshall/Thingy.java)

It is fairly vanilla Parcelable class, wrapped around a string and an integer.

In theory, we could put a Thingy into the Intent used with AlarmManager via a PendingIntent. However, that will run into the problem outlined in this section, as Android does not have a Thingy. In fact, the stack trace shown above comes from this sample project, if you try putting a Thingy into the Intent.

The revised version of the project instead puts a byte array in the Intent as an extra, by way of the Parcelables utility class:

package com.commonsware.android.parcelable.marshall;

import android.os.Parcel;
import android.os.Parcelable;

// inspired by http://stackoverflow.com/a/18000094/115145

public class Parcelables {
  public static byte[] toByteArray(Parcelable parcelable) {
    Parcel parcel=Parcel.obtain();

    parcelable.writeToParcel(parcel, 0);

    byte[] result=parcel.marshall();

    parcel.recycle();

    return(result);
  }

  public static <T> T toParcelable(byte[] bytes,
                                   Parcelable.Creator<T> creator) {
    Parcel parcel=Parcel.obtain();

    parcel.unmarshall(bytes, 0, bytes.length);
    parcel.setDataPosition(0);

    T result=creator.createFromParcel(parcel);

    parcel.recycle();

    return(result);
  }
}
(from Parcelable/Marshall/app/src/main/java/com/commonsware/android/parcelable/marshall/Parcelables.java)

Parcelables mirrors standard Java utility classes like Arrays (static utility methods for Java arrays) and Collections (static utility methods for subclasses of Collection). Parcelables has two static utility methods for working with Parcelable objects:

Both work by way of a Parcel object. You can get one of these from an instance pool by calling the static obtain() method on Parcel.

toByteArray() gets a Parcel, uses writeToParcel() to put your Parcelable into the Parcel, then uses marshall() to get a byte array representation of the Parcel contents. Before returning that result, though, we recycle() the Parcel, returning it to the instance pool for later use.

toParcelable() needs not only the byte array representing your object, but also your Parcelable.Creator, which knows how to convert a Parcel back into your Parcelable. So, toParcelable():

The scheduleAlarms() method on PollReceiver is responsible for creating the Intent to schedule some alarm events. It adds a Thingy to the Intent, but uses toByteArray() to add the extra, rather than putting the raw Thingy in as the extra:

  static void scheduleAlarms(Context ctxt) {
    AlarmManager mgr=
        (AlarmManager)ctxt.getSystemService(Context.ALARM_SERVICE);
    Thingy thingy=
      new Thingy(mgr.getClass().getCanonicalName(), mgr.hashCode());
    Intent i=
      new Intent(ctxt, PollReceiver.class)
      .putExtra(EXTRA_THINGY, Parcelables.toByteArray(thingy));
    PendingIntent pi=PendingIntent.getBroadcast(ctxt, 0, i, 0);

    mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + INITIAL_DELAY,
                     PERIOD, pi);
  }
(from Parcelable/Marshall/app/src/main/java/com/commonsware/android/parcelable/marshall/PollReceiver.java)

When the alarm event occurs, PollReceiver can get the Thingy back by retrieving the byte array extra and using toParcelable():

  @Override
  public void onReceive(Context ctxt, Intent i) {
    Thingy thingy=
      Parcelables.toParcelable(i.getByteArrayExtra(EXTRA_THINGY),
        Thingy.CREATOR);

    if (i.getAction() == null) {
      ScheduledService.enqueueWork(ctxt);
    }
    else {
      scheduleAlarms(ctxt);
    }
  }
(from Parcelable/Marshall/app/src/main/java/com/commonsware/android/parcelable/marshall/PollReceiver.java)

While this approach will add a few lines of code to your project, it should not incur significant additional overhead. All the work that is being done here is part and, um, parcel of passing a Parcelable between processes anyway. We are just doing it proactively, to eliminate any references in the Parcel to our custom Parcelable class.