Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Efficient Object Serialization on Android with Parcelable and Serializable

Notes 2

Passing rich data between Android components relies on turning objects into a transmittable form, typically via Intent and Bundle. Two primary mechanisms exist: Java’s Serializable and Android’s Parcelable. Both serialize state into bytes, but they differ in performance, capabilities, and appropriate use cases.

When to serialize

  • Persisting in-memory objects to disk or caches
  • Sending objects over sockets or other network channels
  • Remote method invocation (e.g., Binder IPC on Android)
  • Activity/Service/Broadcast/Provider boundaries via Intent/Bundle

Parcelable in Android Parcelable defines how an object flattens itself into a Parcel and reconstructs from it. It is optimized for Binder IPC and inter-component communication on Android and typically outperforms Serializable.

Contract

  • int describeContents(): usually returns 0. Non-zero is reserved for special cases like file descriptors.
  • void writeToParcel(Parcel dest, int flags): write fields to the Parcel in a deterministic order.
  • static final Parcelable.Creator<T> CREATOR: recreates objects from a Parcel and builds arrays.

Guidelines for choosing

  • In-memory IPC and intents: prefer Parcelable for speed and allocation efficiency.
  • Long-term persistence or external storage: avoid Parcelable; it is not version-tolerant or stable for storage. Use Serializable or your own stable format (JSON/Proto/etc.).
  • Serializable tends to allocate more temporaries and can trigger more GC compared to Parcelable on Android.

Implementing Parcelable: steps

  1. Implement Parcelable on the class to be transmitted.
  2. In writeToParcel, write each field in a consistent order.
  3. Provide a constructor or factory that reads fields in the exact same order.
  4. Expose a public static final CREATOR implementing Parcelable.Creator<T>.

Example 1: simple Parcelable with null-safe handling

package example.transport;

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

public class Contact implements Parcelable {
    private Integer contactId; // may be null
    private String displayName;

    public Contact() {}

    public Contact(Integer contactId, String displayName) {
        this.contactId = contactId;
        this.displayName = displayName;
    }

    protected Contact(Parcel in) {
        // Read nullability flag, then the value
        boolean hasId = in.readByte() != 0;
        this.contactId = hasId ? in.readInt() : null;
        this.displayName = in.readString();
    }

    public Integer getContactId() { return contactId; }
    public void setContactId(Integer contactId) { this.contactId = contactId; }

    public String getDisplayName() { return displayName; }
    public void setDisplayName(String displayName) { this.displayName = displayName; }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // Encode nullability explicitly for boxed types
        if (contactId == null) {
            dest.writeByte((byte) 0);
        } else {
            dest.writeByte((byte) 1);
            dest.writeInt(contactId);
        }
        dest.writeString(displayName);
    }

    public static final Creator<Contact> CREATOR = new Creator<Contact>() {
        @Override public Contact createFromParcel(Parcel in) {
            return new Contact(in);
        }
        @Override public Contact[] newArray(int size) {
            return new Contact[size];
        }
    };
}

Example 2: Parcelable containing primitives, String, and a list of other Parcelables

package example.transport;

import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;

public class ParcelBundle implements Parcelable {
    private ArrayList<Row> rows;
    private int count;
    private String label;

    public ParcelBundle() {
        this.rows = new ArrayList<>();
    }

    protected ParcelBundle(Parcel in) {
        this.count = in.readInt();
        this.label = in.readString();
        this.rows = in.createTypedArrayList(Row.CREATOR);
    }

    public ArrayList<Row> getRows() { return rows; }
    public void setRows(ArrayList<Row> rows) { this.rows = rows; }

    public int getCount() { return count; }
    public void setCount(int count) { this.count = count; }

    public String getLabel() { return label; }
    public void setLabel(String label) { this.label = label; }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(count);
        dest.writeString(label);
        dest.writeTypedList(rows);
    }

    public static final Creator<ParcelBundle> CREATOR = new Creator<ParcelBundle>() {
        @Override public ParcelBundle createFromParcel(Parcel in) { return new ParcelBundle(in); }
        @Override public ParcelBundle[] newArray(int size) { return new ParcelBundle[size]; }
    };

    // Example row element; must implement Parcelable to be used in writeTypedList
    public static class Row implements Parcelable {
        private long id;
        private String value;

        public Row(long id, String value) { this.id = id; this.value = value; }
        protected Row(Parcel in) { this.id = in.readLong(); this.value = in.readString(); }

        public long getId() { return id; }
        public String getValue() { return value; }

        @Override public int describeContents() { return 0; }
        @Override public void writeToParcel(Parcel dest, int flags) {
            dest.writeLong(id);
            dest.writeString(value);
        }

        public static final Creator<Row> CREATOR = new Creator<Row>() {
            @Override public Row createFromParcel(Parcel in) { return new Row(in); }
            @Override public Row[] newArray(int size) { return new Row[size]; }
        };
    }
}

Example 3: Parcelable with inheritance

package example.transport;

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

public abstract class BasePacket implements Parcelable {
    private int header;

    protected BasePacket(int header) { this.header = header; }
    protected BasePacket(Parcel in) { this.header = in.readInt(); }

    public int getHeader() { return header; }

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

public class DataPacket extends BasePacket {
    private int payload;

    public DataPacket(int header, int payload) {
        super(header);
        this.payload = payload;
    }

    private DataPacket(Parcel in) {
        super(in);
        this.payload = in.readInt();
    }

    public int getPayload() { return payload; }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeInt(payload);
    }

    public static final Creator<DataPacket> CREATOR = new Creator<DataPacket>() {
        @Override public DataPacket createFromParcel(Parcel in) { return new DataPacket(in); }
        @Override public DataPacket[] newArray(int size) { return new DataPacket[size]; }
    };
}

Serializable in Java Serializable is a marker interface (no methods). An object graph can be serialized only if every referenced type in that graph is also Serializable.

  • Pros: trivial to adopt (implements Serializable), useful for disk/network persistence and Java interop.
  • Cons on Android: more allocations and slower than Parcelable for IPC/intents; versioning still requires care for long-term storage.

Sreializable example

package example.transport;

import java.io.Serializable;

public class NumbersCarrier implements Serializable {
    private static final long serialVersionUID = 2L;

    private Double alpha;
    private Float beta;

    public NumbersCarrier() {}

    public Double getAlpha() { return alpha; }
    public void setAlpha(Double alpha) { this.alpha = alpha; }

    public Float getBeta() { return beta; }
    public void setBeta(Float beta) { this.beta = beta; }
}

Passing values with Intent and Bundle

  • Primitives and strings

    • Put: intent.putExtra("age", 42); intent.putExtra("title", "Engineer");
    • Get: getIntent().getIntExtra("age", 0); getIntent().getStringExtra("title");
  • Parcelable objects

    • Put: intent.putExtra("contact", contact);
    • Get: Contact c = getIntent().getParcelableExtra("contact");
  • Parcelable lists

    • Put: intent.putParcelableArrayListExtra("rows", rows);
    • Get: ArrayList<ParcelBundle.Row> rows = getIntent().getParcelableArrayListExtra("rows");
  • Serializable objects

    • Put: intent.putExtra("payload", serializableObject);
    • Get: NumbersCarrier nc = (NumbersCarrier) getIntent().getSerializableExtra("payload");
    • Note: every nested field must also implement Serializabel, or you will get java.io.NotSerializableException at runtime.
  • Mixed graphs

    • A Serializable graph cannot contain non-Serializable members (e.g., Parcelable-only types) without causing NotSerializableException.
    • Parcelable graphs must consist of parcelable-friendly members; use write/read methods for framework types.
  • Enums

    • Enums are Serializable by default: intent.putExtra("state", MyState.ACTIVE);
    • Alternatively, pass by name or ordinal too decouple from serialization: intent.putExtra("stateName", state.name());

Caveats and best practices

  • Always read from Parcel in the same order you wrote.
  • Prefer createTypedArrayList/ writeTypedList for lists of Parcelables.
  • Avoid using Parcelable for long-term storage or across app version boundaries; it is not stable for persistence.
  • Keep payloads small to respect Binder transaction limits.
  • For large or structured persistence, prefer stable formats (e.g., JSON, Protocol Buffers) or databases, and only use Parcelable for in-process IPC and short-lived transfers.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.