In the previous article, we have talked about Thread . As we know it is possible to access UI components inside the worker thread using some of the methods like
- Activity.runOnUiThread(Runnable),
- View.post(Runnable)
- View.postDelayed(Runnable, long)
But there is one more alternative in android i.e Handler which also allows you communicate back with the UI thread from other background thread. In this article, we will talk about Handler in brief.
Main Thread
Android handles all the UI operations and input events from one single thread which is known as Main or UI thread. Android collects all events in this thread in a queue and processes this queue with an instance of the Looper class.
Android supports Thread class to perform asynchronous processing.
Why Handler
By the android single thread model rule, we can not access UI elements (bitmap, text view, button, etc..) directly for another thread defined inside that activity. If you need to update the UI from any worker thread, you need to connect it with the main thread. To achieve this Android comes with the classes i.e. Handler or AsyncTask.
A Handler allows you to communicate back with the UI thread from other background thread. This is useful in android as android doesn’t allow other threads to communicate directly with UI thread.
How Handler Works
- Activity’s main UI thread already has a MessageQueue and a Looper created by the framework and it can be retrieved by calling Looper.getMainLooper().
- The MessageQueue consist of tasks that need to update the UI. This task could be as simple as rendering a button or doing something on click of a button etc. A task may be a Message or a Runnable object.
- If you want to send some task from a background thread to the message queue of the mainThread then we cannot do it directly. But Handler makes it possible.
- Handler contains the reference of this message queue and is associated with the Looper of the MainThread. So in the background thread when you send a task (Message or Runnable object) using handler it gets added in the message queue of the MainThread.
- The Looper of the main Thread loops through the message queue and take one task at a time and send it to the handler. The handler will then execute the task and update the UI.
Handler
- Handler allows you to send and process Message and runnable objects associated with a thread’s MessageQueue.
- Handler instance is associated with a single thread and that thread’s MessageQueue.
- When you create a new Handler, it is bound to the thread/message queue of the thread that is creating it.
- It will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
- The background thread can communicate with the handler, which will do all of its work on the activity’s UI thread. For example, if you want to update the progress bar from the background thread what you do is get the handler which belongs to the main thread and simply sends a message updating the progress bar.
There are two main uses for a Handler:
- To schedule messages and runnables to be executed at some point in the future.
- To enqueue an action to be performed on a different thread than your own.
There are two ways of communicating with the handler:
- Messages
- Runnable objects
Message
Defines a message containing a description and arbitrary data object that can be sent to a handler.
While the constructor of message is public, the best way to get one of these is to call Message.obtain() or one of the Handler obtainMessage methods which will pull them from the pool of recycled objects.
How to send Message objects?
sendMessage(): Puts the message on the queue immediately.
sendMessageAtFrontOfQueue(): Puts the message on the queue immediately and places it in front of the message queue so your message takes priority over all others.
sendmessageAtTime(): puts the message on the queue at the stated time, expressed in the form of milliseconds based on system uptime(Systemclock.uptimeMillis()).
sendMessageDelayed(): puts the message on the queue after a delay, expressed in milliseconds.
sendEmptyMessage(): sends an empty message object to the queue, allowing you to skip the obtainMessage() step.
To process these messages, Handler needs to implement handleMessage(), which will be called with each message that appears on the message queue. There the Handler can update the UI as needed.
Note: sendMessage() can be called in any threads but the handler code (i.e handleMessage()) will always be running in the Thread which the Handler’s Looper resides in.
Now to understand the working of Thread Looper Handler when the task in the MessageQueue will be a message look at the below diagram:
- The main thread has message queue which consists of lot’s of messages and a looper which loops through the message queue.
- The handler is capable of two things that can handle messages that come from the looper and can also send messages from a different thread.
- Thread1 let’s say want to send message to the main thread indicating to update the progress bar so what you do is you get the handler reference and you call sendMessage of the Handler and that message goes inside the message queue of the main thread.
- The looper is going to run one message at a time and ultimately when it comes to your message the looper is going to open the message up and is going to forward the message to the handler.
- Now the handler object is going to have this method called handleMessage inside which whatever code you have written is going to be executed in the main thread.
- Each thread can have its own looper and its own handler, for now, the main thread is the one who has its own looper that’s gonna take one message at a time and its own handler who’s gonna pick one message up and run it. Other thread can use a reference to this handler to send messages to the main thread.
Runnable
Runnable is an interface which contains one single method run() with no arguments. The post versions methods of Handler class accept the Runnable objects to be added to the message queue and the code written inside the run method will be executed by the thread to which this handler is attached.
How to send Runnable objects?
post(Runnable r): Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.
postAtTime(Runnable r, long uptimeMillis): Causes the Runnable r to be added to the message queue, to be run at a specific time given by uptimeMillis.
postDelayed(Runnable r, long delayMillis): Causes the Runnable r to be added to the message queue, to be run after the specified amount of time elapses.
In the above Thread Looper Handler diagram when you want to send a Runnable object from the background thread (Thread 1) to the main thread using the post method of the handler then it gets added in the message queue of the main thread and then it gets executed inside the thread to which handler is attached (the UI thread).
Handler Example
Now let’s take an example in which we are downloading an image over the network in an imageView on button click. In the below example we are communicating with handler using both message and Runnable object.
MainActivity.java
package trendlife.handlerdemo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class MainActivity extends AppCompatActivity { ImageView imageView; Handler handler; ProgressBar progressBar; TextView tv_loading_image; Boolean progressStatus = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = findViewById(R.id.image); tv_loading_image = findViewById(R.id.tv_loading); progressBar = findViewById(R.id.progressbar); //As we have created handler inside the UI thread so this handler get attached with the Looper,message queue of the UI thread. handler = new Handler() { //the code inside the handleMessage() run inside the main thread and handler execute the code and then update the UI. @Override public void handleMessage(Message msg) { progressStatus = false; Object obj = msg.getData().getParcelable("MyObject"); imageView.setImageBitmap((Bitmap) obj); imageView.setVisibility(View.VISIBLE); Toast.makeText(getApplicationContext(), "Image downloaded", Toast.LENGTH_SHORT).show(); } }; } //this method is called on DownloadImage button click public void downloadImage(View view) { Log.i("Button", "Tapped"); progressStatus = true; //background thread started DownloadImage downloadImage = new DownloadImage(); new Thread(downloadImage).start(); } class DownloadImage implements Runnable { @Override public void run() { URL url; HttpURLConnection httpURLConnection; Bitmap result = null; //communicating with handler using Runnable object handler.post(new Runnable() { @Override public void run() { if (progressStatus = true) { tv_loading_image.setText("Loading Image..."); tv_loading_image.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE); } } }); //getting image bitmap try { url = new URL("https://vignette.wikia.nocookie.net/disney/images/0/0a/ElsaPose.png/revision/latest?cb=20170221004839"); httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.connect(); InputStream in = httpURLConnection.getInputStream(); result = BitmapFactory.decodeStream(in); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //communicating with handler using Message object Message message = handler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putParcelable("MyObject", result); message.setData(bundle); handler.sendMessage(message); } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tv_loading" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:text="Loading Image..." android:textColor="@color/colorAccent" android:textSize="20sp" android:textStyle="bold" android:visibility="invisible"/> <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginTop="10dp" android:visibility="invisible" android:gravity="center" android:layout_below="@+id/tv_loading"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/image" android:layout_width="350dp" android:layout_height="350dp" android:background="@drawable/border_image" android:visibility="invisible" /> <Button android:id="@+id/btn_download" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@android:color/holo_blue_bright" android:onClick="downloadImage" android:padding="10dp" android:text="download image" android:textColor="@android:color/black" android:textStyle="bold" /> </LinearLayout> </RelativeLayout>
When you run the application HandlerDemo it will look like this as shown below:
I hope this article will help you in understanding what is Handler, why we use it, it’s working and its example.