Android from the foundation · Puzzle 16 of 16

How big can a Binder call be?

In puzzle 13, a call crossed Binder as bytes packed in a Parcel. The receiving process has only so much room for them.

The problem

In puzzle 13, your call crossed into system_server packed into a Parcel. An Intent can carry data along with it, like a photo. But the Parcel has to wait somewhere in system_server until one of its Binder threads is free. How much room is there?

See it

Calls from several apps wait in system_server's Binder buffer; it holds about 1 MB, shared; a 3 MB photo doesn't fit through and is refused; sending a file name instead works kernel Binder your app system_server about 1 MB 3 MB photo doesn't fit photo file name
  1. Calls into system_server wait in its Binder buffer, an area of its memory set up by the kernel, until a Binder thread is free.

    Step 1 of 4
  2. Its size is fixed: about 1 MB (a megabyte, about a million bytes), shared by every call in progress, from every app. Predict: your app sends an Intent carrying a 3 MB photo. What happens?

    Step 2 of 4
  3. It doesn't fit, so the call fails with TransactionTooLargeException. A transaction is one Binder call.

    Step 3 of 4
  4. The fix: save the photo to a file and send only its name, a few bytes. Your other screen reads the file itself: same app, same UID, so the sandbox allows it.

    Step 4 of 4

The building block

Calls into a process wait in its Binder buffer: an area of that process's memory, set up by the kernel when the process first uses Binder. Its size is fixed then, so it can't grow later: about 1 MB.

That 1 MB is shared by every call in progress into that process, from every app. So a call can fail even when it's well under 1 MB, if many other calls are waiting at the same moment.

A call that doesn't fit fails with TransactionTooLargeException (a transaction is one Binder call). The fix is never a bigger Parcel: save the data somewhere both sides can reach, like a file, and send its name, or a number that identifies it. Inside your own app this just works: all its screens run as the same UID, so the sandbox lets every one of them read the file. (Sharing with another app needs system_server's help: a later puzzle.)

Splitting the data into many smaller calls doesn't help either: they all go through the same shared 1 MB, and the receiver would have to glue the pieces back together.

The analogy

The town hall's mailbox has one fixed size, shared by everyone's letters. A parcel that doesn't fit the slot comes back. So you leave the parcel at home and send a note that says where it is.

Check yourself

You put a 3 MB photo into an Intent and start an Activity. What happens?

Not quite. The buffer has a fixed size of about 1 MB. A Parcel that doesn't fit fails; it doesn't wait.

Not quite. Objects can't leave a process: the Intent is packed into a Parcel and must fit the receiver's Binder buffer.

Right. Every Intent crosses Binder, through a buffer of about 1 MB.

Your app's call into system_server carries 400,000 bytes (0.4 MB). It works most of the time but fails now and then. Why?

Right. A shared, fixed-size buffer: other calls take room too.

Not quite. The buffer's size is fixed when the process first uses Binder; it never changes. Re-read that part ↑

Not quite. The limit is about 1 MB, more than 0.4 MB. It's the sharing that hurts.

How do you show a big photo on another screen of your own app?

Not quite. They all cross the same shared 1 MB, and the receiver has to glue them back together. Send where the photo is, not the photo. Re-read that part ↑

Not quite. The Parcel isn't the limit; the receiver's buffer is, and it's fixed.

Right. Send a small reference; let the other side read the data itself.

Sources

What you now know

Calls into a process wait in its Binder buffer: about 1 MB, shared by every call in progress. A call that doesn't fit fails with TransactionTooLargeException. Send where the data is, not the data.