Android development: Handler asynchronous communication mechanism fully parsed (including Looper, Message Queue)

Keywords: Android Attribute PHP xml

Catalog


Handler.png

Definition

Android provides a set of messaging mechanisms

Effect

Used for updating UI threads by sub-threads and processing asynchronous messages:

  • Send messages in newly started threads
  • Get and process information in the main thread

Why use Handler

In Android development:

  • To ensure that Android's UI operations are thread-safe, Android specifies that only UI threads are allowed to modify UI components in Activity.
  • However, in practical development, multiple threads will inevitably be used to operate UI components concurrently, which will lead to thread insecurity of UI operations.

So the question is: how to satisfy at the same time:

  • Ensuring Thread Safety
  • Make multiple threads operate UI components concurrently

Handler messaging mechanism is the problem.

Related concepts

Main thread (UI thread)

  • Definition: When the program starts for the first time, Android starts a Main Thread at the same time.
  • Role: The main thread is mainly responsible for handling UI-related events, so the main thread is also called UI thread.

    Subthreads are responsible for some time-consuming operations (networking, data fetching, SD card data loading, etc.), while the communication between the main thread and the sub-threads depends on Handler.

Message

  • Definition: Messages, understood as data units for inter-thread communication (message objects accepted and processed by Handler).

    For example, if the background thread needs to update the UI after processing the data, it can send a Message containing the update information to the UI thread.

Message Queue

  • Definition: message queue
  • Function: Used to store messages sent through Handler and execute according to FIFO

Handler

  • Definition: Handler is the primary handler of Message
  • Role: Responsible for adding Messages to message queues & handling Message dispatched by Looper

Looper

  • Definition: Circulator, acting as a bridge between Message Queue and Handler

  • Role: Mainly responsible for message loops: looping out Message Queue Message Sage; message dispatch: delivering the extracted message to the corresponding Handler

    1. There can only be one Looper per thread, but a Looper can be bound to multiple threads of Handler, which means that many threads can send messages to a MessageQueue held by a Looper. This gives us the possibility of communication between threads.
    2. Handler can display the specified Looper when it is created, so that when the Handler calls sendMessage() to deliver the message, it adds the message to the MessageQueue in the specified Looper. If Looper is not specified, the default binding for Handler is the Looper of the thread that created it.

Handler asynchronous communication mechanism workflow diagram


Handler asynchronous communication transfer mechanism flow chart

Handler, Looper, MessageQueue Relational Class Diagrams


Handler, Looper, MessageQueue Relational Class Diagram. png

Handler

  • Provide sendMessage method to place messages in queues
  • Provide handleMessage method, define a variety of message processing methods;

Looper

  • Looper.prepare(): instantiates the Looper object; generates a message queue for the current thread;
  • Looper.loop(): The loop takes messages from the Message queue and handles them to Handler; at this point, the thread is in an infinite loop, constantly getting messages from MessageQueue; if no messages are blocked

MessageQueue

  • enqueueMessage method is provided to place messages in queues according to time.
  • next method is provided to retrieve messages from the queue and block them when no messages are available.

Handler workflow interpretation
The steps of asynchronous communication transmission mechanism mainly include preparation of asynchronous communication, message sending, message circulation and message processing.

  1. Preparation for asynchronous communication
    Including Looper object creation & instantiation, MessageQueue queue creation and Handler instantiation
  2. message sending
    Handler sends messages to message queues
  3. Message loop
    Looper executes Looper.loop() and enters the message loop, during which messages are constantly fetched from the Message Queue and dispatched to the Handler who created the message.
  4. Message processing
    Call the dispatchMessage(msg) method of the Handler, which calls back handleMessage(msg) to process messages

It seems very complicated? Let's look at this sketch first.


page3.png

Detailed explanation of Workflow

As can be seen from the above, the whole asynchronous messaging mechanism mainly includes Handler, Looper and MesageQueue, and then parses these three parts through the corresponding source code.

Part I: Looper

Looper is mainly responsible for:

  1. Create Your Own-Create Message Queue
  2. Message Loop (Message Extraction, Distribution)

For our responsibilities, let's look at the source code.

  1. Create your own & Create Message Queue: prepare() method
    public static final void prepare() {
    //Determine whether sThreadLocal is null or throw an exception
    //That is, the Looper.prepare() method cannot be called twice
    //That is to say, a thread can only correspond to one Looper instance.
         if (sThreadLocal.get() != null) {
             throw new RuntimeException("Only one Looper may be created per thread");
         }
    //sThreadLocal is a ThreadLocal object used to store variables in a thread
    //Instantiate Looper objects and store them in ThreadLocal
    //This means that Looper is stored in Thread threads
         sThreadLocal.set(new Looper(true));
    }
    //Let's look at Looper's construction method.
    private Looper(boolean quitAllowed) {
    //Create a MessageQueue (message queue)
    //This means that when a Looper instance is created, a matching MessageQueue (message queue) is automatically created.
         mQueue = new MessageQueue(quitAllowed);
         mRun = true;
         mThread = Thread.currentThread();
    }

2. Message loop: loop() method

public static void loop() {
//The myLooper() method returns a Looper instance stored in sThreadLocal and throws an exception if me is null and loop().
//That is to say, the loop method must be executed after the prepare method
//That is, the message loop must first create Looper instances in the thread
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
//Get mQueue (message queue) in the looper instance
        final MessageQueue queue = me.mQueue;


        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
//Enter the message loop
        for (;;) {
//next() method is used to fetch messages from message queues
//If the message is empty, the thread is blocked
            Message msg = queue.next(); // might block
            if (msg == null) {

                return;
            }


            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

//Message dispatch: Send messages to the target attribute of msg, and then process them with the dispatchMessage method
//Msg's target is actually a handler object, which we will continue to analyze below.
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
//Release the resources occupied by messages
            msg.recycle();
        }
}

Summarize the role of Looper:

  1. Instantiate itself, bind to the current thread, and create the corresponding MessageQueue: prepare() method

    A thread has only one Looper instance, and a Looper instance has only one MessageQueue.

  2. Message loop (message extraction, message dispatch): loop() method
    Continuously cancel the message from MessageQueue, distribute the message to the Hadler of the target attribute, and then call the dispatchMessage() method of the corresponding Handler to process the message.

Part II: Handler

Mainly responsible for:

  1. Send a message to MessageQueue in a subthread
  2. Handling messages sent by Looper

Before using Handler, a Handler instance is initialized

Handler needs to be bound to threads. When initializing Handler, the corresponding threads are usually bound by specifying Looper objects. That is to say, specifying Looper objects = to Handler is bound to the thread where the Looper objects are located. Handler's message processing callbacks are executed in that thread. There are generally two ways to create:

  1. Loop.myLooper() gets the Loper object of the current thread / Loop.getMainLooper() gets the Loper object of the main thread of the current process.
  2. Without specifying the Looper object, the Handler is bound to the thread that created the thread, and the message processing callback is executed in the thread that created it.

First, look at how Handler is constructed

public Handler() {
        this(null, false);
}
public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
//The Looper instance saved by the current thread is retrieved through Looper.myLooper(), and an exception is thrown if the thread does not have a Looper instance.
//This means that it is impossible to create a Handler object in a thread that does not create Looper.
//So when we create a Handler in a sub-thread, we first need to create a Looper and open a message loop to use the Handler.
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
//Get the Message Queue saved in this Looper instance
//This ensures that the handler instance is associated with MessageQueue in our Looper instance

        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • Note above: When Handler is initialized, Looper and corresponding MessageQueue can be automatically correlated by construction method

1. Handler sends messages to MessageQueue: Handler can be sent in two ways: post and send.

send sending method: sendMessage()

   public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

//Let's go down.
   public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }


 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//Get MessageQueue directly
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
//Called the enqueueMessage method
        return enqueueMessage(queue, msg, uptimeMillis);
    }

//Calling the sendMessage method actually ends with calling the enqueueMessage method
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//Assign this to msg.target, which means that the current handler is the target attribute of MSG
//If you remember that Looper's loop() method takes out each MSG and executes msg.target.dispatchMessage(msg) to process the message, it is actually dispatched to the corresponding Handler.
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
//Eventually, the queue's enqueueMessage method, that is, the message sent by handler, will be saved to the message queue.
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Post sending method: sendMessage()

  showhandler.post(new Runnable() {
                @Override
                public void run() {
                    String line = "\n";
                    StringBuffer text = new StringBuffer(show.getText());
                            text.append(line).append("angelababy:Yes,I do");
                            show.setText(text);
                }
  • Compared with send method, the biggest difference of post method is that updating UI operation can directly override the definition of run method.
  • In fact, Runnable did not create any threads, but sent a message. Look at the source code below.
 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

  private static Message getPostMessage(Runnable r) {
//Create a Message object
//Message objects can be created either by new or by using the Message. get () method.
//However, the obtain method is more recommended because a Message pool is maintained internally for message reuse, avoiding new reallocation of memory.
        Message m = Message.obtain();
//The unable object we created is assigned to this message as the callback attribute.
        m.callback = r;
//Create a Message object
        return m;
    }

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

 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);
    }
  • Did you find the source code above? It's the same as handler.sendMessage in send.

    sendMessageAtTime is invoked, and then the enqueueMessage method is invoked to assign a handler to msg.target, which eventually adds the message to MessagQueue.

  • But careful you will find that when using the Post method, we assign the unable object we created to this message as a callback attribute.
    So the callback and target of msg have values, so which one will be executed?
    We know that the way to send a message back is dispatchMessage()

    public void dispatchMessage(Message msg) {
    //Judgment begins with judgment.
    //If the msg.callback attribute is not null, the callback callback is executed, which is our Runnable object.
          if (msg.callback != null) {
              handleCallback(msg);
          } else {
              if (mCallback != null) {
                  if (mCallback.handleMessage(msg)) {
                      return;
                  }
              }
              handleMessage(msg);
          }
      }

2. Processing messages sent by Looper: dispathMessage() method

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    public void handleMessage(Message msg) {
    }
  • You can see that the handleMessage() method is called in the dispathMessage() method, but handleMessage() is an empty method.
  • Because Handler sends messages in the hope of processing, and how to process messages is the ultimate control of the handler, we need to copy handleMessage() method to achieve the message processing we need when creating handler, and then process messages according to the msg.what identity.

That's why we need to rewrite handleMessage() when we instantiate Handler in the main thread.

Particular attention

When an Android application starts, it creates a main thread, ActivityThread (also called UI thread), which has a static main method: the entry point of the application.

//A process generates a main thread by default
public static void main(String[] args) {
 ......
//When the main thread is generated, a Looper is automatically created for the main thread through the prepareMainLooper method.
//The prepare() method is used to create a Looper object in a subthread that can exit the message loop: the quit method that calls the message queue
//When Looper is generated, the matching message queue is automatically generated.
Looper.prepareMainLooper(); 
ActivityThread thread = new ActivityThread(); 
thread.attach(false);
 ...... 
//The loop() method opens the message loop
//Message loops in the main thread are not allowed to exit
Looper.loop(); 
throw new RuntimeException("Main thread loop unexpectedly exited");
}

Part 3: MessageQueue

Message queue, which stores messages sent by Handler

In order to improve the efficiency of insertion and deletion, a single linked list is adopted.

For MessageQueue, let's look at the entry and exit operations

MessageQueue Entry

boolean enqueueMessage(Message msg, long when) {

    ......

    synchronized (this) {

        ......

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Entry (insertion) of messages

  • Firstly, it judges whether there is a message in the message queue, and if there is no message, it regards the current inserted message as the head of the queue, and then the message queue wakes up if it is in a waiting state.
  • If it is inserted in the middle, it is inserted according to the time when the Message was created.

MessageQueue Out

Message next() {

    ......

    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
// The nativePollOne method is at the native level, if nextPollTimeout Millis is - 1, then the message queue is in a waiting state.   
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
//Get the message at the time we set
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
// If there is no message in the message queue, set nextPoll Timeout Millis to - 1, and the next circular message queue is in a waiting state
                nextPollTimeoutMillis = -1;
            }


//When you exit the message queue and return null, the message loop in Looper will also terminate. 
            if (mQuitting) {
                dispose();
                return null;
            }

            ......
        }

        .....
    }
}

The corresponding relationship among Thread, Looper and Handler:

  • A Thread can have only one Looper and multiple Handler s
  • A Looper can bind multiple handlers;
  • A Handler can only bind one Looper;

Correspondence relation. png

Review of working principle diagram

Having explained all the principles of Handler's work, let's look at the schematic diagram at the beginning. You'll probably have a better understanding.


Handler asynchronous communication transfer mechanism flow chart


  1. Layout file:
    activity_main
<?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: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.example.carson_ho.handlerdemo.MainActivity">

    <TextView
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" />
</RelativeLayout>

2. 1 send method: MainActivity

package com.example.carson_ho.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    private TextView show;
    private Handler showhandler;

    @Override
    //Looper and corresponding MessageQueue are automatically created when the main thread is created, and Loop() is executed before entering the message loop.
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        show = (TextView) findViewById(R.id.show);
    //Instantiate Handler, where no Looper is specified, that is, to automatically bind the Loper and MessageQueue of the current thread (main thread)
        showhandler = new FHandler();
    //Starter threads
        new Thread_1().start();
        new Thread_2().start();

    }

    class FHandler extends Handler{
        //Decide how to update the UI by overwriting handlerMessage().
        @Override
        public void handleMessage(Message msg) {

            StringBuffer text = new StringBuffer();
            switch (msg.what) {
                case 1:
                    text.append("I love Carson_Ho");
                    show.setText(text);
                    break;
                case 2:
                    text.append("I hate Carson_Ho");
                    show.setText(text);
                    break;

            }
        }
    }

    class Thread_1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Define the message to be sent
            Message msg = Message.obtain();
            msg.what = 1;//Identification for messages
            msg.obj = "AA";//Used for message storage
            //Handler is passed in to the main thread and its MessageQueue sends the message
            showhandler.sendMessage(msg);
        }
    }

    class Thread_2 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message msg = Message.obtain();
            msg.what = 2;
            msg.obj = "BB";
            showhandler.sendMessage(msg);
        }
    }



}

2. 2 Post Method: MainActivity

package com.example.carson_ho.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    public TextView show;
    public Handler showhandler;

    @Override
    //Looper and corresponding MessageQueue are automatically created when the main thread is created, and Loop() is executed before entering the message loop.
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        show = (TextView) findViewById(R.id.AA);
        StringBuffer text = new StringBuffer();
        text.append("Carson_Ho:Do you love me?");
        show.setText(text);
    //Instantiate Handler, where no Looper is specified, that is, to automatically bind the Loper and MessageQueue of the current thread (main thread)
        showhandler = new Handler();
    //Starter threads
        new Thread_1().start();
        new Thread_2().start();

    }


    class Thread_1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            showhandler.post(new Runnable() {
                @Override
                public void run() {
                    String line = "\n";
                    StringBuffer text = new StringBuffer(show.getText());
                            text.append(line).append("angelababy:Yes,I do");
                            show.setText(text);
                }

                });
            }
        }



    class Thread_2 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            showhandler.post(new Runnable() {
                @Override
                public void run() {
                    String line = "\n";
                    StringBuffer text = new StringBuffer(show.getText());
                    text.append(line).append("Huang Xiaoming:what the fuck?");
                    show.setText(text);
                }

            });
        }
    }
}

Posted by warptwist on Thu, 20 Dec 2018 23:12:06 -0800