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:
- Thread
- Looper
- Message Queue
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
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().