Whats in a Looper?
We often get a message when we try to display a Toast
from a background thread ( for example ).
A thing to wonder is that why can’t it just say that “You cannot display a Toast on a worker thread”. What does the first line
of the exception mean? ( Thank god there is StackOflw for just copying the answer without actually knowing how it solves the problem and in some cases what was the problem). The first line of exception isnt decipherable unless i know what a Looper
and Handler
is. So i looked into that and also looked into how they have been implemented in Android.
What exactly is Looper
A Looper
can be thought of as a Message Queue. MessageQueue
is actually also a class that that has been created in
Android which Looper
uses internally. However lets take it as a simple Queue
which can contain Message
objects. Message
is the component in which you can wrap anything and send it to a Handler
for processing. Looper
class
provides methods to loop over that queue and process the messages in it.
As an example, suppose we have a worker thread and we want to send some data from it to the main thread. Then what should be done? We will simply send the data to the UI thread’s MessageQueue
and the its looper will process the MessageQueue
and execute the tasks( using a Handler
). These are the steps for passing the data
- The UI thread has a
Looper
by default. So we will create aHandler
in the main thread. While instantiating the handler, it will associate the created handler with the looper(lets say it as a MessageQueue for now) of the main thread
And of course, there is only one Looper
that can be created per thread. Since there is already a Looper
associated with the UI thread, it will throw exception when you will try to call Looper.prepare()
on it. So you can create a Looper
on any other thread but not on the main thread.
- Using the above created
Handler
and its post() method, post a Runnable( which contains a task to be executed in the UI thread) to theMessageQueue
.
It will post the message to the queue of the UI thread beacuse this handler is associated with the Looper
of UI thread.
Now the Looper of the UI thread will loop through this queue and process the tasks that are present.
Implementation of Looper
Looper functioning
This is how a Looper
has been implemented internally in Android.
Since a Looper
belongs specifically to a particular thread,so it internally uses a ThreadLocal
object for storing
the information about that thread. ThreadLocal
as the name suggests, is an object which is local to every thread accessing this object. So suppose you create a ThreadLocal
object and have 2 threads accessing it, then each thread can do get()
and set()
on this object separately and the value set by one thread will remain local to that thread only. ThreadLocal
saves the reference to a thread and its associated value in a HashMap like data-strcuture.
Now coming back to Looper
,Looper.prepare()
is implemented as
So the threadlocal’s value is set as the instance of the Looper
. Also a new instance of MessageQueue
,which is the soul of a Looper
is created further in its constructor.(quitAllowed
is a boolean used internally in MessageQueue).
There is also a function prepareMainLooper()
which is used by Android internally to initialize the Looper
for
UI thread. So we dont need to call this function at all.
Now to start your Looper
for it to process the queue,you will have to call Looper.loop()
.It gets its
MessageQueue
and checks the value of MessageQueue.next()
in an infinite loop. Now next()
is actually a
blocking call. When there are no messages left in the queue, the thread blocks until there are new messages. Then it takes
the messages from the queue and return back to the Looper
. The Looper
sends the Message
object for
processing to its corresponding Handler
using
Every Message
object holds a reference to its corresponding Handler
. So target
here refers to that Handler
. After a Message
has been dispatched, all its variables are cleared( it is recycled ).In any case when next()
returns NULL
, then the loop ends and MessageQueue
quits.
Quitting the Looper
Since a Looper
processes the messages in an infinite loop, it must be quit after the work associated with it
is done. So we can use quit()
to finish it abruptly and it will not process any message pending in the queue.
Or using quitSafely()
which will process all the pending messages in the queue and then terminate. But both
of these methods will not allow any message scheduled in the future. Once quit()
is called, Looper.prepare()
cannot be called again.
Sync Barrier
Another useful thing ( sometimes )in Looper
is postSyncBarrier()
. So if you have started the loop,and sometimes
you want that the messages in the queue should not be processed until a particular condition is satisfied,you can block
the process using this function. But again you have to unblock this operation using removeSyncBarrier(int token)
,
with the token that you get in postSyncBarrier()
otherwise the system will hang. As the name suggests,this blocking/unblocking works only in case of synchronous messages (where ack. is required). An example for this function could be like
in the UI thread, block all messages until the view is rendered.
And to answer the question at the beginning, actually Toast
uses a Handler
internally in TN
class.
And you cant create a Handler
in a thread that doesnt have a Looper
. So it throws the exception.
If Looper.prepare()
is called before calling Toast
, no exception is thrown.