Android thread communication – Handler used

An Android application is a multithreaded application. Android has a main thread, and any code you execute runs in the main thread without a specified thread. Generally speaking, Android View drawing is done in the main thread, so if we do time-consuming operations in the main thread, the thread is not responding, which will cause UI drawing and other things that the user sees and performs to get stuck, giving the user a bad experience. So Android specifies ANR (Application Not Responding: Application Not Responding).

ANR specifies that when there is no response in the Activity for 5s, in the BroadcastReceiver for 10s, and in the Service for 20s (foreground) and 200s (background), users are told to continue to wait or close the application. So we want to avoid ANR as much as possible. Of course, a comfortable application, even if there is no provision of ANR, we should not be in the main thread time-consuming operation to bring bad experience to the user.

So when we use multithreading, we inevitably encounter thread communication. The Android API gives us many ways to communicate with threads. Let’s first look at the use of Handler.

Handler is introduced

Handler is an API used for intra-process and inter-thread communication in the Android OS package. Handler is commonly used for Hander, Message, MessageQueue, and Looper.

Handler is mainly used to process asynchronous messages. When a Message is sent by Hander, the Message will enter a Message queue MessageQueue, and Looper will read the Message in MessageQueue in a loop. And passes the fetched Message to Handler for processing.

This article will only share the usage of the Handler. We will learn the details in the next article.

Handler to use

When we use Handler, we only use Handler and Message. We use Handler sendMessage() to send messages and override handleMessage() to handle the received messages.

Before we look at what the Handler sends and receives, let’s look at what Message the Handler sends. Message is a Message and the vehicle for communication in our Handler. Message has several public fields that define and describe messages for arbitrary data objects.

Message contains two int fields, arg1 and arg2, and an Object field: Obj, Message also has a WAHT field of type int as Message code to distinguish what the Message is from or where it came from. The constructor of Messsage is public, but message.obtain () is officially recommended.

// Not recommended
Message msg1 = new Message();
// This method returns a Message from the global pool, avoiding duplicate object creation
Message msg2 = Message.obtain();
Copy the code

The obtain() method in Message is overloaded to support passing in handler, what, arg1, arg2, obj, and so on.

With a brief introduction to Message, we are ready to look at Handler usage, but first we need to look at how to create a Handler.

Handler has seven constructors, which you can see in the Handler class, but I’m not going to post them here, and I’m going to talk about what these seven constructors do. The seven constructors mainly choose to pass in three arguments: looper: looper, callback: callback, async: Boolean. What are these three parameters mainly for? CallBack is an interface that has only one handleMessage(), similar to the handleMessage() method in the Handler. Used to process Messsage. Handler’s handleMessage() and Callback’s handleMessage() methods are called together in the Handler source code. What does Async do? As the name suggests, for thread synchronization, if async is true, then Message insertion into MessageQueue is asynchronous. However, all async constructors apply the hide annotation (hide: this Api is not public), so we normally only have four constructors available.

private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            / / the Message processing
            
            return false; }};1 / / way
private Handler handler = new Handler(Looper.getMainLooper(),callback);
2 / / way
private Handler handler2 = new Handler(callback);
Copy the code

A Handler must hold a Looper. A Handler without a Looper will throw an exception when constructed or used, and you’ll be surprised. So what’s the point of method 2? In fact, when you call a constructor without Looper, the Handler will grab the Looper of the current thread. The Android main thread will create the Looper itself at the start of the process, but if you use mode 2 of the child thread, it will throw an exception. So mode 2 is not recommended.

So how do we generate Looper?

    // Get the main thread Looper
    private Looper mainLooper = Looper.getMainLooper();

    // Only calls are required in the child thread
    Looper.prepare();
Copy the code

There are a number of ways to send a Message to a Handler, so let’s look at the simplest one:

    private void sendMessage(int arg1, int arg2, Object obj){
        
        Message message = Message.obtain();
        message.arg1 = arg1;
        message.arg2 = arg2;
        message.obj = obj;
        // Handler sends the message
        handler1.sendMessage(message);

    }
Copy the code

If we use handlers for thread communication, sendMessage() and handleMessage() can be handled in separate threads. So what about Message? We can use either the handleMessage() of CallBack or the handleMessage() of Handler:

    private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            / / the Message processing

            return false; }};private Handler handler1 = new Handler(Looper.getMainLooper(),callback){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            / / MSG processing}};Copy the code

Callback handleMessage() returns a Boolean value. If it returns true, the event will be intercepted. If false is returned, the Callback is passed to Handler.

Let’s look at a concrete example:

Case: Download a picture from the Internet, display the picture successfully, display the toast message failed:


    private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            / / the Message processing

            return false; }};private Handler handler1 = new Handler(Looper.getMainLooper(),callback){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            / / MSG processing
            
            if(msg ! =null && msg.obj instanceof Bitmap){
                Bitmap bitmap = (Bitmap) msg.obj;
                imageView.setImageBitmap(bitmap);
            }else {
                Toast.makeText(context,"Fail",Toast.LENGTH_SHORT).show(); }}};private void getImage(String url){

        Thread thread = new Thread(){
            @Override
            public void run(a) {
                super.run();
                // Download the image
                Bitmap bitmap = DownloadUtil.download(url);
                Message msg = Message.obtain();
                
                if(bitmap ! =null){
                    msg.obj = bitmap;
                }else {
                    msg.obj = null; } handler1.sendMessage(msg); }}; thread.start(); }Copy the code

Above is a simple communication between a child thread and the main thread. There are many other ways that the Handler can send messages.

// sendMessage 使用的是 sendMessageDelayed()
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

// sendMessageDelayed() 使用的是 sendMessageAtTime()
// This method means that the Message must be processed after delayMillis
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // systemclock. uptimeMillis(); // Systemclock. uptimeMillis()
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// sendMessageAtTime() 使用的是 enqueueMessage()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
Copy the code

The Handler also has the POST () method, which is also called the send() method. The Runnable object passed in is assigned to the Message Callback:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
// getPostMessage() obtains () a Message and uses Runnable as the Callback to the Message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
Copy the code

So what does Message’s Callback do? As mentioned above, sendMessage() requires handler.callback or handleMessage() to process the message, while POST () does not require the above method to process the message. When the Looper loop retrieves the message from the MessageQueue, it runs the run method of the callback. This replaces the above handleMessage or handler. Callback code:

private Handler handler = new Handler(Looper.getMainLooper());
public void dealData(String msg){
    new Thread(new Runnable() {
        @Override
        public void run(a) {
            // The child thread uses Handler to post a message without start, so it does not start a new thread
            handler.post(new Runnable() {
                @Override
                public void run(a) { textView.setText(msg); }}); } }).start(); }Copy the code

There are many more POST methods that implement the same logic but call a different send() method.