Overview

Every Android developer is familiar with the Android Handler mechanism at some point, so before I start, I thought it would be important to clarify the target audience for this article

Suitable for people:

1, Android development novice, is not very familiar with the Handler mechanism, want to understand how the Handler internal operation

2, other client developers, such as iOS, Windows, etc., want to know how to update the interface in Android system in non-UI thread

Not suitable for the crowd:

1. Middle and senior engineers who have a deep understanding of Handler mechanism, this article is a waste of time

2, feel the source code is too boring, want to find an article to look at, this article may not be very appropriate, because the article will not involve too much source code

One, foreword

Android Handler mechanism is an unavoidable obstacle for every Android developer to grow up. Understanding the Handler mechanism is very helpful for solving the problems encountered in development such as lag detection, ANR monitoring, and how APP components run

There have been many articles on the market explaining the Handler mechanism from various angles, including many in-depth good articles; This article does not explain the source code (mainly about other articles), from the Android Handler mechanism design ideas start to speak, from shallow to deep, take you step by step into the implementation of the internal principle of the Handler

Enjoy:

Second, Handler design background

The Android developer website introduces the View as follows:

Photo source:Android Developer

The entire view tree is single-threaded, and any method called on any view must always be on the UI thread. If you are working on another thread and want to update the state of the view from that thread, you should use a Handler

Not only Android, but also the iOS developer website has a similar tip for UIKit:

Photo source:Apple iOS Developer

Unless otherwise specified, UIKit classes can only be used from the main thread or main scheduling queue of your application. This restriction applies especially to classes derived from UIResponder or classes that involve manipulating the application’s user interface in any way.

Android and iOS are two operating systems with a Graphical User interface. Why do they both need to operate the page on the main thread?

1. Why is a GUI designed to be single-threaded?

When designing Java Swing, Former Sun VP Graham Hamilton wrote an article on why A GUI should be single-threaded: Multithreaded Toolkits: A Failed Dream?

A recent question was asked, “Should we make Swing libraries truly multithreaded?” Personally, I don’t think so. Here’s why.

Failed Dream

In computer science, certain ideas are “unattainable dreams,” to borrow Vernor Vinge’s term. It seems like a good idea at first glance, but people come back to it every once in a while, and it takes a lot of time. Usually in the research phase, things are going well and there are some interesting results that are almost ready to be applied to production scale. However, there are always some problems that cannot be solved. Once the problems here are solved, there are problems on the other side.

Multithreaded GUI toolkits, in my opinion, are such unattainable dreams. In a multithreaded environment, it seems obvious and straightforward that any thread can update the GUI state of buttons, Text fields, and so on. Why would it be so hard for any thread to update the GUI state by simply adding some locks? There may be some bugs in the implementation, but we can fix them, right? Unfortunately, it turns out not to be that simple.

Multithreaded GUIs have an uncanny tendency to constantly have deadlocks or race conditions. I first heard about this trend in the early 1980s from people working in Xerox PARC’s Cedar GUI library. These people are very smart and really understand multithreaded programming. Their GUI code has deadlocks from time to time, which is interesting in itself. This alone doesn’t mean anything, or it’s just a special case.

It’s just a pattern that’s been repeated over the years. People started with multithreading, and over time, they moved to the event queue model. “It’s better to let the event thread do the GUI work.”

. slightly

Why is it so hard?

We write code using abstractions and naturally lock each abstraction layer separately. So, unfortunately, we have the classic lock order nightmare: there are two different types of activities, in reverse order, trying to acquire the lock. So deadlocks are inevitable.

. slightly

Threaded toolkits: A failed dream? , the original page has been 404, you can click the following two links to view the original text

  • CSDN original copy
  • Multithreaded GUI Toolkit: An impossible dream?

In short, multithreading a UI can easily lead to, or extremely easily lead to, reverse locking and deadlock problems

We change the interface through user-level code, such as TextView.settext, in a top-down process:

Photo source:Why isn’t the GUI designed to be multithreaded

However, events initiated at the bottom of the system, such as keyboard events and click events, follow a bottom-up process:

Photo source:Why isn’t the GUI designed to be multithreaded

This is troublesome because to avoid deadlocks, each process must follow the same lock order, whereas in the GUI the two processes are completely opposite. If each layer has a lock, locking is a difficult task, whereas if each layer shares a lock, it is no different from a single thread.

To sum up, the current mainstream operating system with user interface (GUI), except DOS, almost all use UI single thread model, namely message queue mechanism

2. What is message queuing?

When it comes to message queues, the producer-consumer pattern naturally comes to mind. We have already introduced the producer-consumer pattern in the article “Behavior Patterns from the Perspective of Android source code (3). If you haven’t seen it yet, you can come back.

In a producer-consumer model, there are usually three roles

  • Message queue (single)

    Responsible for saving messages and providing access to messages

  • Producers (multiple)

    Responsible for producing messages and stuffing them into a shared message queue

  • Consumers (multiple)

    Is responsible for retrieving messages from the shared message queue and performing tasks corresponding to messages

In these three roles, the number of message queues is unique because producers and consumers fetch and drop messages from the same message queue, and the number of producers and consumers can be arbitrary

Going back to Android, most GUI frameworks use a single-threaded design, which requires that all UI operations occur in only one thread:

There is only one consumer thread, and the consumer thread is the UI thread

Now that we have a clear idea of the message queue mechanism, let’s summarize:

Android, Swing, iOS, and other GUI libraries use message queuing to handle messages for drawing interfaces, event responses, and so on

In this design, each task to be processed is encapsulated as a message to be added to a message queue

The message queue is thread-safe (the message queue itself is locked to ensure that messages are not lost in multithreaded contention), and any thread can add messages to the queue

However, only the main thread (the UI thread) fetches the message from it and executes the message’s response function, ensuring that only the main thread performs these operations

Section finish

Android message queue: Handler

In the first two sections, we explained why the Android system can only update the interface in the UI thread, and found that not only Android, but most other operating systems with GUI frameworks are designed with single-threaded message queues. Since these are single-threaded designs, the main thread must be notified if the child thread wants to update the UI

In Android, Handler is a set of threads that can send messages to the UI thread from any thread

As you can see from the figure, handlers act as message producers, and each Handler holds a reference to the shared MessageQueue

When callingHandler.sendMessage()When the method sends a message, the Handler saves the message to a shared message queue

Unlike ordinary message queuing mechanisms, There is no upper limit to the size of MessageQueue in Android, so you can theoretically send messages to the queue and exhaust the memory

In the Handler mechanism, the consumer of a message is a Looper and, more precisely, a callLooper.loop()Because Looper essentially just encapsulates operations on message queues,Looper.loop()Method is responsible for fetching messages from the shared message queue and sending them to Handler for execution

Unlike normal message queuing, the consumer of the message executes the message’s response function. However, in Android Handler mechanism, the consumer does not perform the message task itself, but takes out the message, and then redistributes the message to the sender for execution

In the whole Handler mechanism, the Handler is the producer of the message first and the executor of the message second

In addition to messaging, Android also adds some additional functionality to handlers, such as child threads submitting synchronization tasks, asynchronous messaging, and synchronization barriers

The purpose of this section is to understand the design background of the Handler. We will not cover other functions of the Handler here. We will examine the internal source code of the Handler in detail in the next article in the Handler series

Summary the

How to implement a set of Handler mechanism?

In the previous chapter, we introduced the Handler design background. This chapter will try to implement a simple Handler mechanism on its own

Assuming that the reader already has some development experience and has worked with handlers, it’s going to be boring code time

1. Introduction to Handler members

Before we begin, we will, as usual, introduce the members of the Handler mechanism and their common methods

1.1 Handler class

Handler class is the entry of application program development, in the message queue mechanism, plays the role of producer, but also shoulder the burden of message execution, commonly used methods are:

Method names instructions
The sendMessage () series Send a normal message, delay a message, and finally callqueue.enqueueMessage()Method stores a message to a message queue
Post (series) Commit plain/deferred Runnable, which is then wrapped as Message and calledsendMessage()Store to message queue
dispatchMessage(Message msg) Distribute messages and execute them firstmsg.callback(aka runnable), secondmCallback.handleMessage()And the lasthandleMessage()

1.2 which kind of

Looper plays the role of consumer in message queue mechanism and internally holds a shared message queue. Its essence is to encapsulate operations on the message queue. There are only two common methods:

Method names instructions
prepare() Creating a message queue
loop() Iterating through the message queue, continuously fetching messages from the message queue, waiting if the message queue is empty

1.3 MessageQueue class

The actual shared message queue provides the function of saving and retrieving messages. The bottom layer is realized by linked lists. The common method is as follows:

Method names instructions
next() To obtain a message, there are three scenarios: 1. There is a message, and the message can be executed. 2. 3. If no message is sent, the system enters the waiting state indefinitely until it is waked up

1.4 the Message class

The buffer pool size varies according to API. When API 4 is used, the buffer pool size is 10.

Method names instructions
Obtain (series) Gets a message instance
recycle() Reclaim message instance

1.5 summary

ThreadLocal (ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal) ¶

In my opinion, ThreadLocal is part of the Java concurrency module. The Handler only uses ThreadLocal to ensure the uniqueness of MessageQueue in the current thread

Section finish

2. Implement Handler mechanism based on Object.wait()/notifiy()

Section 3.1 introduces Handler’s member classes and common methods. This section uses Java synchronization to implement the Handler

Don’t say a word. Just masturbate

2.1 Handwritten message queue mechanism

We’ve looked at the concept of message queuing in the previous section, so let’s go straight to the code

So first we create a producer, and in the producer thread, we write an infinite loop that keeps sending messages, to simulate user action; After each message, the consumer thread that might be in the wait state wakes up, and then places itself in the timed wait state

Then create a consumer, in the consumer thread, the same is an infinite loop, constantly polling message queue, there is a message to process, no message will put itself in an indefinite waiting state, until it is woken up

Next comes the shared message Queue, which uses the Java collection Queue. Here’s the final test code:

Print result:

In the test code, the two bosses, Zhang SAN and Li Si, sent instructions to subordinates from time to time. The two subordinates, Xiao Ming and Xiao Hong, kept polling and waiting for the boss’s instructions. A simple message queue mechanism was completed. Without considering delayed messages, and with the test code, the entire message queuing mechanism was relatively straightforward in less than 100 lines of code

2.2 Handwritten Handler Mechanism

We have implemented the message queue with Object.wait()/notifiy() in Section 2.1. To implement the Handler mechanism, we only need to modify the above message queue slightly:

Attention class, I’m about to transform!!

First, we move the logic from the consumer thread into Looper and the polling task into the loop() method. If a message is received, the Android Handler mechanism is simulated and the message is distributed to the producer (Handler) to execute it

Next, pull the logic from the consumer thread to get the message and put it in the Next () method of MessageQueue

When a message is sent, it is delayed to determine whether the message is due or not, and the expiration is returned to Looper for distribution (this is lazy to use PriorityQueue in Java package to ensure that the message with the smallest time is first).

Then, the logic from the producer thread to store the message directly into the message queue is pulled out and put into the Handler’s sendMessage() method

Then, take a look at the design of the Message class, adding a member variable of type Handle, target

Good! With that done, let’s take a look at the test code:

Created in the main() methodhandler1andhandler2, simulating the scenario where the user applies for Handler in the main thread; You then start both child threads and let the child thread code send messages using the Handler you just created, so that the messages sent by the child thread using this Handler are added to the main thread’s message queue and wait for the main thread’s Looper to process them

Print result:

See, if the task is normal, the loop() method will distribute it directly, and the delayed task will be released last because of the PriorityQueue

A simple Handler mechanism is now complete. Please button 1 in the comments section if you don’t understand

The code designed for this section is here

Ps: The code to the image because the source code is too long I know you are too lazy to read ~

2.3 summary

The first two sections respectively introduce how to write a message queue mechanism and write a simple Handler mechanism. I comb through the above code call process and summarize a simple version of the flow chart:

Each member class in the figure only writes two key methods. The Message class is just a Message carrier, so IT is not included. I think it is enough to introduce the general process of the Handler

Section finish

Handler mechanism details

This section design at the beginning of the idea is to analyze the Handler machine in detail internal source code, after writing two or three chapters to look back to find

With the exception of synchronous barriers and asynchronous messages, IdleHandler, Callback, etc., the entire Handler mechanism seems to be covered

Since we don’t have much else to talk about, this section will focus on what Android Handler provides in addition to message queuing, how it is implemented, and what we need to be aware of when using it

1. What other functions does Handler provide besides submitting messages to queues?

1.1 IdleHandler

IdleHandler is a mechanism implemented in the early days of the Handler mechanism. Its purpose is to submit an unimportant task to the mIdleHandlers variable in MessageQueu and execute it when the message queue is idle

    /** * Callback interface for discovering when a thread is going to block * waiting for more messages. */
    public static interface IdleHandler {
        /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */
        boolean queueIdle(a);
    }
Copy the code

IdleHandler requires a value of type bool, false to indicate that the task is removed from the collection after execution, and true to indicate that the task can be repeated

In the messagequyue.next () method, let’s look at the processing logic in Android 1.6:

// There was no message so we are going to wait... but first,
// if there are any idle handlers let them know.
boolean didIdle = false;
if(idlers ! =null) {
    for (Object idler : idlers) {
        boolean keep = false;
        try {
            didIdle = true;
            keep = ((IdleHandler)idler).queueIdle();
        } catch (Throwable t) {
        }
        if(! keep) {// Delete it from the collection if the result is false
            synchronized (this) { mIdleHandlers.remove(idler); }}}}Copy the code

IdleHandler (IdleHandler, IdleHandler, IdleHandler, IdleHandler)

Here are a few examples of tripartite libraries and official use of IdleHandler to see if you can take some inspiration from them

  • ActivityThread.GcIdler

    Look directly at the code

    final class GcIdler implements MessageQueue.IdleHandler {
            @Override
            public final boolean queueIdle(a) {
                doGcIfNeeded();
                return false; }}void doGcIfNeeded(a) {
            mGcIdlerScheduled = false;
            final long now = SystemClock.uptimeMillis();
            // Get the time of the last GC
            if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
                //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!" );
                BinderInternal.forceGc("bg"); }}Copy the code

    The doGcIfNeeded method is simple to understand. It simply takes the time of the last GC and determines whether a GC operation is required

  • Wechat performance monitoring framework: Matrix

    Matrix uses IdleHandler in several places, such as:

    IdleHandlerLagTracer.java

    WarmUpScheduler.java

    AndroidHeapDumper.java

    LooperMonitor.java

    For example, LooperMoitor is a class that listens to Looper message distribution. Wechat uses IdlerHandler to do a check

        @Override
        public boolean queueIdle(a) {
            if (SystemClock.uptimeMillis() - lastCheckPrinterTime >= CHECK_TIME) {
                resetPrinter();
                lastCheckPrinterTime = SystemClock.uptimeMillis();
            }
            return true;
        }
    Copy the code

    We see that the queueIdle method returns true, indicating that the IdleHandler will be called repeatedly. Each time queueIdle() is called, The resetPrinter method will be called to check whether the printer object in Looper is a wechat custom LooperPrinter

  • Memory Leak detection framework: LeakCanary

    LeakCanary has used IdleHandler in ReferenceCleaner, the source code looks like it registered IdleaHandler in onViewDetachedFromWindow(), The purpose of registering this IdleHandler is to remove Android IMS bugs

1.2 Asynchronous messages and synchronization barriers

Android added support for asynchronous and synchronous barrier messages in API 16(4.1.1)

The so-called synchronization barrier mechanism is to insert a synchronization barrier message into the queue head of Looper. Specifically, it is to remove a current time message to the queue. If there is a message in the queue that is due but has not been executed, the synchronization barrier message will be queued behind it. When Looper calls Next () to get messages and finds that the queue header is a synchronization barrier, it skips all synchronous messages and looks for all asynchronous messages to execute. Therefore, the asynchronous message mechanism is essentially a mechanism to rearrange the priorities of message queues

Handler, MessageQueue, Looper, etc

1.3 Handler Callback Mechanism

When the Handler sends a message, it provides the user with the option of priority distribution.

/** * Handle system messages here. */
public void dispatchMessage(Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code

Msg.callback (also known as runnable) is executed first, followed by McAllback.handlemessage (), and handleMessage() if callback returns false

2. Precautions for using Handler

2.1 Memory Leakage

The problem with memory leaks caused by handlers is that components with long lifespans refer to components with short lifespans, so that a component with short lifespans can’t be recycled

This Handler holds a reference to the Activity. If you use this Handler to send a delayed message and then destroy the Activity, you will find that the Activity, including its referenced objects, will not be released until the delayed message is executed

The solution is simple: declare a static inner class, as follows:

public static class InternalHandler extends Handler {
    
    private WeakReference<Context> contextReference;

    public InternalHandler(WeakReference<Context> contextReference) {
        this.contextReference = contextReference; }}Copy the code

2.2 Enjoy yuan mode pit

The Message class in Handler is designed using the share pattern. The share pattern itself can cause data inconsistencies, as explained in the article “From the Android source perspective design pattern (2) : Structural pattern”

Simply put, when you pass a share object to a child thread, because Java is value passing, when the child thread uses the share object passed in, it may be in the recycle pool, or it may have been taken out for use by another method

If looper.loop () calls msg.recycle() after the message is distributed, it will recycle the message instance object.

 public static class InternalHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 250) {// Create asynchronous thread processing logic
                new Thread(() -> {
                    Thread.sleep(100*1000);// The simulation takes time
                    System.out.println(msg.obj);// Simulate message usage}).start(); }}}Copy the code

As shown above, after we get the MSG, a new thread is created to process the message

To see which side of the logic, the loop () method calls inside the MSG. Target. DispatchMessage () method and then will recycle the message object instances

The object reference we get in the child thread is empty, or the object reference is already in use by another thread, or the message in the child thread is not the original message

3. What’s great about Handler

3.1 APP that never crashes

1. Use method

Before using Handler mechanism to intercept exceptions, I was a little popular on the Internet two years ago. I was very happy when I just learned that Handler could be used in this way, and the APP would not crash due to failure to catch exceptions. My performance was saved

Of course, it is an overstatement to say never crash, it can only intercept Java layer exceptions, native exceptions can not catch, next we understand how to implement

First, let’s take a look at the methods we use. It’s very simple. Let’s add the following code to any method in Application:

new Handler(Looper.getMainLooper()).post(() -> {
    while (true) {
        try {
            Looper.loop();
        } catch (Exception e) {
            // Save logs and report..
        }
}catch (Throwable){}// If you want to block Error(such as OOM), use Throwable}});Copy the code

As shown above, you can catch all exceptions in the Java layer with just a few lines of code. How?

2. Implementation principle

Before introducing the implementation principle, let’s review the Java exception handling mechanism. When an exception occurs:

  1. The virtual machine searches the exception table in the current exception method to see if there is an appropriate handler to handle the exception
  2. If the current method exception table is not empty and the exception matches the handler’s FROM and to nodes, and type matches, the virtual machine calls the caller at target to handle it
  3. If the previous entry does not find a reasonable handler, the rest of the exception table is searched
  4. If the exception table for the current method cannot be handled, look up (stack handling) to the call where the method was just called and repeat the above action
  5. If all stack frames are ejected and still unprocessed, they are thrown to the current Thread, and the Thread terminates
  6. If the Thread is the last non-daemon Thread and no exception is handled, the VM will stop running
Exception table:
       from    to  target type
           0     3     6   Class java/lang/Exception
Copy the code

Let’s do an experiment where the Activity actively throws an exception and look at the method call chain, focusing on where we can manually try and catch

Java. Lang. RuntimeException: I crashed at the android app. ActivityThread. PerformLaunchActivity (ActivityThread. Java:3639)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3796)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2214)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)//point 2
        at android.os.Looper.loop(Looper.java:288)//point 1
        at android.app.ActivityThread.main(ActivityThread.java:7842)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) under Caused by: Java. Lang. RuntimeException: I collapsedCopy the code

So let’s go from the bottom up, the bottom ones, what ZygoteInit, RuntimeInit, all these system classes sound unoperable

Until you see the looper.loop () method at point 1, refer back to the flow chart in Chapter 3, section 2.3. Loop () performs an infinite loop, polling the message queue

Looper.loop() is called to poll the message queue for a message. As long as the message is submitted correctly, the above exception stack will never appear

Also, since I called the looper.loop () method in the message, the code equivalent to the dispatch message executes in the message I submitted, which means I can catch any in-app task Java exception as long as I try catch where loop() was called

If looper.loop () is called repeatedly in MSG, let’s look at the method stack call chain:

// The original method call chain
ZygoteInit.main()
  -> RuntimeInit.MethodAndArgsCaller.run()
  	-> ActivityThread.main()
  		-> Looper.loop()
  			-> MessageQueue.next()
// After calling looper.loop () again, the chain becomes:
ZygoteInit.main()
-> RuntimeInit.MethodAndArgsCaller.run()
	-> Activity.main()
		-> LooperThread.loop() { // Because it is called in the same thread, it is equivalent to wrapping a layer on the looper. loop method
  		-> Looper.loop()
				-> MessageQueue.next()
		}
Copy the code
3. Summary

This section describes the implementation principles for making Java code never crash, which may seem like a pretty cool solution, but

Not recommended in production environment!!

When I just knew this program, I immediately tested it in the project, and the test result was the same as the introduced effect. I really felt very cool at that time, so I happily sent it to the production environment

A few days after the release of the feedback, the page white screen no data, click no response and so on

Later, I checked the reported log and found that there was indeed a bug in the faulty page, but the function could not be executed normally because it was blocked, which led to no response from users

Simply put, when a production data function fails, subsequent pages that rely on that data can cause a number of problems

This can lead to more uncontrollable factors in the app

Finally, after internal discussion, we gave up this scheme. We can’t treat bugs in such a rude way

GitHub has an open source library that provides a more complete solution: github.com/android-not…

Summary the

3.2 ANR monitoring

For details, please refer to the article of wechat client technology team:

  • ANR monitoring scheme of wechat Android client
  • Wechat Android client caton monitoring scheme

5. Development history of Handler

At the end of this article, we’ll take a look at the history of Handler

The following table charts the Handler’s evolution from Android 1.6 (API 3) to Android 12 (API 31) :

The Android 1.6 (API 4)

This is probably the oldest version of the Handler source you can find, and it is already quite complete.

Java object.wait ()/notfity()

Call MessageQueue’s next method to get the message, and check the queue for messages

  • No message callthis.wait()Wait indefinitely
  • Call with message but message not expiredthis.wait()Incoming expiration time

Call MessageQueue. EnqueueMessage () to add messages, after joining the queue is called this. Notifity () wake up the next () method, there is no judgment here is a bug when add message to perform, delay message will wake up

2. MessageQueue supports IdleHandler

IdleHandler is the internal class of MessageQueue. The addIdleHandler(handler) method is called to add an IdleHandler to the mIdleHandlers collection, which is executed when the MessageQueue is idle

3. Asynchronous and synchronous barrier messages are not supported

All messages are the same, which is one of the reasons the early Android device experience was so bad

4. Handler supports Callback Callback

This method is mentioned because it is important, so let’s take a look at the order in which messages are distributed:

Msg.callback (also known as runnable) is executed first, followed by McAllback.handlemessage (), and handleMessage().

The McAllback.handlemessage () method returns a value of type bool, which is true and is not distributed to Handler’s handleMessage() method, and false and continues

Once we know the distribution logic, we can hook class H from ActivityThread and pass in callback to intercept the information we want for componentation or monitoring

The Android 2.3 (API)

2.3 The big change is to add Handler message mechanism of native layer

1. MessageQueue supports processing of native layer messages

MessageQueue saves NativeMessageQueue objects through mPtr variables, thus making MessageQueue become the hub of Java layer and Native layer, and can process both upper-layer and Native layer messages

To learn more about the native layer Handler, see here:

  • Android Message Mechanism 2-Handler(Native Layer)
  • Native MessageQueue source: Android-2.3_R1 /core/jni/android_os_MessageQueue

2. In messagequeue.next (), the idle wait scheme is changed from Object.wait() to nativePollOnce()

Since the native layer Handler is added, MessageQueue of Java layer cannot be only judged when the queue is idle. MessageQueue changes the original this.wait() method to call native nativePollOnce() method. If everyone is idle, The method blocks in the native epoll_wait() method, waiting to wake up

3, MessageQueue. EnqueueMessage () method, wake up the solution from the Object. The notify () instead of nativeWake () implementation

Referring to the previous section, there is a small detail here. Prior to Android 2.3, whenever the enqueueMessage() method was called, this.notify() was called to wake up the thread, even if the message was delayed for 10,000 years. This problem was fixed in the enqueueMessage() method in version 2.3

The Android 4.0 (API) 14

1. Message adds flags to indicate whether the Message has already been consumed, preventing the same Message from being submitted indefinitely

The isInUse() method is called to check whether the current message has been used, and more meanings will be added later

Android 4.4.1 (API 16)

Version 4.1.1 has slightly larger changes, mainly with support for asynchronous and synchronous barrier messages

1. Message can be set to asynchronous messages with the @hide modifier

Call the setAsynchronous(true) method to set Message to asynchronous, and the asynchronous flag is stored in the flags member of Message

2. MessageQueue supports processing of asynchronous messages

The main thing is to add asynchronous message processing logic to the enqueueMessage() and next() methods

3. MessageQueue supports adding/deleting synchronization barrier messages

EnqueueSyncBarrier () and removeSyncBarrier()

Processing logic for synchronous barrier messages has also been added to MessageQueue’s next() method

4. MessageQueue supports quit()

The exit logic of version 4.1.1 is to set the member variable mQuiting of MessageQueue to true, check the value of mQuiting variable when calling messagequeue.next (), and return null to Lopper if it is true. The null value in the looper.loop () method terminates the loop directly

Note that messages in MessageQueue are not emptied, which means that if messages hold strong references from outside, they will leak

Lopper removes the mRun member variable

It is useless, I used it when printing MSG execution logs

The Android 4.2 (API) 17

Handler supports asynchronous Handler, @hide modifier

The Handler(Boolean async) constructor is added as follows. All messages sent using this Handler are asynchronous

public Handler(boolean async) {
	mAsynchronous = async;
}
Copy the code

2. Handler supports sub-threads to submit synchronization tasks. New runWithScissors() method and @hide modifier

The runWithScissors() method accepts a Runnable and a timeout and is called to submit a task:

1. If the message sending thread is the same thread as the Handler creating thread, execute Runnable’s run method

2. If the message sending thread and the Handler creating thread are not in the same thread, it can be interpreted that the child thread submits a task to the main thread. After the task is submitted, the child thread will sleep and wait to wake up until the task is finished

Attention!! Not only is this method decorated with @hide, but the code comment tells the developer that this is a dangerous method and is not recommended because the runWithScissors() method has two serious flaws:

1. The submitted task cannot be cancelled. Even if the sending thread of the message has died, the main thread will still pull out the task from the message queue to execute, but the program running at this time is not in line with our expectations

2, may cause a deadlock: the child thread to the main thread (create Handler thread) submitted a delay task, is the child thread is in a state of waiting to be awakened, if at this time the main thread out of the loop cycle and emptying the message queue, the child thread submitted will never be awakened task execution, this task is to hold the lock will never be released, cause a deadlock

The Android 4.3 (API)

1. MessageQueue supports safe exit, quit(safe)

Add the following two methods, safe parameter to true when the removeAllFutureMessagesLocked ()

  • RemoveAllMessagesLocked () : Clears all messages
  • RemoveAllFutureMessagesLocked () : emptying delay messages, message due to the Handler

The Android 6.0 (API 23)

1, MessageQueue support to monitor the file descriptor, corresponding methods: addOnFileDescriptorEventListener ()

This part of the code I do not understand, the current knowledge is not enough for me to understand ~ / cry

PostSyncBarrier () = postSyncBarrier() = postSyncBarrier()

I have seriously confirmed that the main logic has not been changed, really just a name change

26 the Android 8.0 (API)

Handler added the getMain() method to get the Handler instance running on the UI thread, and the @hide modifier

GetMain () checks whether the member variable MAIN_THREAD_HANDLER already holds Handler instances. If MAIN_THREAD_HANDLER is empty, looper.getManlooper () creates a new Hnadler instance. Assigns to the MAIN_THREAD_HANDLER variable and returns the result

Note that this method only returns a Handler running in the UI thread, not a member variable in the ActivityThread.

28 the Android 9.0 (API)

Handler added executeOrSendMessage(), @hide modifier

This method is relatively simple and provides the same functionality as the literal

If yes, call handler.dispatchMessage () to dispatch the message. If not, the message will be queued for dispatch

Handler Allows an APP to create asynchronous handlers

Added static method createAsync(), which returns an instance of a Handler that is an asynchronous Handler

The Android 10.0 (API) 29

1, Looper added setObserver(Observer) method, listen to the message distribution process, @hide modifier

There are three ways

  1. Object messageDispatchStarting() : called before sending messages
  2. Messagehealth (Token, MSG) : Called when the message is processed by the Handler
  3. DispatchingThrewException (token, MSG, exception) : in the process the message when an exception is thrown when called

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler

Ps: It is inevitable that there will be omissions in personal arrangement. Welcome to add them in the comment area

Six, summarized

This article breaks down the Handler mechanism into three parts

The first part introduces the background of the creation of Handler. Why does Android design Handler

The second part is about how to write a handler.wait ()/notifiy() method for Java synchronization.

Part 3 looks at what the Handler provides in addition to implementing message queues. And what you need to be aware of when using handlers in your development

I hope everyone has learned something after reading this article

The full text after

Vii. Reference materials

  • Csdn-liuqiaoyu080512: Why isn’t the GUI designed to be multithreaded