Looper/Handler API - Part 1

12 Jan 2017

Contents

last modified: 02-feb-2017 (23:15)

This is part one of looper/handler API series

Background

In Android, thread communication is trivial unless it involves UI thread. Care must be taken when communicating with UI thread, because blocking UI thread can effect you user’s experience badly.

Many high level threading APIs in java are not well suited for communication with UI thread because of their blocking nature. e.g. BlockingQueue. To overcome these issues, Android has come up with it’s own message passing mechanism.

Looper/Handler API in Android is specific implementation of Consumer-Producer problem (also known as bounded-buffer) in which Producer never blocks. The producer puts the tasks in the queue and consumer consumes them on appropriate time.

Classes

Looper

Looper associates a Message Queue with the thread. Later, this queue will be utilized by Handler and Looper

Handler

Handler is a two way interface. In producer thread, it inserts messages in the queue while in consumer thread it consumes those messages.

Message Queue

Message Queue is an unbounded linked list attached with looper. It contains object of type Message. Looper will loop through this queue and pass messages to handler.

Message

Message defines a message containing a description and arbitrary data object that can be sent to a Handler.

Relationship

Handler
Handler
Looper
Looper
(1,*)
(1,*)
(1, 1)
(1, 1)
Message Queue
Message Queue
(1, 1)
(1, 1)
(1, 1)
(1, 1)
thread
thread
(1, 1)
(1, 1)
(1, 1)
(1, 1)
Message
Message
(0, *)
(0, *)
(1, 1)
(1, 1)

Example

Just to understand how every component fits together, let’s write a simple app which does a long running operation when a button is clicked.

We will write a Worker Thread which will do some long running operation in background when a message is received. On the main thread, we will add message to the MessageQueue using handler.

I am writing code for this example here. In next article I will explain how it works.

Worker Thread

class WorkerThread extends Thread implements Handler.Callback{

    private static final boolean LOG = true;

    private Handler mHandler;

    WorkerThread() {
        super("WorkerThread");
    } // WorkerThread

    @Override
    public void run() {
        Looper.prepare(); // attached looper to current thread
        mHandler = new Handler(this); // attach this handler to looper of current thread
        Looper.loop(); // starts to process queue, this is a blocking call.

        if(LOG){
            log("run -> %s", "terminating worker thread.");
        }

    } // run


    @Override
    public boolean handleMessage(Message msg) {
        try {
            int sleepTime = new Random().nextInt(2000);
            if(LOG){
                log("handleMessage -> sleeping for [%s ms]", sleepTime);
            }
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(LOG){
            log("handleMessage -> what[%s], thread[%s]", msg.what, Thread.currentThread().getName());
        }
        return true;
    } // handleMessage

    Handler getHandler(){
        return mHandler;
    } // getHandler

    private void log(String format, Object... args){
        Utils.log("class[WorkerThread]", String.format(format, args));
    } // log

} // WorkerThread

Main/Launcher Activity

public class MainActivity extends AppCompatActivity {

    private static final boolean LOG = true;
    private WorkerThread workerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActivityUtils.setToolbar(this, getString(R.string.app_name));
    } // onCreate

    @Override
    protected void onStart() {
        super.onStart();
        if(LOG){
            log("onStart()");
        }

        workerThread = new WorkerThread();
        workerThread.start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(LOG){
            log("onStop()");
        }
        if(workerThread != null){
            if(LOG){
                log("onStop -> %s", "quit looper.");
            }

            workerThread.getHandler().getLooper().quit();
            workerThread = null;
        }
    } // onStop

    private void log(String format, Object... args){
        Utils.log("class[MainActivity]", String.format(format, args));
    } // log

    public void onDoWork(View view) {
        if(workerThread.getHandler() != null){
            int what = new Random().nextInt(100);
            if(LOG){
                log("onDoWork -> seding [%d]", what);
            }

            workerThread.getHandler().sendMessage(workerThread.getHandler().obtainMessage(what));
        }
    } // onDoWork

} // MainActivity

MainActivity Layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.mallaudin.handlers.ui.MainActivity">

    <include layout="@layout/toolbar_layout" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:onClick="onDoWork"
        android:text="@string/do_work"/>

</RelativeLayout>

Output (after clicking Do Something button many times, Log message are specially crafted for this example, see source code for better understanding)

D/handlerapp: class[MainActivity] -> onStart()
D/handlerapp: class[MainActivity] -> onDoWork -> seding [44]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [1963 ms]
D/handlerapp: class[MainActivity] -> onDoWork -> seding [8]
D/handlerapp: class[MainActivity] -> onDoWork -> seding [22]
D/handlerapp: class[MainActivity] -> onDoWork -> seding [36]
D/handlerapp: class[MainActivity] -> onDoWork -> seding [70]
D/handlerapp: class[MainActivity] -> onDoWork -> seding [89]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[44], thread[WorkerThread]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [227 ms]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[8], thread[WorkerThread]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [1935 ms]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[22], thread[WorkerThread]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [1339 ms]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[36], thread[WorkerThread]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [542 ms]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[70], thread[WorkerThread]
D/handlerapp: class[WorkerThread] -> handleMessage -> sleeping for [1255 ms]
D/handlerapp: class[WorkerThread] -> handleMessage -> what[89], thread[WorkerThread]
D/handlerapp: class[MainActivity] -> onStop()
D/handlerapp: class[MainActivity] -> onStop -> quit looper.
D/handlerapp: class[WorkerThread] -> run -> terminating worker thread.
D/handlerapp: class[MainActivity] -> onStart()
D/handlerapp: class[MainActivity] -> onStop()
D/handlerapp: class[MainActivity] -> onStop -> quit looper.
D/handlerapp: class[WorkerThread] -> run -> terminating worker thread.

Please don’t use this code in production, it is not thread safe, I have written here just give you an idea about how it works.

Download Source Code