Android from the foundation · Puzzle 13 of 16

Inside a Binder call

In puzzle 4, Binder carried messages between processes. Here's how a method call actually crosses: packed into bytes, marked with the sender's identity, and picked up by a waiting thread.

The problem

When your app calls startActivity(), the method that sends an Intent, it looks like an ordinary method call. But the object that does the work lives in system_server's memory, another process. And an object is data in a process's memory (puzzle 0), so it can't leave that process. So how does a call cross from your app to the town hall and back? The answer: each "message" from puzzle 4 is really a method call on an object in another process.

See it

Your app's thread calls the proxy, which packs a Parcel; Binder in the kernel carries it and stamps your UID; a Binder thread in system_server calls the real method; the reply comes back while your thread waits; it works the other way too kernel Binder your app proxy system_server real one waiting… goes on Binderthread ParcelUID reply
  1. Your app calls a method that lives in system_server. In your process sits a proxy: an object that looks exactly like the one in system_server.

    Step 1 of 5
  2. Objects can't leave a process; bytes can. The proxy packs the arguments into a Parcel: a flat envelope of bytes (a byte is the smallest unit of data a program stores). Your thread that called now waits.

    Step 2 of 5
  3. Binder, in the kernel, carries the Parcel and stamps it with your app's UID, its key. Predict: who picks it up at the town hall?

    Step 3 of 5
  4. One of system_server's Binder threads: ordinary threads inside a process that wait for Binder to hand them calls. It unpacks the Parcel and calls the real method.

    Step 4 of 5
  5. The reply (a result, or just "done") travels back the same way, and only now does your waiting thread go on. If it was the main thread, the screen waited too.

    Step 5 of 5

The building block

On your side sits a proxy: an object that looks exactly like the object in the other process that does the work. You never write it: Android's own code hands it to your app. Calling it packs the arguments into a Parcel, a flat envelope of bytes. Binder carries the Parcel across, and stamps it with your app's UID.

Binder itself lives in the kernel. A Binder thread is different: an ordinary thread inside a process, one of a few that wait for Binder to hand them calls. In the receiving process, one of them unpacks the Parcel and calls the real method. Meanwhile the thread in your app that made the call waits (if that's the main thread, the screen waits too); the reply comes back the same way, and only then does the call return.

It works both ways: system_server holds proxies for your app's objects too, and its calls arrive on your app's own Binder threads.

The stamp is how permission checks from puzzle 7 work, and your app can't change it. (For the curious: the receiver reads it with Binder.getCallingUid()Native: the UID the kernel stamped on the call. The caller can't fake it. 338 public static final native int getCallingUid();Binder.java, line 338 · AOSP frameworks/base ↗; Binder's own code is in the Linux kernel, under drivers/android/binderThe Binder driver in mainline Linux (now written in Rust) stamps the sender's user ID on every transaction. Android phones run Android's own kernel branches, which may still use the older C driver. 392 from_parent, 393 sender_euid: Kuid::current_euid(),transaction.rs, line 392 · Linux kernel ↗.)

The analogy

You hand your letter to the counter in your house (the proxy). The post office carries the envelope, stamps your key (your UID) on it, and a courier at the town hall's door brings it to the right desk.

Check yourself

Your main thread calls a method on a proxy, and system_server takes 2 seconds to answer. What does the user see?

Not quite. The calling thread waits until the reply comes back. If it's the main thread, nothing else on screen runs.

Right. A Binder call waits for its reply, so slow calls don't belong on the main thread.

Not quite. Binder doesn't give up: your thread waits for the reply.

Can an app pretend to be another app in a Binder call?

Not quite. The stamp isn't part of your envelope's contents: Binder adds it.

Right. The UID comes from the kernel, not from your data.

In system_server, which thread first picks up your call?

Not quite. Incoming calls are picked up by the couriers, Binder threads, not by the main thread.

Right. Each process keeps a few Binder threads waiting for calls.

Sources

What you now know

proxy → Parcel → Binder stamps your UID → a Binder thread → the real method, and back. Meanwhile the thread that called waits.