Yet another post on Serializable vs Parcelable

Author is a professional software developer providing android development services. More info here.

All my android developing life I’ve used java serialization as a data transfer mechanism between activities. I was vaguely aware of Parcelable and it proclaimed benefits but never really saw any performance issues related to this and thus never had a need to start writing all the parcelable boilerplate in my classes instead of just marking them as Serializable. When I recently started working on an ongoing project I’ve quickly jumped ship since: a) it’s supposed to be an order of magnitude faster and b) there is an awesome code generation library, Parceler, relieving developers of writing the boilerplate while keeping all the performance goodness.

I’ve just finished implementing my first big feature in the app and all was well until our amazing QA guy came to me with an issue – app was crashing on transition from one activity to another – throwing a TransactionTooLargeException (or just logging E: !!! FAILED BINDER TRANSACTION !!! on pre 15 API levels). And he wasn’t even trying to do anything crazy. Quick google search led me to a world of remote procedure calls and binder transaction buffers, a world I was blissfully unaware previously. So there is this binder transaction buffer, and it appears to be limited to 1MB, and it is used by all ongoing transactions simultaneously. I am moving a fairly large object graph through an activity starting intent, and maybe I’m not supposed to be doing that, but surely I can’t be hitting a 1MB limit? Or can I?

Let’s get to the bottom of this

First thing I tried was reverting to java serialization and sure enough everything worked. Then I tried to hit the limit of this approach and failed, even when creating a 100x larger graph than the one causing the original crash and many times larger than worst case scenario I could conceive. I’ve already discovered, before this problem surfaced, that Parcelable/Parceler combination will not preserve references to the same object in a graph on demarshalling but create copies of the object as many times as it is referenced in the original graph. Although this behavior might not be very surprising, or not surprising at all, it is different from how Serializable magic works.

To test this in more controlled environment I created a little experiment app. You can find an apk here and the source here. In it you’ll find an EmptyObject class, and it’s just that – a class with no instance variables of any kind, actually with no code whatsoever. I wanted to see how many of these useless bastards I could cram into an ArrayList and transfer from one activity to another using java serialization or Parcelable/Parceler. I’ve also created a class called Package holding a reference to the ArrayList<EmptyObject> and nothing else. An instance of Package class is the only thing I’ll put in an Intent and start another activity with it.

The results are in

Parcelable/Parceler mechanism can transfer a package of 4 045 EmptyObject instances. When the Package instance is marshalled the resulting byte[] is 517 764 bytes long. Adding one more EO instance to the mix crashes the app with TransactionTooLargeException, so I guess 517 892 bytes is a bit too much. At the same time a package of 4 045 EO instances is only 24 482 bytes long when it’s serialized to byte[] so it can be transferred easily. The limit when using java serialization is at 86 276 EO instances in a package, creating a serialized byte[] of 517 928 bytes. My assumption was that if I create a list with all the references pointing to just one EO instance this number will be much larger but actually it’s not. A package containing a list of 86 276 EO references to a single instance when serialized to byte[] has 431 593 bytes so we can cram another 20k or so in there :) It’s clear that regardless of the used technology the limit is the amount of bytes you can transfer. On my Moto X (2nd gen) running Android 5.0 that seems to be a number a little lower than 512KB. The results are device specific: On other devices I’ve seen behavior ranging from slightly better on Galaxy S5 and S4, they can transfer couple of KB more, to almost a full MB on HTC One M8. It doesn’t look to be connected to the runtime environment the phone is using (dalvik/ART) .The one thing that is clear is that java serialization creates much smaller byte[] than using a Parcelable/Parceler combination on the same object graph.

Shit gets weird

Maybe you’re wondering why I needed a Package class, why didn’t I send ArrayList<EmptyObject> directly. In order to simplify I’ve decided to do just that. And everything blew up. Suddenly I couldn’t send 86k of objects with java serialization, I couldn’t even send a measly 4k I could with Parcelable/Parceler. And what’s even weirder if I sent a list with 10 references to the same object I’d get 10 different objects on the other side. I had no idea what was going on.

Since ArrayList implements Serializable interface, and it’s full of also serializable EmptyObject instances I thought I was calling Intent.putExtra(String name, Serializable value) flavor of putExtra methods. And I was, but it wasn’t doing what I expected. If you look at the code of this method you’ll see that it’s actually calling putSerializable(String key, Serializable value) method of a Bundle class (BaseBundle from API level 21) and all that method does is dumps the Serializable object in a map under the given String name key. This map will actually get marshalled by a writeArrayMapInternal(ArrayMap<String, Object> val) method from a Parcel class. That’s where things get interesting. This method will iterate on a said map and call writeValue(Object v) method on every object in it, a method that is only a glorified switch statement and it will do a bunch of instanceof checks and call writeThis and writeThat methods. Our list will not in fact be written with a writeSerializable(Serializable s) method but with a writeList(List val) method iterating and doing separate writeSerializable on list elements. And there you have it: we gave a serializable object to Intent’s putExtra method that also happens to be a list and expected it to serialize the whole thing in one go but instead it will serialize every instance in the list by itself.

Ok, but Parcelable is still 10x faster right?

I actually don’t know since I’m not going to start implementing pure Parcelable classes. However the experiment app measures the time from creating an Intent in Activity A to unpacking it in Activity B, albeit perhaps naively. I didn’t do the test 1000 times to get the average but it looks to me that 10x or 17x improvement over java serialization is not going to happen in this scenario. Yes, it looks like Parcelable/Parceler combination is faster but more like up to 2x faster. But hey, I’m sending a bunch of empty objects here. Remember the object graph from the real app, the one that started all this? It’s actually something like 3x faster to use java serialization on it, and I’ve confirmed this on a plethora of devices. I don’t know why and at this point I’m too afraid to ask.

Conclusion

I’m not going to tell you to go back to java serialization. Maybe I’m doing something wrong here or maybe it has something to do with the fact that android devices are now very different beasts than the ones from just couple of years ago. My only advice is do not trust blindly everything you read on the internet and make some trials on your own real life scenario. That should enable you to draw some meaningful conclusions.

Be Sociable, Share!

5 thoughts on “Yet another post on Serializable vs Parcelable”

  1. Even better. I’ve done my own, more in-depth benchmark. The sources and description can be found here https://bitbucket.org/afrishman/androidserializationtest

    My conclusion is:
    Usual Java serialization on an average Android device (if done right) is about 3.6 times faster than Parcelable for writes and about 1.6 times faster for reads.

    And now the question is: if everybody just shouts that Parcelable is better! faster! greater! than maybe its time to support these statements? Otherwise I see this as one huge mistification.

  2. I love this kind of post, we’re all sick and tired of myths all over the interwebz, and it’s great to read other people’s history. In our case, we rarely use Parceable, because the simplicity of Serializable is far superior to the performance benefit we don’t know we would gain.

    best regards,
    hsousa

  3. You don’t need serialization of any kind to transfer objects between activities.

    Just pass reference to object via singleton class from one activity to another.

    1. @Virl process death will eat your singletons. You need to serialize to the Bundle at `onSaveInstanceState()`, or your state will be lost.

  4. @Virl That’s a really, really, reallyreallyreallyreallyreallyreally bad idea.

    You’re counting on the fact that your device isn’t doing any unexpected cleanup or that the user doesn’t flip out of your app to something else (like a phone call) and then come back.

    Here’s how to see that problem. Open up the developer options on your phone and check the “Don’t keep activities” option. Then open your app, navigate to an Activity where you used this approach, click on home, and then use the recent activities button to go back to your app.

    Your singleton will be null, probably resulting in a crash.

Leave a Reply

Your email address will not be published. Required fields are marked *