Looper, Handler & Threading Quiz

ANDROID › System

How many Loopers can a single thread have at once?

Answer: Exactly one; Looper is thread-local to each thread.

A Looper is thread-local; calling Looper.prepare() a second time on the same thread throws because the thread already has a Looper.

Which call correctly posts work to the UI thread from a background thread?

Answer: Handler(Looper.getMainLooper()).post(runnable) to the main thread

Binding a Handler to Looper.getMainLooper() and calling post() enqueues the Runnable on the main thread's MessageQueue; Looper itself has no post() method.

What must you call to make a plain background thread able to receive Handler messages?

Answer: Looper.prepare() creates the queue, then Looper.loop() runs it

Looper.prepare() creates the thread's Looper and MessageQueue, and Looper.loop() starts dispatching messages so a Handler bound to it can run work.

Which scenario is the most direct cause of an ANR?

Answer: Performing a synchronous disk read on the main thread in onCreate

Blocking the main thread with synchronous I/O prevents it from servicing input events and rendering, which triggers an Application Not Responding error.

What is the difference between Looper.quit() and Looper.quitSafely()?

Answer: quit() drops pending messages; quitSafely() first delivers those already due

quit() terminates immediately and discards every pending message, while quitSafely() finishes dispatching messages whose time has arrived before stopping.

Why is the no-argument Handler() constructor deprecated?

Answer: It implicitly uses the current thread's Looper, and crashes if none exists

Relying on the implicit current-thread Looper hides which thread the work runs on and throws if the thread has no Looper, so an explicit Looper should be passed.

What does a HandlerThread provide out of the box?

Answer: A thread that sets up its own Looper and runs the message loop

HandlerThread is a Thread that sets up its own Looper and runs the message loop, so a Handler attached to it serializes work on that single background thread.

Why is Message.obtain() preferred over constructing a Message with new Message()?

Answer: It reuses a Message from a global pool, avoiding a new allocation

Message.obtain() pulls a recycled instance from Android's global Message pool, reducing garbage-collection pressure compared with allocating a new object each time.

When you call handler.postDelayed with a delay, how does the MessageQueue decide when to dispatch it?

Answer: It inserts the message by absolute trigger time (uptimeMillis() + delay) and dispatches then

Delayed messages are stored with an absolute wake time based on uptimeMillis and the queue keeps them ordered by that time, blocking efficiently until the soonest one is due.

What does Looper.myLooper() return?

Answer: The Looper for the current thread, or null if none exists

Looper.myLooper() reads the thread-local Looper for the current thread and returns null when the thread has not called Looper.prepare().

On Android, what does kotlinx.coroutines Dispatchers.Main ultimately use to resume coroutines on the UI thread?

Answer: A Handler bound to the main thread's Looper

The Android Main dispatcher is implemented on top of a Handler attached to Looper.getMainLooper(), so resumed continuations are posted as messages onto the main thread's queue.

You need a task to run roughly 15 minutes from now and to still execute even if the app's process is killed first. Which approach fits?

Answer: WorkManager, which persists deferred work across process death and reboot

Handler/Looper scheduling lives only in memory and dies with the process, whereas WorkManager persists deferrable work and guarantees it runs even after the app is killed or the device reboots.

How do you cancel a Runnable you scheduled with handler.postDelayed before it executes, for example to debounce rapid input?

Answer: Call handler.removeCallbacks(runnable) using the same Runnable

removeCallbacks(runnable) removes any pending messages wrapping that exact Runnable from the MessageQueue, which is the standard way to implement debouncing with a Handler.

What is the purpose of registering an IdleHandler via MessageQueue.addIdleHandler?

Answer: It runs a callback when the queue has no dispatchable message and goes idle

An IdleHandler is invoked when the Looper's MessageQueue runs out of currently dispatchable messages, letting you defer low-priority work until the thread would otherwise sit idle.

Back to Looper, Handler & Threading