Android Looper: Communicate with main UI thread via Handlers

Android looper is very good concept for handling various task on same thread. Whole android UI runs on single thread. So how this looper works and what are entities involved in this process.

Four things involved in this process are:

  1. Thread
  2. Looper
  3. Message Queue
  4. Handler

 

So the story begins with the thread. Once a thread started will be dead after completing its work and wont be usable. If we call Looper.prepare() on that thread then a message queue is associated with that thread.

And when Looper.loop() is called then the looper start looping that message queue. Now whenever new handler instance is created on some thread by calling new Handler() then this handler attach itself to that Thread. Now this handler can post message to the message queue. These messages aggregate on message queue and looper picks these message one by one and pass them to the handler which have posted that message on message queue.

This looper will loop until looper.quit() is called on that thread.

Sample Looper Application

Modify your main layout file res/layout/activity_main.xml as below

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.androidsrc.looper.MainActivity" >

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Start Looper for Background Thread" />

</RelativeLayout>

In your MainActivity.java, We need to create two Handlers. One Handler is associated with main UI thread while other is associated with message queue of another thread.

package com.androidsrc.looper;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

import java.util.Random;

public class MainActivity extends Activity implements OnClickListener {

    private static Handler mainUIHandler;
    private static Handler bgThreadHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainUIHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                // TODO Auto-generated method stub
                Toast.makeText(getApplicationContext(),
                        "Random no " + (int) msg.obj + " from BGThread",
                        Toast.LENGTH_SHORT).show();
            }

        };
        new BackgroundThread().start();
    }

    class BackgroundThread extends Thread {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            this.setName(BackgroundThread.class.getName());
            this.setPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
            // Looper must be prepared before creating handler for background
            // thread
            Looper.prepare();

            bgThreadHandler = new Handler() {

                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    Bundle bundle = msg.getData();
                    Toast.makeText(getApplicationContext(),
                            bundle.getString("hello_msg"), Toast.LENGTH_SHORT)
                            .show();

                    try {
                        // Perform some task that need to be updated to UI
                        // thread after completion
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    if (mainUIHandler != null) {
                        Message message = mainUIHandler.obtainMessage();
                        message.obj = new Random().nextInt(100);
                        mainUIHandler.sendMessage(message);
                    }
                }

            };

            // Run the message queue in this thread call Looper.loop()
            Looper.loop();
        }

    }

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        // end Looper for background thread
        if (bgThreadHandler != null)
            bgThreadHandler.getLooper().quit();
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        if (bgThreadHandler != null) {
            Message msg = bgThreadHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString("hello_msg", "from on click of button");
            msg.setData(bundle);
            bgThreadHandler.sendMessage(msg);
        }
    }
}

When we click button, it will send message to background thread handler. I have attached Bundle object with Message to show how we can use Bundle with Message. After sending message to a handler, it is processed in handleMessage(Message). In background thread, we perform some intensive task that should not be performed n main UI thread. After its completion, we sendMessage(Meassage) to  Handler of main UI where it can reflect processing results on main UI. In this example, i have called Looper.quit() in onDestroy() on activity to ensure finishing of loop. Generally, we should have logic with handlers with some particular message so that it process quit().

 

 

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *