Android's message processing mechanism (Looper, Handler, Message)

I. Introduction

Before formally analyzing the principle of the Handle mechanism, let's first understand the related terminology:

  1. Synchronous communication (Synchronous): Only when the client sends a request to the server, it must wait until the server returns a result before the client continues to send other requests.
  2. Asynchronous communication (Asynchronous): It means that after the client sends the request, it can send the next request without waiting for the response from the server.

The so-called synchronous call means that when a function or method is called, the call does not return until the result is returned. Asynchronous call and synchronization are relative. After an asynchronous call is initiated, the callee immediately returns to the caller, but the caller cannot get the result immediately. The callees are all processed through the status, notification, or notification after the actual processing of the call request is completed. Callback and other methods to notify the caller of the result of the request processing.

Android's message processing has three core classes: Looper , Handler and Message . In fact, there is also a Message Queue (message queue), but MQ is encapsulated in Looper, we will not directly deal with MQ, so it is not a core class.

Two core class

1. Message class: Message class

The main function of android.os.Message is to encapsulate the message. At the same time, you can specify the operation form of the message. The variables and common methods defined by the Message class are as follows:

(1) public int what: variable, used to define what kind of operation this Message belongs to

(2) public Object obj: variable, used to define the information data passed by this Message, and pass information through it

(3) public int arg1: variable, used when passing some integer data

(4) public int arg2: variable, used when passing some integer data

(5) public Handler getTarget(): common method to obtain the Handler object that operates this message.

In the entire message processing mechanism, message is also called task, which encapsulates the information carried by the task and the handler that processes the task. The usage of message is relatively simple, but there are a few points to note:

(1) Although Message has a public default construction method, you should obtain an empty message object from the message pool through Message.obtain() to save resources.

(2) If your message only needs to carry simple int information, please use Message.arg1 and Message.arg2 to transmit information first, which saves more memory than using Bundle

(3) Use message.what to identify information, so that the message can be processed in different ways.

(4) Use setData() to store Bundle objects.

2. Message channel: Looper

When using Handler to process Message, Looper (channel) is needed to complete. In an Activity, the system will automatically start the Looper object for the user, and in a user-defined class, the user needs to manually call the method in the Looper class, and then the Looper object can be started normally. Looper literally means "looper", and it is designed to turn a normal thread into a Looper thread. The so-called Looper thread is a thread that works in a loop. In program development (especially in GUI development), we often need a thread to loop continuously. Once there is a new task, it will be executed, and after execution, it will continue to wait for the next task. This is the Looper thread. Using the Looper class to create Looper threads is very simple:

public class LooperThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        // 将当前线程初始化为Looper线程
        Looper.prepare();
         
        // ...其他处理,如实例化handler
         
        // 开始循环处理消息队列
        Looper.loop();
    }
}

Through the above two lines of core code, your thread is upgraded to Looper thread! So what do these two lines of code do? Let's take a look at the Looper class first:

  1. Looper.prepare();
public class Looper {
    
    
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    //其他属性
    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
    
    
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }
    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
    
    
        if (sThreadLocal.get() != null) {
    
    
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

The working method behind prepare() is clear at a glance, and its core is to define the looper object as a ThreadLocal. If you don’t understand the role of ThreadLocal, you can refer to this article: ThreadLocal principle analysis and usage scenarios.
In fact, in simple terms, ThreadLocal, as the name suggests, is thread exclusive, which means that it can only be accessed by this thread, that is, threads are isolated. It is shared in methods and classes. The implementation principle is the internal class ThreadLocalMap in ThreadLocal. The data structure inside is the Map collection. Looper can realize thread exclusiveness. In fact, it is the set method in ThreadLocalMap, which is stored in the Map collection by using the local thread as the Key and Looper as the value. , If you want to get this Looper, you can only get it through a local thread.

  1. Looper.loop(): Get the message in MQ in a loop and send it to the corresponding Handler object.
    Insert picture description here
    After calling the loop method, the Looper thread starts to work. It constantly takes out the message (also called task) of the head of the queue from its own MQ for execution. The source code analysis is as follows
  public static final void loop() {
    
    
        Looper me = myLooper();  //得到当前线程Looper
        MessageQueue queue = me.mQueue;  //得到当前looper的MQ
         
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        // 开始循环,死循环,这里也说明了Handle机制是一个不断通过Looper从队列里获取消息的一个过程
        while (true) {
    
    
            Message msg = queue.next(); // 取出message
            if (msg != null) {
    
    
                if (msg.target == null) {
    
    
                    // message没有target为结束信号,退出循环  
                    return;
                }
                // 日志
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
                msg.target.dispatchMessage(msg);
                // 日志
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                 
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
    
    
                    Log.wtf("Looper", "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);
                }
                // 回收message资源
                msg.recycle();
            }
        }
    }

In addition to the prepare() and loop() methods, the Looper class also provides some useful methods, such as Looper.myLooper() to get the current thread looper object:

  1. myLooper(): Get the looper object of the current thread:
  public static final Looper myLooper() {
    
    
        // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper
        return (Looper)sThreadLocal.get();
    }
  1. getThread(): Get the thread to which the looper object belongs:
public Thread getThread() {
    
    
    return mThread;
}
  1. The quit() method ends the looper loop
public void quit() {
    
    
    // 创建一个空的message,它的target为NULL,表示结束循环消息
    Message msg = Message.obtain();
    // 发出消息
    mQueue.enqueueMessage(msg, 0);
}

3. Message operation class: Handler class

The Message object encapsulates all messages, and the operation of these messages requires the android.os.Handler class to complete. What is a handler? The handler plays the role of processing messages on MQ (only processing messages sent by itself), that is, notifying MQ that it will perform a task (sendMessage), and execute the task (handleMessage) when the loop reaches itself, the whole process is asynchronous of. When the handler is created, a looper will be associated. The default construction method will be associated with the looper of the current thread, but this can also be set. The default construction method:

  public class handler {
    
    
    final MessageQueue mQueue;  // 关联的MQ
    final Looper mLooper;  // 关联的looper
    final Callback mCallback; 
    // 其他属性
    public Handler() {
    
    
        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());
            }
        }
        // 默认将关联当前线程的looper
        mLooper = Looper.myLooper();
        // looper不能为空,即该默认的构造方法只能在looper线程中使用
        if (mLooper == null) {
    
    
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
     
    // 其他方法
}

Below we can add Handler to the previous LooperThread class:

public class LooperThread extends Thread {
    
    
    private Handler handler1;
    private Handler handler2;
 
    @Override
    public void run() {
    
    
        // 将当前线程初始化为Looper线程
        Looper.prepare();
         
        // 实例化两个handler
        handler1 = new Handler();
        handler2 = new Handler();
         
        // 开始循环处理消息队列
        Looper.loop();
    }
}

The effect after adding the handler is as follows: As
Insert picture description here
you can see, a thread can have multiple Handlers, but there can only be one Looper!

Handler sends a message :

With the handler, we can use

  • post(Runnable)

  • postAtTime(Runnable, long)

  • postDelayed(Runnable, long)

  • sendEmptyMessage (int)

  • sendMessage(Message)

  • sendMessageAtTime(Message, long)

  • sendMessageDelayed(Message, long)

    These methods send messages to MQ. Just looking at these APIs, you may think that the handler can send two kinds of messages, one is Runnable object, the other is message object. This is an intuitive understanding, but in fact, the Runnable object sent by post is finally encapsulated into message object, see Source code:
    // This method is used to send a Runnable object to the associated MQ, and its run method will be executed in the looper thread associated with the handler

public final boolean post(Runnable r)
{
    
    
   // 注意getPostMessage(r)将runnable封装成message
   return  sendMessageDelayed(getPostMessage(r), 0);
}
 
private final Message getPostMessage(Runnable r) {
    
    
    Message m = Message.obtain();  //得到空的message
    m.callback = r;  //将runnable设为message的callback,
    return m;
}
 
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
    
    
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
    
    
        msg.target = this;  // message的target必须设为该handler!
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else {
    
    
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

The message sent by the handler has the following characteristics:

  • .message.target is the handler object, which ensures that when looper executes the message, it can find the handler that processes it, that is, the key code in the loop() method
  • msg.target.dispatchMessage(msg);
    2. For the message sent by post, its callback is a Runnable object

Handler handles the message :
After finishing sending the message, let's look at how the handler handles the message. The processing of the message is through the core method dispatchMessage (Message msg) and the hook method handleMessage (Message msg)

Completed, see source code

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

  private static void handleCallback(Message message) {
    
    
        message.callback.run();
    }

It can be seen that, except for the handleMessage (Message msg) and Runnable object's run method implemented by the developer (implementing specific logic), the internal working mechanism of the handler is transparent to the developer. Handler has the following two important features:

  1. The handler can send messages in any thread, and these messages will be added to the associated MQ

Insert picture description here

  1. The processing of the message is completed by the core method dispatchMessage (Message msg) and the hook method handleMessage (Message
    msg). The handler processes the message in its associated looper thread.
    Insert picture description here
    This solves the most classic Android problem that the UI cannot be updated in other non-main threads. Android's main thread is also a looper thread (looper is widely used in Android), and the handler we create in it will be associated with the main thread MQ by default. Therefore, a solution using the handler is to create the handler in the activity and pass its reference to the worker thread, and the worker thread uses the handler to send a message to notify the activity to update the UI after performing the task. (The process is shown in the figure)
    Insert picture description here

Guess you like

Origin blog.csdn.net/qq_39431405/article/details/113716185