Android advanced – Handler low-level source code analysis

  • preface
  • Handler function
  • Handler in the main thread
  • Handler Sends messages
  • Create a Message
  • The classification of the Message
    • Asynchronous messages:
    • Synchronization barrier

preface

  • In this article, we will look at how the handler completes the communication between threads step by step
  • Introduce handler by looking at the source code
  • Handler will also be introduced around a wide range of interview questions

Handler function

Handler is mainly used for asynchronous message processing. After sending a message, it first enters a message queue, and the function that sent the message immediately returns. Another part of the message queue takes out the message one by one, and then processes the message. This mechanism is typically used to handle relatively long operations.

Baidu encyclopedia

Handler in the main thread

As we all know, every Java file is launched by the main function, and in Android the same way.

Here is the startup process of APK:

Launch app -> Zygote -> JVM -> ActivityThreadCopy the code

The mian function in Android is inside this ActivityThread:

ActivityThread{ ... public static void main(String[] args) { Looper.prepareMainLooper(); . Looper.loop(); }... }Copy the code

In the main function, it’s initialized

  • Looper.prepareMainLooper();
  • Looper.loop();

PrepareMainLooper ()

Which the constructor

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
//public static native java.lang.Thread currentThread();
Copy the code

The point of interest in the constructor is that MessageQueue() is initialized

Look at prepareMainLooper ()

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } 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)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }Copy the code

When prepareMainLooper() is called, it goes to prepare(), and in prepare() there is something called ThreadLocal, which is static final, so this ThreadLocal is unique

In perpare(), sthreadLocal.get () checks if the variable exists and raises an exception if it does not already exist. If not, set Looper()

Conclusion: Prepare () can only be called once in the main thread, so the constructor can only be called once, and MessageQueue()/Looper() are both unique

In to see stars. The loop (); The source code

Public static void loop() {// Get looper final looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); MessageQueue final messageQueue queue = me.mqueue; // Loop out the message for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }... Recycling MSG. RecycleUnchecked (); }}Copy the code

There is an infinite loop in looper, executing all the time, then retrieving the message through messagequeue.next () and returning it if it is null

Conclusion: MessageQueue is a store of things, just like an ArrayList is a store of things

Handler Sends messages

public class MainActivity extends AppCompatActivity { Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Switch (MSG. What){case 1: log. I (" message :","1"); break; }}}; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler.sendEmptyMessage(1); }}Copy the code

First, call sendEmptyMessage() -> sendEmptyMessageDelayed() ->sendMessageDelayed() -> sendMessageAtTime() -> enqueueMessage()

By calling, found that both sendXXX (), or postXXX () will perform to the enqueueMessage () method, and then call MessageQueue. EnqueueMessage ()

class Message{ Handler target; } class Handler{ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }}Copy the code

When called to enqueueMessage(), message.target first holds the Handler object, which in technical terms Message() holds the Handler reference

class Message{ Message next; } class MessageQueue{ Message mMessages; boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { ... msg.when = when; Message p = mMessages; //true wake Boolean needWake; // When the first message comes in, save the current state and return true; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { ... for (;;) { prev = p; p = p.next; / / when the current time p.w hen a time on the if (p = = null | | the when < p.w hen) {break; } // p.isasynchronous () is an asynchronous message if (needWake &&p.isasynchronous ()) {needWake = false; } } msg.next = p; prev.next = msg; } if (needWake) {// Native low-level implementation wakes up nativeWake(mPtr); } } return true; }}Copy the code

This code is currently sent to the message to save, according to the time, stored in the queue

I put it in the queue, but when do I get it? Take a look at MeeageQueue’s next() method

class MessageQueue{ Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; NextPollTimeoutMillis =0 is not blocked // nextPollTimeoutMillis<0 is blocked all the time // NextPollTimeoutMillis >0 blocks the corresponding duration, which can be woken up by new messages in // Next (). Since message queues are sorted by latency, consider the headers with the lowest latency first. NextPollTimeoutMillis = 0; nextPollTimeoutMillis = 0; for (;;) {... NativePollOnce (PTR, nextPollTimeoutMillis); nativePollOnce(PTR, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // If target==null, then it is a barrier that needs to be iterated until the first asynchronous message is found. = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg =  msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } // handle synchronization messages if (MSG! = null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message immediately after all pending messages are processed. if (mQuitting) { dispose(); return null; }... }}}Copy the code

As you can see, the messagequeue.next () method fetches the message from the MessageQueue

And where can I pick it up? Let’s revisit the looper.loop () method

Public static void loop() {// Get looper final looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); MessageQueue final messageQueue queue = me.mqueue; // Loop out the message for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); . Recycling MSG. RecycleUnchecked (); }}Copy the code

Call the messagequeue.ntxt () method in looper.loop () to loop out the message

The dispatchMessage() method is then called

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

HandleMessage () is finally called to send the message out

Picture process :(pictures from the Internet)



Handler Execution process summary:

On the main thread, the system initializes the call looper.prepareMainLooper () on the ActivityThread main; PrepareMainLooper () initializes the MessageQueue queue in the constructor of the Looper method to store messages. PrepareMainLooper () executes prepare() to initialize the unique Looper, and looper.loop () is called to receive Mess in an infinite loop Age, as you can see, all code in Android executes on top of Handler

Then when the Activity creates a Handler, both sendXXX and postXXX calls are executed to enqueueMessage(). , and then execute the MessageQueue. EnqueueMessage (), through time in order to save the Message to the queue, through MessageQueue. Next () method to take out the head of the Message (because in the queue have it sorted, so the Message of the head Is the message to be executed first.

The looper.loop () method waits for the message by calling messagequeue.next (). After receiving the message, handler.dispatchMessage () is called and handleMessage() is sent to the Handler

Note that Looper is already initialized in ActivityThread, so looper.pperpare () cannot be called in the main thread;

Create a Message

  • new Message()
  • Message.obtain();
  • Message.obtain(Message msg); / / bind Message
  • Message.obtain(Handler h)// Bind Handler

Message.obtain() because you can check to see if there are any messages that can be reused

The goal is to optimize memory and performance without creating or destroying too many Message objects

When creating a Message, you can bind it using the message.obtain (Handler h) constructor. EnqueueMessage () is also bound in Handler. All methods that send messages call this method to queue, so messages can be created unbound

class Handler{ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; / / bind... return queue.enqueueMessage(msg, uptimeMillis); }}Copy the code

The classification of the Message

  • Synchronous messages (plain plain messages)
  • SetAsynchronous messages (true)
  • Synchronous barrier messages (messages that meet target == NULL, typically used with asynchronous messages)

Asynchronous messages:

Let’s review when to get an asynchronous message:

class MessageQueue{ Message next() { ... // If target==null, then it is a barrier that needs to loop through until it finds the first asynchronous message if (MSG! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg =  msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } // handle synchronization messages if (MSG! = null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; }}... }Copy the code

If MSG. Target == null, the message is sent asynchronously or asynchronously It’s an asynchronous message, but sendXX/postXX is sent and it ends up at enqueueMessage(), still assigning a value to target

class Handler{ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; . }}Copy the code

Set to asynchronous message:

Message msg = handler.obtainMessage(); // Set the asynchronous message to msg.setasynchronous (true); handler.sendMessage(msg);Copy the code

The async argument passes true for an asynchronous message.

Messages sent in this way are not yet asynchronous because they still end up in enqueueMessage() and still assign a value to target, resulting in target not being null. This is the same as synchronizing messages mentioned earlier. So when does target == null satisfy the condition?

Sync Barrier.

Synchronization barrier

What is a synchronization barrier?

Yes, the key to sending asynchronous messages is that the messages open a synchronization barrier. A barrier stands for block, and as the name suggests, a synchronous barrier blocks synchronous messages, allowing only asynchronous messages to pass through. How do you turn on the synchronization barrier?

handler.getLooper().getQueue().postSyncBarrier()

class MessageQueue{ /** * * @hide */ public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { ... Synchronized (this) {// Obtain Message final Message MSG = message.obtain (); msg.markInUse(); // Here it is!! Target ==null MSG. When = when; msg.arg1 = token; . / / If prev is null, the MSG is inserted chronologically to the appropriate position in the message queue. = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; }}}Copy the code

As you can see, the Message object is initialized without assigning a value to target, so the source of target == null is found. Thus, a message with target == NULL enters the message queue.

So how are so-called asynchronous messages handled once the synchronization barrier is turned on?

If you know anything about the message mechanism, you should know that the final processing of the message is in the message poller looper.loop (), and the loop() calls messagequyue.next () to fetch the message from the MessageQueue. Let’s look at the key code:

class MessageQueue{ Message next() ..... int pendingIdleHandlerCount = -1; // -1 only during first iteration // 1. If nextPollTimeoutMillis=-1, the block does not time out. // 2. If nextPollTimeoutMillis=0, it will not block and return immediately. If nextPollTimeoutMillis>0, block nextPollTimeoutMillis for milliseconds (timeout). int nextPollTimeoutMillis = 0; // Next () is also an infinite loop for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); Synchronized (this) {final long now = systemclock. uptimeMillis(); synchronized (this) {final long now = systemclock. uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // The head of the current list // key!! // If target==null, then it is a barrier that needs to loop through until it finds the first asynchronous message if (MSG! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg =  msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) {// If there is a message that needs to be processed, first check whether the time is up, if not, set the blocking time, PostDelay if (now < msg.when) {// Assign the nextPollTimeoutMillis value to nextPollTimeoutMillis, NextPollTimeoutMillis = (int) math.min (MSG. When - now, Integer.MAX_VALUE); } else {// The message mBlocked = false; If (prevMsg! = null) prevMsg.next = msg.next; } else { mMessages = msg.next; } MSG. Next = null; msg.markInUse(); Return MSG; NextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; }... // omit}}Copy the code

Call removeSyncBarrier() to remove the synchronization barrier once all asynchronous messages have completed

Example of synchronous barrier life: Suppose we want to take a plane now, ordinary users queue up one by one to check in. If a VIP user arrives now, the VIP user will jump the queue to board the plane, and the ordinary user will board the plane after all the VIP users have left

  • VIP users asynchronously send messages
  • Regular users syncing messages (waiting in line to board one plane at a time)
  • Flight attendant/flight attendant: synchronous barrier (ordinary users are screened at first and wait for VIP users to complete boarding before ordinary users are processed)

The synchronization barrier is tagged with @hide, so it cannot be used directly

But especially in View,

For example, in the View update, in many parts of the draw, requestLayout, invalidate calls the ViewRootImpl. ScheduleTraversals (), as follows:

class ViewRootImpl{ Choreographer mChoreographer; void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; MTraversalBarrier = mhandler.getLooper ().getQueue().postSyncbarrier (); / / send asynchronous messaging mChoreographer. PostCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); . }}}Copy the code

PostCallback () eventually reached the ChoreographerpostCallbackDelayedInternal () :

The synchronization barrier is turned on and asynchronous messages are sent, and since UI update-related messages are of the highest priority, they are processed first.

In the end, when you remove synchronization barrier needs to call ViewRootImpl. UnscheduleTraversals ().

void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; // Remove the synchronization barrier mhandler.getLooper ().getQueue().removesyncBarrier (mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); }}Copy the code

Synchronization barrier reference :juejin.cn/post/684490…

Summary of common Handler interview questions

Original is not easy, your praise is my biggest support ~