I06 — Looper lab
Rebuild the main thread's message loop by hand: a Looper, a Handler, a MessageQueue, and a choreographer-style frame callback. Then break it on purpose and watch the ANR.
Every Android app has one thread that is not allowed to block, and the reason is a loop that pulls messages from a queue. Building a small version of that loop is the fastest way to understand why the main thread behaves the way it does.
What you are building#
A single screen with buttons that post work to a Handler and a log that shows the order of
execution. A second mode posts a long task to the main thread and demonstrates an ANR, so you can
see the failure the design exists to prevent.
Steps#
- Create a Handler on a background Looper
Create a
HandlerThread, get itsLooper, and create aHandlerbound to it. Post a runnable that logs the thread name. That is the whole mechanism in miniature.1You should seeWork runs off the main thread. - Post with delays and order
Post three runnables: immediate, 100 ms, and 50 ms. The order should be immediate, 50 ms, 100 ms. The queue is ordered by time, not by insertion.
You should seeThe log shows the order you posted, with delays respected. - Post from the main thread
Post from a button click and log both the calling thread and the executing thread. They differ, and that difference is the point of a Handler.
You should seeThe work runs on the background thread. - Build the frame callback
Use
Choreographerto post a frame callback that logs its frame time and re-posts itself. Compare the frame times with the wall clock: the callback is aligned to vsync, not to your delay.You should seeA callback runs at a steady cadence. - Block the main thread
On a button click, sleep on the main thread for longer than the input dispatch timeout. The UI freezes and the system shows an ANR. The log has an ANR entry, and the trace has the blocked interval.
You should seeAn ANR dialog or a frozen UI. - Read the ANR trace
Pull the ANR trace from the device and find the main thread's stack. It points at the sleep, which is the evidence the system uses.
You should seeThe main thread stack, blocked in your sleep. - Fix it properly
Move the long task to the HandlerThread and post the result back to the main thread. The UI stays responsive and the work still completes.
You should seeNo ANR, and the work still runs.
The Handler and the thread:
val thread = HandlerThread("worker").apply { start() }
val handler = Handler(thread.looper)
handler.postDelayed({ Log.d("LooperLab", "100 ms") }, 100)
handler.postDelayed({ Log.d("LooperLab", "50 ms") }, 50)
handler.post { Log.d("LooperLab", "immediate") }adb shell am start -W -n com.example.looperlab/.MainActivity
adb shell input tap 540 1200
adb logcat -b system -d | grep -i "ANR in"
adb shell ls /data/anrExample output
Illustrative — the trace path and timings differ.Verification#
The ordering test is the cleanest check: post with 0, 100 and 50 ms delays and confirm the execution order. Then confirm that the frame callback's timestamps cluster at the vsync interval rather than at arbitrary times.
What you learned#
- A Handler is a queue, not a thread. It posts to a Looper, which owns the thread.
- The queue is time-ordered. Delays are respected; posting order is not the only ordering.
- The main thread is single and serial. That is the design, and blocking it is the only way to ANR.
- The Choreographer is a vsync-aligned Handler. Frame callbacks are messages like any other.
Read Looper, Handler and MessageQueue for the real implementation, and Lab 12 — Jank for what happens when the queue is overloaded.
Extend it#
- Add a
MessageQueueidle handler and log when the queue goes idle; it is the hookIdleHandlerexposes for deferred work. - Add a second HandlerThread and compare throughput with one thread doing both jobs.
- Add a
SyncBarriertest by posting an asynchronous message and observing that it can jump ahead of synchronous ones.
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsProcesses and threads overview | App qualitydeveloper.android.comaccessed 2026-10-27↩
- docsAnrdeveloper.android.comaccessed 2026-10-27↩
- docsOverview of system tracingdeveloper.android.comaccessed 2026-10-27↩
- docsCompose performancedeveloper.android.comaccessed 2026-10-27↩
- docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩