Android from the foundation · Puzzle 14 of 16

The main thread's queue

Messages arrive on Binder threads, but screen code must run on the main thread. So everything for the screen waits in one line, and the main thread takes it one by one.

The problem

In puzzle 13, a Binder thread picked up the town hall's message. But in puzzle 2, only the main thread could touch the screen. Why only one thread? Because Android's screen code isn't thread-safe: if two threads changed the same button or text at the same moment, it could end up broken. So how does the message get from the Binder thread to the main thread?

See it

Letters wait in the main thread's queue; a Binder thread drops a new one in with a Handler; the main thread takes one at a time; a slow one blocks the touch behind it until the system shows an ANR kernel your app main thread queue: next in line ← draw onCreate touch Binder thread resume
  1. The main thread works from an in-tray: its message queue. Draw the screen, run onCreate, handle a touch (a tap): each is a message, waiting its turn.

    Step 1 of 3
  2. A Binder thread receives "resume" from the town hall. It doesn't touch the screen: it drops the message at the end of the queue with a Handler: an object tied to the main thread's queue that drops messages into it. Predict: when will it run?

    Step 2 of 3
  3. When its turn comes. The main thread takes the first message, runs it to the end, and the line moves up: one at a time, in order. This endless take, run, repeat is the Looper.

    Step 3 of 3

The building block

The main thread has an in-tray: its message queue. Every piece of screen work waits there as a message: draw the screen, handle a touch, run a lifecycle callback. Touches arrive like lifecycle messages: system_server sends them to your app, and they're posted into the queue.

The Looper is the main thread's endless loop from step 3: take the next message, run it to the end, repeat. A Handler is an object tied to one thread's queue that drops messages into it. The ones on this page are tied to the main thread's queue: the Binder thread uses one, then goes back to waiting (it doesn't wait for the main thread), and handler.post { … }post() wraps your code in a Message and puts it in this Handler's queue. 438 public final boolean post(@NonNull Runnable r) { 439 return sendMessageDelayed(getPostMessage(r), 0); 440 } … 782 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, 783 long uptimeMillis) { 784 msg.target = this; … 789 }Handler.java, line 438 · AOSP frameworks/base ↗ is how you drop your own code in. Every app process starts in ActivityThread.main()The real entry point of every app process: it prepares the main Looper, says hello to system_server, then loops forever. 8907 public static void main(String[] args) { … 8927 Looper.prepareMainLooper(); … 8943 thread.attach(false, startSeq); … 8956 Looper.loop();ActivityThread.java, line 8907 · AOSP frameworks/base ↗, Android's code that sets up your app's main thread, which sets up this loop for the main thread and runs Looper.loop()The main thread's endless loop: take one message, run it, repeat. 304 public static void loop() { … 327 for (;;) { 328 if (!loopOnce(me, ident, thresholdOverride)) { 329 return; 330 } 331 }Looper.java, line 304 · AOSP frameworks/base ↗ forever.

The analogy

The clerk at the front desk has an in-tray. Couriers drop letters in through the slot; the clerk takes one at a time and finishes it before the next.

Check yourself

Two Binder threads receive a message for your screen at the same moment. Can your buttons and text be changed by both at once?

Not quite. Neither Binder thread touches the screen: they post to the queue, and only the main thread runs it.

Right. The queue turns many senders into one worker, in order.

What does handler.post { … }post() wraps your code in a Message and puts it in this Handler's queue. 438 public final boolean post(@NonNull Runnable r) { 439 return sendMessageDelayed(getPostMessage(r), 0); 440 } … 782 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, 783 long uptimeMillis) { 784 msg.target = this; … 789 }Handler.java, line 438 · AOSP frameworks/base ↗ do?

Right. A Handler is the slot into the queue.

Not quite. A Handler starts nothing: it drops a message into one queue.

What's the difference between a Binder thread and the main thread?

Not quite. The courier receives; the clerk does the work. The Binder thread hands over through the queue.

Right. Couriers receive; one clerk works.

Sources

What you now know

Binder thread → Handler → the main thread's message queue → the Looper runs it. One message at a time, in order, so buttons and text never need locks.