Android’s comprehensive analysis of handler message mechanism

Time:2021-6-12

preface

Nice to meet you. Welcome to read my article.

Blogs about handler can be found everywhere, and this is also a common topic. It can be seen that he is very basic and important. But a lot of blogs are rarely introduced from the beginning. When I started learning, I directly told me about looper and blocking, which is very difficult to understand. At the same time, there are few systematic explanations about handler, and the knowledge is relatively scattered. I hope to write oneFrom the beginning to the depth, the article of handler is explained systematically and comprehensivelyTo help you know handler.

The depth of this article is gradual. Readers of different programs can choose the corresponding parts to view:

  1. The first part is the introduction of handler. To understand a new thing, we need to ask three questions: what, why and how to use it. Including the structure of the handler are introduced.
  2. The second part is a detailed explanation and source code analysis of each class based on the cognition of handler.
  3. The third part is the overall process analysis and the analysis of common problems.
  4. The last part is Android’s explanation of message mechanism design and full text summary.

The article basically covers the knowledge about handler, so the length is relatively long
Considering that the article is divided into several small articles, and considering the integrity and convenience of reading, a large article is finally integrated
The article is a systematic, comprehensive explanation of knowledge points, rather than the fragmentation of knowledge, otherwise it is difficult to really understand the single knowledge, more difficult to grasp the overall knowledge
Readers can choose the chapters they are interested in

So, let’s get started.

summary

What is a handler?

To be exact, it is the handler mechanism, and the handler is only a role in the handler mechanism. It’s just that we have a lot of contact with handler, so we often use it as a substitute.

Handler mechanism is a thread message mechanism based on single line message queue mode in Android.

Its essence is message mechanism, which is responsible for the distribution and processing of messages. This may be a bit abstract and not easy to understand. What is “one line message queuing mode”? What is “news“?

Generally speaking,Per threadThere is a “pipeline”. We can put “messages” on this pipeline. There are staff at the end of the pipeline who will process these messages. Because the pipeline is single line, all messages must be processed in the form of first come first served (there is an “urgent line” in the handler mechanism: synchronization barrier, which will be discussed later). As shown in the figure below:

0073QO.png

We need to customize what message to put and how to handle it. The handler mechanism is equivalent to providing such a set of patterns. We only need to “put messages on the pipeline” and “write the processing logic of these messages”. The pipeline will continuously deliver messages to the end for processing. Finally, we should pay attention to the following pointsOnly one pipeline per threadIts basic scope is thread, which is responsible for communication within and between threads. Each thread can be regarded as a factory, and each factory has only one production line.

Two key issues

Before understanding the role of handler, we need to understand two key issues in the context of handler

  1. You cannot operate the UI in a non UI creation thread
  2. Time consuming tasks cannot be performed on the main thread

Our common understanding is that the UI cannot be updated on a non main thread. But this is not accurate. If we update the UI in the child thread, we can see what the error message is

Android's comprehensive analysis of handler message mechanism

The author left tears of English dregs

Android's comprehensive analysis of handler message mechanism

Only the original thread that created the view hierarchy can access its view. But why do we always say that non main threads can’t update UI? This is because our interface is generally drawn by the main thread, so the update of the interface is generally limited to the main thread. This exception is thrown in the viewrootiimpl. Checkthread() method. Can you bypass it? Of course, you can update the UI secretly before it is created. Readers who have read the activity startup process know that viewrootimpl is created after the oncreate method, so we can create sub threads in the oncreate method to update the UI secretly(Analysis portal of activity startup process)But still that sentence, yes, but there is no need to bypass this restriction, because this is designed by Google to make our program more secure.

Why can’t I update the UI in a child thread? Because it makes the interface produce unexpected results. For example, when the main thread is drawing a button, the other thread suddenly comes over and changes the size of the button to twice the size. At this time, it goes back to the main thread to continue the drawing logic, and the effect of the drawing will be problematic. Therefore, UI access must not be concurrent. However, the sub thread wants to update the UI. What should I do?Lock. Locking can solve this problem, but it will bring another problemInterface stuck. Lock is a heavy-duty operation that consumes performance, while UI operation is fast, accurate and ruthless. Adding lock will greatly reduce the performance of UI operation. What’s a better way? Handler is the solution to this problem.

Second, time consuming operations cannot be performed on the main thread. Time consuming operations include network requests, database operations and so on, which will lead to anr (application not responding). It’s easy to understand. There’s no problem, but when the two problems are combined, there’s a big problem. Generally, data request is a time-consuming operation, which must be requested in the child thread. When the request is completed, the UI must be updated, and the UI can only be updated in the main thread, which leads to the need to updateSwitch thread execution codeAs discussed above, locking is not desirable, so the importance of handler is reflected.

No handlercan I? Yes, but not necessarily. Handler is designed by Google to facilitate developers to switch threads and process messages. Then you say I don’t need to use Java tool classes. Can’t I make one myself? So… Please take in your small knees.

Why a handler?

First, the conclusion is given

  1. Switch threads of code execution
  2. Process messages in order to avoid concurrency
  3. Block threads to avoid thread termination
  4. Delay processing message

The first function is the most obvious and commonly used. In the last part, we have talked about the necessity of the existence of handler, and Android limits itYou cannot operate the UI in a non UI creation threadAt the same timeTime consuming tasks cannot be performed on the main threadSo we usually perform network requests and other time-consuming operations on the sub thread to request data, and then switch to the main thread to update the UI. At this time, the handler must be used to switch threads. It has been discussed above and will not be repeated here.

There is a mistake here: our activity is executed in the main thread. After the completion of the network request, the callback method of the main thread is switched to the main thread? Cough, don’t laugh. Don’t think this kind of low-level mistake is too unreasonable. Many children’s shoes make this thinking mistake when they first contact with development. This is actually a misunderstanding of the concept of thread. The code itself does not limit which thread to run on. The thread environment of code execution depends on which thread your execution logic is on. Maybe it’s a bit abstract. For example, there is now a wayvoid test(){}And then two different threads call it:

new Thread(){
    //First thread call
    test();
}.start();

new Thread(){
    //The second thread calls
    test();
}

At this time, although the test method is used, its execution logic is called by different threads, so it is executed in two different thread environments. When we want to switch logic to another thread for execution, we need to use handler to switch logic.

The second effect may look a little confused. But in fact, he solved another problem: concurrent operation. Although switching threads is solved, if the main thread is drawing a button, and just after measuring the length and width of the button, a new request from the sub thread interrupts. First stop the drawing operation here, change the button to twice the size, and then switch back logically to continue drawing. At this time, the length and width measured before is not accurate, The result of drawing is certainly not accurate. How to solve it?Single line message queue model. In the part about what is a handler, I briefly introduced that it is equivalent to a pipeline model. The request of the sub thread will become a message one by one, and then the main thread will process it in turn, so the problem of half drawing being interrupted will not appear.

At the same time, this model is not only used to solve the problem of UI concurrency. There is an H class in activitythread, which is actually a handler. More than 100 message types and corresponding processing logic are defined in activitythread. In this way, when activitythread needs to process a certain logic, it only needs to send the corresponding message to it, and it can ensure that the messages are executed in sequence, such as calling oncreate first and then onresume. If there is no hanlder, more than 100 interfaces of activitythread need to be opened to the outside world. At the same time, callbacks need to be carried out continuously to ensure that tasks are executed in sequence. It’s obviously a lot more complicated.

When we execute a java program, we enter it from the main method. After the execution, we exit immediately. But our Android application is definitely not allowed. It needs to wait for the user’s operation all the time. The handler mechanism solves this problem, but when there is no task in the message queue, it will block the thread and restart processing messages when there is a new task.

The fourth function gives the best solution to delay processing messages. If you want a dialog box to pop up 5 seconds after the app starts, what will you do without a handler? Turn on a thread and use thread. Sleep to make the thread sleep for a corresponding time, right? But what if there are multiple delayed tasks? And starting thread is also a heavy operation, and the number of threads is limited. You can directly send a message to the handler that delays the corresponding time, and he will process the message on time after the corresponding time (of course, there are special cases, such as the processing time of a single message is too long or the synchronization barrier, which will be discussed later). And no matter how many delayed messages are sent, there is no performance impact. At the same time, this function is also used to record the time of ANR.

Talking about these functions, readers may not have a very vivid concept in mind, or they may forget it after reading. However, we should not forget the definition of handlerHandler mechanism is a thread message mechanism based on single line message queue mode in Android.The above four functions are to make readers better understand the handler mechanism.

How to use handler

We usually use handler in two different ways, but the overall process is the same

  1. Create looper
  2. Using looper to create a handler
  3. Start looper
  4. Using handler to send information

Looper can be understood as a circulator, just like the rolling belt on the “pipeline”, which will be discussed in detail later.There is only one looper per threadUsually, the main thread has been created, and the traceability application startup process can know that Looper.prepareMainLooper is invoked during the startup process, and the Looper must be initialized in the sub thread.

Looper.prepare();

The second step is to create a handler, which is also the most familiar step. There are two ways to create a handler: pass in a callback object and inherit. As follows:

public class MainActivity extends AppComposeActivity{
    ...;
    //The first method is to create a handler using callback
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        Handler handler = Handler(Looper.myLooper(),new CallBack(){
            public Boolean handleMessage(Message msg) {
                TODO("Not yet implemented")
            }
        });
    }
    
    //The second method is to inherit the handler and override the handlermessage method
    static MyHandler extends Hanlder{
        public MyHandler(Looper looper){
            super(looper);
        }
        @Override
        public void handleMessage(Message msg){
            super.handleMessage(msg);
            //Todo (override this method)
        }
    }
}

Pay attention to the second method, use static inner class, otherwise it may cause memory leakage. The reason is that the non static inner class holds the reference of the outer class, while the message issued by the handler holds the reference of the handler. If the message is a delayed message and the activity is exited, but the message is still in the pipeline, message > handler > activity, then the activity cannot be recycled, resulting in a memory leak.

The inheritance method can write more complex logic, and the callback method is suitable for simple logic, which depends on the specific business.

Then call the loop method of the loop to start the loop

Looper.loop();

Finally, the handler is used to send information. After we get the instance of handler, we can send information through his SendMessage and post related methods, as follows:

handler.sendMessage(msg);
handler.sendMessageDelayed(msg,delayTime);
handler.post(runnable);
handler.postDelayed(runnable,delayTime);

Then, in general, which handler sends the information, and which handler will handle it. In this way, as long as we get the handler object, we can send information to the corresponding thread.

Handler internal schema structure

After the previous introduction, I have a certain understanding of looper, but I may not be clear about his internal mode. This part first explains the general internal mode of handler, in order to pave the way for the following detailed explanation and for the overall concept perception. First, the picture above:

Android's comprehensive analysis of handler message mechanism

There are three key roles in the handler mechanism: handler, looper and message queue. Among them, the message queue is an internal object of the looper. Each thread of the message queue and the looper has and only has one, while the handler can have many. Their workflow is as follows:

  1. After the user constructs the handler with the thread’s looper, the message is sent through the handler’s send and post methods
  2. The message will be added to the message queue, waiting for the looper to obtain and process
  3. The looper will continuously get messages from the message queue and deliver them to the corresponding handler for processing

This is the famous internal mode of handler mechanism. It’s hard to say, but it’s also very simple.

Key classes of handler mechanism

1、 ThreadLocal

summary

ThreadLocal is a tool class for storing data inside threads in Java.

ThreadLocal is used to store data, but each thread can only access the data of its own thread. Our general usage is:

ThreadLocal stringLocal = new ThreadLocal<>();
stringLocal.set("java");
String s = stringLocal.get();

Different threads access different data

public static void main(String[] args){
    ThreadLocal stringLocal = new ThreadLocal<>();
	stringLocal.set("java");
    
    System.out.println(stringLocal.get());
    new Thread(){
        System.out.println(stringLocal.get());
    }
}

result:
java
null

A thread can only access the data stored in its own thread.

The function of ThreadLocal

The ThreadLocal feature applies toThe same data type, different threads have different backupFor example, we have been talking about looper in this article. Each thread has an object, but the loops of different threads are different. At this time, it is particularly suitable to use ThreadLocal to store data, which is why we need to talk about ThreadLocal here

ThreadLocal internal structure

The internal mechanism structure of ThreadLocal is as follows:

Android's comprehensive analysis of handler message mechanism

Each thread, that is, each thread maintains a threadlocalmap, which stores multiple entries. An entry can be understood as a key value pair. Its essence is a weak reference with an internal variable of object type, as follows:

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal> k, Object v) {
        super(k);
        value = v;
    }
}

Entry is a static inner class of threadlocalmap, so that each entry maintains a ThreadLocal and ThreadLocal generic object. Each thread maintains an entry array, and the speed of reading data is O (1) by hash algorithm. Because the thread objects corresponding to different threads are different, the corresponding threadlocalmap must be different. Only when the thread object is obtained can the internal data be obtained, and the data will be isolated in different threads.

ThreadLocal workflow

How does ThreadLocal store data in different threads? First, we start with his set method

TheadLocal.class
    
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

The logic is not very complicated. First, get the thread object of the current thread, and then get the threadlocalmap object of the thread. If the map object does not exist, create one and call its set method to store the data. Let’s continue to look at the set method of threadlocalmap

ThreadLocalMap.class

private void set(ThreadLocal> key, Object value) {
    //There is an entry array inside each threadlocalmap
    Entry[] tab = table;
    int len = tab.length;
    //Gets the subscript of the new ThreadLocal in the entry array
    int i = key.threadLocalHashCode & (len-1);
    //Determine whether there is a hash conflict at the current location
    for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
        ThreadLocal> k = e.get();

        //If the data exists and is the same, it is returned directly
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //If there are no other elements in the current position, the new entry object is put directly into the
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //Determine whether to expand the array
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

The logic here is very similar to HashMap. We can directly use the thinking of HashMap to understand threadlocalmap: the key of threadlocalmap is ThreadLocal, and the value is the generic type corresponding to ThreadLocal. His storage steps are as follows:

  1. According to its own threadlocalhashcode and the length of the array, the subscript is obtained
  2. If this subscript is empty, it is inserted directly
  3. If the subscript already has an element, judge whether the ThreadLocal of the two is the same, update the value and return, otherwise find the next subscript
  4. Until you find the right place to insert the entry object
  5. Finally, judge whether to expand the entry array

Is it very similar to HashMap? The difference between HashMap and HashMap is that the hash algorithm is different, and the development address method is used here, while the linked list method is used in HashMap. Threadlocalmap sacrifices space for faster speed. Specific hash algorithm here is no longer in-depth, interested readers can read this articleThreadLocal portal

Then continue to see the get method of ThreadLocal

ThreadLocal.class

public T get() {
    //Gets the threadlocalmap of the current thread
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //Get the entry object according to ThreadLocal
        ThreadLocalMap.Entry e = map.getEntry(this);
        //If it is not found, initialization will also be performed
        if (e != null) {
            @SuppressWarnings("unchecked")
            //Return the obtained object
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

As mentioned earlier, threadlocalmap is actually very similar to a HashMap, and its get method is the same. Use ThreadLocal as the key to get the corresponding entry, and then return the value. If the map has not been initialized, the initialization operation is performed. Let’s continue to look at the get method of threadlocalmap

ThreadLocalMap.class

private Entry getEntry(ThreadLocal> key) {
    //Find subscript according to hash algorithm
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //If the data is found, it will be returned; otherwise, the next subscript will be found by developing the address method
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

The index is obtained by ThreadLocal hashcode of ThreadLocal, and then the data is found according to the index. If not, find the next subscript according to the algorithm.

Memory leak problem

We will find that ThreadLocal is a weak reference and value is a strong reference in entry. If there is no external reference to ThreadLocal, then ThreadLocal will be recycled, and its corresponding value will become meaningless, but it cannot be recycled, which leads to memory leakage. How to solve it? When ThreadLocal recycles, remember to call its remove method to remove the entry to prevent memory leakage.

ThreadLocal summary

ThreadLocal is suitable for data backup in different thread scopes

The ThreadLocal mechanism maintains a ThreadLocal map in each thread. The key is ThreadLocal and the value is the generic object corresponding to ThreadLocal. In this way, each ThreadLocal can store different values in different thread maps as a key. When obtaining data, the same ThreadLocal can get different data in different thread maps, as shown in the following figure:

Android's comprehensive analysis of handler message mechanism

Threadlocalmap is similar to a modified version of HashMap. It also uses array and hash algorithm to store data, which makes the speed of storage and reading very fast.

At the same time, we need to pay attention to memory leakage when using ThreadLocal. When ThreadLocal is no longer used, we need to remove value through the remove method.

2、 Message

summary

Message is the class responsible for carrying messages, mainly focusing on its internal properties

//User defined, mainly used to identify the type of message
public int what;
//Used to store some integer data
public int arg1;
public int arg2;
//Can put a serializable object
public Object obj;
//Bundle data
Bundle data;
//Time of message processing. Time relative to 1970.1.1
//Invisible to users
public long when;
//Handler to handle this message
//Invisible to users
Handler target;
//When we use the handler's post method, we encapsulate the runnable object as a message
//Invisible to users
Runnable callback;
//A message queue is a linked list, and next represents the next
//Invisible to users
Message next;

Recycle message

When we get the message, the official suggestion is to use the message. Obtain () method to get it, and then use the recycle () method to recycle it. Instead of directly new a new object:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

Message maintains a static linked list. The chain header issPool, message has a next attribute, and message itself is a linked list structure.sPoolSyncIs an object object, only as a solution to concurrent access security design. When we call obtain to get a new message, we will first check whether there are idle messages in the linked list. If not, we will create a new one to return.

After using, we can call the recycle method of message to recycle

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

If the message is in use, an exception will be thrown; otherwise, therecycleUncheckedRecycling:

void recycleUnchecked() {
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

The logic of this method is also very simple. Clear the content in the message, judge whether the linked list reaches the maximum value (50), and then insert it into the linked list.

Message summary

The function of message is to carry messages. There are many properties inside message to assign values to users. At the same time, message itself is also a linked list structure, which is used by both the message queue and the internal recycling mechanism of message to form a linked list. At the same time, the official suggests not to initialize the message directly, but to get a message recycling through the message. Obtain() method. Generally speaking, we don’t need to call recycle to recycle. In looper, messages will be recycled automatically, which will be discussed later.

3、 Message queue

summary

Each thread has and only has one message queue, which isA queue used to carry messages, using linked list as data structureSo messages waiting to be processed will be queued here. As mentioned earlier, threadlocalmap is a “modified version of HashMap”, while message queue is a “modified version of linkqueue”. He also has two key methods: enqueuemessage and next. This is also the focus of message queue.

Message also involves a key concept: thread hibernation. When there are no messages in the message queue or all of them are waiting, the thread will sleep and give up CPU resources to improve the utilization efficiency of CPU. After hibernation, if you need to continue to execute the code, you need to wake up the thread. When the method cannot return directly for the time being and needs to wait, it can block the thread, that is, sleep, wait to be awakened and continue to execute the logic. This part will be discussed in detail later.

Key methods

  • Out of the team — next ()

    The next method is mainly to do the message out of the team work.

    Message next() {
        //If the looper has exited, null is returned here
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        ...
        //Blocking time
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //Blocking time 
            nativePollOnce(ptr, nextPollTimeoutMillis);
    		//Lock the message queue to ensure thread safety
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                if (msg != null) {
                    if (now < msg.when) {
                        //The next message hasn't started yet, the time difference between the two
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //Get the message and execute it now, mark the message queue as non blocking
                        mBlocked = false;
                        //Linked list operation
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //No message, blocking
                    nextPollTimeoutMillis = -1;
                }
               ...
        }
    }

    The code is very long, which also involves synchronization barrier and idlehandler. I’ll talk about these two parts later. First, I’ll talk about the main out of team logic. I’ve added comments to the code. Let’s talk about it again. The purpose of the next method is to get a message in the message queue. If there is no message in the queue, it will block the method and wait for a new message to wake up. The main steps are as follows:

    1. If the looper has exited, return null directly
    2. Enter the dead loop until you get the message or exit
    3. In the loop, first judge whether to block, after blocking, lock the message queue and get the message
    4. If there is no message in the message queue, the thread will be blocked infinitely and waiting to wake up;
    5. If there is a message in the message queue, judge whether to wait, otherwise directly return the corresponding message.

    You can see that the logic is to determine whether to wait in the message at the current time. amongnextPollTimeoutMillisIndicates the blocking time,-1It means infinite time. Only by waking up can we break the block.

  • Enqueuemessage ()

    MessageQueue.class
    
    boolean enqueueMessage(Message msg, long when) {
        //Hanlder is not allowed to be empty
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
    
        //Lock the message queue
        synchronized (this) {
            //Judge whether the target thread is dead or not
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            //Mark the time that message is being executed and needs to be executed. Here, when is the time from 1970.1.1
            msg.markInUse();
            msg.when = when;
            //P is the chain header of the message queue
            Message p = mMessages;
            boolean needWake;
            //Determine whether to wake up the message queue
            //If there is a new queue head and the message queue is blocked, it is necessary to wake up the queue
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                ...
                //Find the insertion position according to the time
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    ...
                }
                msg.next = p; 
                prev.next = msg;
            }
    		
            //Wake up the queue if necessary
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

    There seems to be a lot of code in this part, but the logic is not complicated. The main thing is to operate the linked list and determine whether it is necessary to wake up the message queue. I have added some comments in the code, which are summarized as follows:

    1. First, judge that the target handler of message cannot be empty and cannot be in use

    2. Lock the message queue

    3. Judge whether the target thread is dead or not, and return false when it is dead

    4. Initialize the execution time of the message and the token is in execution

    5. Then, according to the execution time of the message, find the insertion position in the linked list to insert

    6. At the same time, determine whether to wake up the message queue. There are two situations to wake up: when the newly inserted message is at the head of the chain, if the message queue is empty or waiting for the delay time of the next task to execute, the message queue needs to wake up at this time.

Message queue summary

Message has two key points: blocking sleep and queue operation. Basically, they all revolve around two points. The source code also involves the synchronization barrier and idlehandler, which I separate into the last part. It is usually used less, but it is also an important content.

4、 Looper

summary

Looper is a very important core of handler mechanism. Looper is equivalent to the engine of thread message mechanism, which drives the whole message mechanism to run. The looper is responsible for fetching messages from the queue and handing them to the corresponding handler for processing. If there is no message in the queue, the next method of the message queue will block the thread and wait for a new message to arrive. Each thread has and can only have one “engine”, namely looper. If there is no looper, the message mechanism will not work. If there are multiple loopers, it will violate the concept of single line operation and cause concurrent operation.

Each thread has only one looper, and messages distributed by different loopers run in different threads. A message queue is maintained inside the looper. When initializing the looper, the message queue will be initialized.

Looper uses ThreadLocal to ensure that each thread has and has only one identical copy.

Key methods

  • Prepare: initializes the looper

    Looper.class
        
    static final ThreadLocal sThreadLocal = new ThreadLocal();
    
    public static void prepare() {
        prepare(true);
    }
    
    //Finally, this method is called
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    Before each thread uses a handler, it must callLooper.prepare()Method to initialize the looper of the current thread. parameterquitAllowedIndicates whether the looper can exit. The looper of the main thread cannot exit, otherwise the program will be terminated directly. We don’t need to initialize the looper when the main thread uses the handler. Why? Activiy helps us initialize the main thread looper when it starts, which will be expanded in detail later. So in the main thread, we can call it directlyLooper.myLooper()Gets the looper of the current thread.

    The prepare method focuses onsThreadLocal.set(new Looper(quitAllowed));You can see that ThreadLocal is used to create a copy of the looper object of the current thread. If the current thread already has a looper, an exception will be thrown. ThreadLocal is a static variable of the looper class. We have described ThreadLocal before. Here, each thread can initialize the looper of the current thread by calling the prepare method once.

    Next, we see the construction method of looper

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

    The logic is simple,A message queue was initializedAnd then assign the thread object of the current thread to mthread.

  • Mylooper(): gets the loop object of the current thread

    Gets the looper object of the current thread. This method is to call the get method of ThreadLocal directly

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
  • Loop (): loop to get message

    After the initialization of the looper, it will not start itself. We need to start the looper ourselves and call the looperloop()The method is as follows

    public static void loop() {
        //Gets the looper of the current thread
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            //Gets the message in the message queue
            Message msg = queue.next(); // might block
            if (msg == null) {
                //Returns NULL to indicate that the message queue exited
                return;
            }
            ...
            try {
                //Call the handler corresponding to message to process the message
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }
            ...
    		//Recycle message
            msg.recycleUnchecked();
        }
    }

    The loop () method is the core of the “engine” loop. First, get the looper object of the current thread. If there is no looper object, an exception will be thrown. Then enter a dead circle: call the next method of MessageQueue continuously to get the message, then call the dispatchMessage method of target handler of message to process Message.

    As we have learned about the message queue, the next method may block when the message queue is empty or there is no message to process. So the looper will wait all the time, block in the thread, and the thread will not end. When we exit the looper, the next method will return null, and the looper will end.

    At the same time, because looper is a logic running in different threads, its dispatchmessage method is also running in different threads, which achieves the purpose of thread switching.

  • Quit / quitsafely: quit looper

    Quit is to exit the looper directly, and quitsafely is to exit the looper in the message queueMessages that don’t need to waitAfter processing, exit. Take a look at the code

    public void quit() {
        mQueue.quit(false);
    }
    //This method is called in the end
    void quit(boolean safe) {
        //If it cannot exit, an exception is thrown. This value is assigned when initializing the looper
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
    
        synchronized (this) {
            //You can't run it again after you exit once
            if (mQuitting) {
                return;
            }
            mQuitting = true;
    		//Perform different methods
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
            //Wake up message queue
            nativeWake(mPtr);
        }
    }

    We can see that the quitsafely method is called in the end. This method first determines whether it can exit, and then executes the exit logic. If mqutting = = true, the method will be used directly here. We will find that the variable mqutting can only be assigned here, so once the looper exits, it cannot be run again. After that, different exit logics are executed. Let’s take a look at them respectively

    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

    This method is very simple, directly remove all the current messages. Let’s look at another way:

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            //If they are all waiting, remove them all and exit directly
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                //Remove all messages that need to wait
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

    The logic of this method is not complicated, that is to remove all the messages that need to wait, and keep the messages that need to be executed at present. Finally, in the next method of the message queue, null will be returned after judgment, indicating exit. After receiving this return value, the looper will also exit.

Looper summary

As the “power engine” of handler message mechanism, looper continuously obtains messages from the message queue, and then hands them to the handler for processing. Before using the loop, you need to initialize the loop object of the current thread, and then call the loop method to start it.

At the same time, the handler is also the core of switching, because different loops run in different threads, and the dispatchmessage method he calls runs in different threads, so the message processing is switched to the thread where the loop is located. When the looper is no longer in use, you can call different exit methods to exit it. Note that once the looper exits, the thread will end directly.

5、 Handler

summary

Our whole message mechanism is called handler mechanism, so we can know the high frequency of our use of handler. Generally, our use revolves around handler.Handler is the initiator and processor of the whole message mechanismThe message is sent to the target thread’s message queue through the handler in different threads, and then the target thread’s looper calls the handler’s dispatchmessage method to process the message.

Create handler

In general, we use the handler in two ways: inherit the handler and override the handlemessage method, directly create the handler object and pass it into callback, which will not be repeated in the previous section on using the handler.

It should be noted that the looper parameter must be displayed when creating a handler, but the parameterless constructor cannot be used directly, such as:

Handler handler = new Handler(); //1
Handler handler = new Handler(Looper.myLooper())//2

One is wrong, two is right. Avoid the situation that the looper has exited in the process of creating the handler.

send message

There are two series of methods for handler to send messages: postxx and sendxx. As follows:

public final boolean post(@NonNull Runnable r);
public final boolean postDelayed(@NonNull Runnable r, long delayMillis);
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis);
public final boolean postAtFrontOfQueue(@NonNull Runnable r);

public final boolean sendMessage(@NonNull Message msg);
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis);
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis);
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)

Here, I only list two common methods. Except for the two methods inserted in the queue head, the other methods are finally calledsendMessageAtTime. Let’s analyze the post method and the source code

public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

The post method encapsulates the runnable object as a message, and then calls thesendMessageDelayedMethod, let’s see how it encapsulates:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

You can see that the logic is very simple. Assign the runnable object directly to the callback attribute. Now go back and continuesendMessageDelayed

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

public boolean sendMessageAtTime(@NonNull 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);
}

sendMessageDelayedChange the delay time less than 0 to 0 and then call it.sendMessageAtTime. This method is mainly used to determine whether the message queue has been initialized before callingenqueueMessageMethods to join the team

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //Here, set the target to yourself
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
	//The asynchronous handler sets the flag bit true. We will talk about the synchronization barrier later
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // finally, the method of calling MessageQueue is entered.
    return queue.enqueueMessage(msg, uptimeMillis);
}

You can see that the queue entry operation of the handler is also very simple. Set the target of the message to itself, so that the message will be processed by itself. Finally, call MessageQueue’s team entry method to join the team, which is not mentioned again before.

Other methods of sending messages are similar. Readers can follow the source code themselves if they are interested.

Processing messages

As mentioned above, when looper processes messages, it calls the dispatcher’s dispatchmessage method to process them. Let’s take a look at this method

public void dispatchMessage(@NonNull 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();
}

His logic is not complicated. First, determine whether the message has a callback, and if so, directly execute the logic of the callback. This callback is the runnable object that we pass in by calling the handler’s post series methods. Otherwise, judge whether the handler has callback, and if so, execute its method. If it returns true, it ends. If it returns false, it directly uses the handlemessage method of the handler itself. This process can be shown as follows:

dispatchMessage逻辑

Memory leak problem

When we use inheritance handler method to use handler, we should pay attention to use static inner class instead of non static inner class. Because the non static inner class will hold the reference of the outer class, and from the above analysis, we know that the target attribute of the message after it is queued points to the handler. If the message is a delayed message, then the object of the reference chain will not be released, causing a memory leak.

Generally, this kind of leakage phenomenon is: we send a delay message in the activity, and then exit the activity, but because it can’t be released, the activity can’t be recycled, resulting in memory leakage.

Handler summary

As the processing and sender of messages, handler is the starting point and end point of the whole message mechanism, and it is also a class that we contact most, because we call this message mechanism handler mechanism. The most important thing of handler is to send and process messages, as long as you master these two aspects. At the same time, pay attention to memory leakage, do not use non static inner class to inherit handler.

6、 Handlerthread

summary

Sometimes we need to create a thread to perform some time-consuming tasks. In general, you can create a new thread, and then initialize the looper of the thread in its run method. In this way, you can use its looper to cut the thread to process messages. Here’s the kotlin code, which is similar to Java. I believe it can be understood:

val thread = object : Thread(){
    lateinit var mHandler: Handler
    override fun run() {
        super.run()
        Looper.prepare()
        mHandler = Handler(Looper.myLooper()!!)
        Looper.loop()
    }
}
thread.start()
thread.mHandler.sendMessage(Message.obtain())

But, run it and it explodes:

00tQns.png

Handler has not been initialized. It takes a certain amount of time to initialize the looper, which leads to this problem. That’s simple. Just wait for a moment

val thread = object : Thread(){
    lateinit var mHandler: Handler
    override fun run() {
        super.run()
        Looper.prepare()
        mHandler = Handler(Looper.myLooper()!!)
        Looper.loop()
    }
}
thread.start()
Thread(){
    Thread.sleep(10000)
    thread.mHandler.sendMessage(Message.obtain())
}.start()

Carry it out. Ah, there’s no error. Sure enough. But, This kind of code is particularly embarrassing and bloated, but also to open a thread to delay processing. Is there a better solution? Yes, handlerthread.

Handlerthread itself is a thread. It inherits from thread. Its code is not complex. Take a look at it (there are still a lot of code. You can choose to look at it or not. I will talk about the key methods below)

public class HandlerThread extends Thread {
    //The order is: thread priority, thread ID, thread looper, and internal handler
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    //Two constructors. Name is the thread name and priority is the thread priority
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    //Method before looper starts running
    protected void onLooperPrepared() {
    }

    //Initialize looper
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            //Notification initialization complete
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    //Gets the looper of the current thread
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        //If it has not been initialized, it will block until initialization is complete
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    //Using the wait method of object
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    //Gets the handler, which is marked hide and cannot be obtained by the user
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    //Two different types of exit have been mentioned before and will not be repeated
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    //Get thread ID
    public int getThreadId() {
        return mTid;
    }
}

The code of the whole class is not very much, the focus is onrun()andgetLooper()method. First, see the getlooper method

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    //If it has not been initialized, it will block until initialization is complete
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                //Using the wait method of object
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

Unlike what we wrote earlier, he has onewait()This is a method provided by the object class in Java, which is similar to the message queue blocking. When the initialization of the looper is completed, it will wake him up and return smoothly, without causing the situation that the initialization of the looper has not been completed. Then you see the run method:

//Initialize looper
@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        //Notification initialization complete
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

The conventional Looper initialization is called after completion.notifyAll()Method, corresponding to the above getlooper method.

The use of handlerthread

The application scope of handlerthread is very limited, and it is time-consuming to open sub thread to accept message processing. Therefore, his usage is relatively fixed

HandlerThread ht = new HandlerThread("handler");
Handler handler = new Hander(ht.getLooper());
handler.sendMessage(msg);

Get his looper and use it with external custom handler.

7、 Summary

Handler, message queue and looper constitute the Android message mechanism, and each performs its own duties. Among them, the handler is mainly responsible for sending and processing messages, the message queue is mainly responsible for sorting messages and blocking codes when there are no messages to be processed, and the looper is responsible for fetching messages from the message queue to the handler for processing, so as to achieve the purpose of thread switching. Through the source code analysis, I hope readers can have a clearer understanding of these concepts.

Workflow

This part mainly talks about the overall process. In the front, we talked about the function and source code of each component. Now let’s talk about their overall process. Look at the picture first

0wcEX6.png

  1. Handler sets up a series of APIs for developers to send various types of information using handler, and eventually calls enqueuemessage method to join the queue
  2. Call the enqueuemessage method of the message queue to insert the message into the linked list of the message queue and wait for the looper to obtain and process it
  3. After the looper gets the message, it calls the handler corresponding to the message to process the message

In this way, the process of sorting out is clear, and I will not repeat the detailed source code analysis. If any part of the reader is not clear enough, you can go back to the corresponding part above and see it again.

Related issues

Why doesn’t the main thread initialize the looper?

A: because the application has already initialized the main thread looper during startup.

Every Java application has a main method entry, and Android is no exception. The entry of Android program is in the main method of activitythread

public static void main(String[] args) {
    ...
	//Initializes the main thread looper
    Looper.prepareMainLooper();
    ...
    //Create a new activitythread object
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    //Get the handler of activitythread, which is also its internal class H
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...
    Looper.loop();
	//If the loop method ends, an exception is thrown and the program ends
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

In the main method, first initialize the main thread looper, create a new activitythread object, and then start the looper, so that the looper of the main thread runs when the program starts. We don’t need to initialize the main thread looper.

Why is the looper of the main thread a dead loop, but not anr?

A: because when the looper processes all the messages, it will enter the blocking state. When a new message comes in, it will break the blocking and continue to execute.

This is actually not a good understanding of the concept of ANR. Anr, full name application not responding. When I send a message of UI drawing to the main thread handler and it is not executed after a certain period of time, an anr exception is thrown. Looper’s dead loop is to execute all kinds of transactions, including UI drawing transactions. Looper loop indicates that the thread is not dead. If looper stops the loop, the thread ends and exits. Loop itself is one of the reasons to ensure that UI drawing tasks can be executed. At the same time, there is a synchronization barrier for UI rendering tasks, which can ensure faster rendering. We’ll talk about it next.

How does handler ensure the concurrent access security of message queue?

A: cycle lock, with blocking wake-up mechanism.

We can find that the message queue is actually a “producer consumer” model. The handler keeps putting in messages and the looper keeps taking them out. This involves the deadlock problem. If the looper gets the lock, but there is no message in the queue, it will wait all the time, and the handler needs to put the message in, but the lock is held by the looper and cannot enter the queue, which causes a deadlock. The solution of handler mechanism isCyclic locking. In the next method of message queue:

Message next() {
   ...
    for (;;) {
		...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            ...
        }
    }
}

We can see that his waiting is outside the lock. When there is no message in the queue, he will release the lock first, and then wait until he is awakened. This will not cause deadlock problems.

When you join the team, will you hold the lock while waiting for the message processing because the queue is full? The difference is that there is no upper limit for the messages in the message queue, or the upper limit is the memory allocated by the JVM to the program. If the memory exceeds the limit, an exception will be thrown, but it will not normally.

Can looper be rerun after exiting?

A: No.

The thread survives by blocking the next method called by looper. If the looper exits, the thread will end immediately and there will be no chance to run for a second time. Even if the thread is not finished, call loop () again. There is an mqutting variable inside the loop. When it is assigned to false, it cannot be assigned to true again. So you can’t run it again.

How does the handler switch threads?

A: loops of different threads are used to process messages.

As we mentioned earlier, the execution thread of the code is not determined by the code itself, but by which thread or thread the logic of the code is called. Each looper runs on its own thread, so the dispatchmessage method called by different loopers runs on its own thread.

What’s the blocking wake-up mechanism of handler?

A: handler’s blocking wake-up mechanism is based on Linux.

This mechanism is also similar to the handler mechanism. Create a file descriptor locally, and then the waiting party will listen to the file descriptor. The wakeup party only needs to modify the file, and the waiting party will receive the file and break the wakeup. It is similar to looper listening to message queue and handler adding message. Readers can learn more about the specific Linux layer knowledge through this article(Portal

Can a message be processed urgently/ What is handler synchronization barrier?

A: it can / can be a mechanism that enables asynchronous messages to be processed faster

If a UI update operation message is sent to the main thread, and there are many messages in the message queue, the processing of this message will become slow, causing the interface to jam. Therefore, through synchronization barrier, messages drawn by UI can be executed faster.

What is synchronization barrier? This “barrier” is actually a message, inserted in the chain header of the message queue, and its target = = null. Didn’t message judge that target can’t be null when it joined the team? No, adding synchronization barriers is another way:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        //Execute all messages that need to be executed
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        //Insert synchronization barrier
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

We can see that the synchronization barrier is a special target. Where is it special? Target = = null, we can see that it does not assign a value to the target property. What’s the use of this target? Look at the next method

Message next() {
    ...

    //Blocking time
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ...
        //Blocking time 
        nativePollOnce(ptr, nextPollTimeoutMillis);
		//Lock the message queue to ensure thread safety
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            /**
            *  1
            */
            if (msg != null && msg.target == null) {
                //Synchronization barrier, find the next asynchronous message
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //The next message hasn't started yet, the time difference between the two
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //Get the message and execute it now, mark the message queue as non blocking
                    mBlocked = false;
                    /**
            		*  2
            		*/
                    //Generally, only asynchronous messages take messages from the middle, and synchronous messages are obtained from the chain header
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                //No message, blocking
                nextPollTimeoutMillis = -1;
            }

            //When looper. Quitsafe() is called, it will exit after all messages are executed
            if (mQuitting) {
                dispose();
                return null;
            }
            ...
        }
        ...
    }
}

I’ve talked about this method before. Let’s focus on the part about synchronization barrier, and look at the code in note 1

if (msg != null && msg.target == null) {
    //Synchronization barrier, find the next asynchronous message
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

If it encounters synchronization barrier, it will loop through the whole linked list to find the message marked as asynchronous message, that is, isasynchronous returns true, and other messages will be directly ignored. In this way, the asynchronous message will be executed in advance. Note the code in Note 2.

Note that the synchronization barrier will not be removed automatically, and it needs to be removed manually after use, otherwise the synchronization message will not be processed. It can be seen from the source code that if the synchronization barrier is not removed, it will always be there, so that the synchronization message can never be executed.

With synchronization barrier, the judgment condition of wake-up must be addedThere is a synchronization barrier in the message queue and it is blocking. At this time, the new asynchronous message is inserted before all asynchronous messages. It’s also easy to understand. It’s the same as synchronous messages. If all synchronization messages are ignored first, a new list header is inserted and the queue is blocked. At this time, it needs to be awakened. Take a look at the source code:

boolean enqueueMessage(Message msg, long when) {
    ...

    //Lock the message queue
    synchronized (this) {
        ...
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            /**
            *	1
            */
            //When the thread is blocked and there is a synchronization barrier, and the queued message is asynchronous
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                /**
                *	2
                */
                //If an asynchronous message is found, it indicates that the asynchronous message with delay needs to be processed and does not need to be awakened
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; 
            prev.next = msg;
        }
		
        //Wake up the queue if necessary
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Similarly, I’ve talked about this method before, ignoring the code that has nothing to do with the synchronization barrier. See the code in note 1. If the inserted message is asynchronous and has synchronization barrier, and the message queue is in blocking state, it needs to wake up. If the asynchronous message is not inserted before all asynchronous messages, there is no need to wake up, as shown in Note 2.

So how do we send an asynchronous message? There are two ways:

  • All messages sent using asynchronous handler are asynchronous
  • Mark message asynchronously

The handler has a series of constructors with boolean type parameters, which determine whether it is an asynchronous handler

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    //The value is assigned here
    mAsynchronous = async;
}

When sending a message, it will assign a value to the message:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
	//Assignment
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

But the handler constructor of asynchronous type is marked as hide, so we can’t use it. So we can use asynchronous message only by setting asynchronous flag to message

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

But!!!!In fact, synchronization barrier is not very useful for our daily use. Because the method of setting synchronous barrier and creating asynchronous handler is marked as hide, which indicates that Google doesn’t want us to use it. So here synchronization barrier is also used as an understanding, which can more comprehensively understand the content of the source code.

What is idlehandler?

A: when the message queue is empty or there is no message to execute, the interface object will be called back.

Idlehandler looks like a handler, but it’s just an interface with a single method, also known as a functional interface

public static interface IdleHandler {
    boolean queueIdle();
}

A list in the message queue stores the idlehandler object. When the message queue has no message queue to be executed, it will traverse and call back all idlehandlers. Therefore, idlehandler is mainly used to process messages when the message queue is idleLightweightMy job.

Idlehandler is called in the next method:

Message next() {
    //If the looper has exited, null is returned here
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    //Number of idlehandlers
    int pendingIdleHandlerCount = -1; 
    //Blocking time
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //Blocking time 
        nativePollOnce(ptr, nextPollTimeoutMillis);
		//Lock the message queue to ensure thread safety
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //Synchronization barrier, find the next asynchronous message
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //The next message hasn't started yet, the time difference between the two
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //Get the message and execute it now, mark the message queue as non blocking
                    mBlocked = false;
                    //Generally, only asynchronous messages take messages from the middle, and synchronous messages are obtained from the chain header
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                //No message, blocking
                nextPollTimeoutMillis = -1;
            }

            //When looper. Quitsafe() is called, it will exit after all messages are executed
            if (mQuitting) {
                dispose();
                return null;
            }

            //When the messages in the queue run out or are waiting for time to delay execution, the pending idlehandlercount < 0
           	//Assign pendingidlehandlercount the number of idlehandlers in the message queue
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //There is no idlehanlder direct continue to execute
            if (pendingIdleHandlerCount <= 0) {
                //Execute idlehandler and mark the message queue into blocking state
                mBlocked = true;
                continue;
            }

            //Convert list to array type
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        //Execute idlehandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //  Release the reference to idlehandler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            //If false, remove idlehanlder
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        //Finally, set pendingidlehandlercount to 0 to prevent it from being executed again
        pendingIdleHandlerCount = 0;

        //When idlehandler is executed, a new message may have come in
        //So we can't block it at this time. We have to go back to the loop and have a look
        nextPollTimeoutMillis = 0;
    }
}

There’s a lot of code, which may be a bit messy. I’ll sort out the logic, and then go back to see the source code

  1. When the next method is called, thependingIdleHandlerCountThe value is – 1
  2. If there is no message to process in the queue, it will be judgedpendingIdleHandlerCountIs it<0If yes, the length of the list storing idlehandler is assigned topendingIdleHandlerCount
  3. Put all idlehandlers in the list into the array. This step is to prevent the list from being inserted into a new idlehandler when idlehandler is executed, causing logical confusion
  4. Then we traverse the entire array and execute all idlehandlers
  5. Finally givependingIdleHandlerCountThe value is 0. Then go back and see if there are any new messages inserted during this period. becausependingIdleHandlerCountThe value of is 0, not – 1, so idlehandler is executed only once in idle time
  6. Also note that if idlehandler returns false, it is discarded after one execution.

It is suggested that readers go back to read the source code again, so that the logic will be much clearer.

Recognition of handler message mechanism

So far, we have talked about the handler mechanism. But I don’t know if readers have the same doubts as me

Why is handler mechanism called message mechanism in Android? Is handler really just used to switch threads and update UI? How to better understand handler message mechanism from the perspective of source code design?

Every time I learn about the mechanism of Android, I like to study its role in the Android source code design, or thinking. This helps to raise my understanding to a higher level. Here is my understanding of the handler mechanism.

I think the reason why we call the handler mechanism is that we are all exposed to the handler, so it is called the handler mechanism. If we are exposed to the looper more often, maybe its name is the looper mechanism. To be more precise, he should beAndroid message mechanism

We know that every Java program has an entry: the main method, and then we start to enter our application from here. I believe every reader has the experience of using C language to write student management system. How do we make the program pause and not end directly? Wait through loop + input. We will write a dead loop in the outermost layer, and then continuously monitor the input, and then execute the command according to the input. When the user has no input, he will wait all the time. This is actually similar to the handler mechanism. The handler mechanism uses the idea of multithreading. The main thread keeps waiting for messages, and then sends messages from other threads to let the main thread execute the logic. This is also called the handler mechanismTransaction driven designThe logic of the main thread is driven by message.

Let’s take a look at the main method of Android applications

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    AndroidOs.install();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);
    Process.setArgV0("");
    //Initialize looper
    Looper.prepareMainLooper();
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    //Create activitythread
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    //Start looper
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

However, we can see that his code is not many. After activitythread and looper are started, no other logic is executed. How can our activity be called and executed? Through the handler. Android is a transaction driven design, which makes the whole program run by continuously distributing transactions. Readers familiar with the activity startup process should be able to associate AMS with the program through the binder mechanism, and then the binder thread sends a message to the main thread, which then executes the corresponding logic. Their relationship can be represented by the following figure:

00IgT1.png

When an application process is created, it only creates the looper and handler of the main thread, and other binder threads. After that, AMS communicates with the application through binder and sends a message to the main thread to let the program perform the operation of creating an activity. In this design, we don’t need to write dead loops and wait for user input, so the application can run without ending. I won’t talk about the startup of activity here. Readers can read another article of the author(Detailed explanation of activity startup process)。 After that, the program will open other threads to receive the user’s touch input, and then package these as a message to send to the main thread to update the UI.

It can be said that “no message, no android”. The whole Android program runs based on this set of message mechanism. It’s not just about switching threads, it’s about the foundation of the whole Android program.

summary

This article from the beginning of the introduction, to explain the source code and function of each class, and finally sublimate the design idea of the whole message mechanism. I believe readers have a profound understanding of handler message mechanism.

We don’t use the message mechanism much in our daily life. Although it is very important, our use is also the main user switching threads to update the UI. We have many mature and convenient frameworks to use: rxjava, kotlin coroutine and so on. But because the handler mechanism is very important for Android program, it is necessary to learn and understand it for in-depth learning of Android.

I hope the article is helpful to you.

This is the full text. It’s not easy to be original. If you think it’s helpful, you can like it and forward it.
The author’s ability is limited. If you have any ideas, you are welcome to exchange and correct in the comment area.
If you need to reprint it, please send a private message.

In addition, welcome to my personal blog:Portal