Whats in a Looper?

Important part of Message passing between threads

We often get a message when we try to display a Toast from a background thread ( for example ).

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
     at android.os.Handler.<init>(Handler.java:121)
     at android.widget.Toast.<init>(Toast.java:68)
     at android.widget.Toast.makeText(Toast.java:231)

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

  1. The UI thread has a Looper by default. So we will create a Handler 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
      private Handler handler = new Handler();
    

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.

  1. Using the above created Handler and its post() method, post a Runnable( which contains a task to be executed in the UI thread) to the MessageQueue.
   handler.post(new Runnable() {
          @Override
          public void run() {}
   });
   

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

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

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

message.target.handleMessage(message);

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.