preface

Android customizes its Davik and Art virtual machines based on the JVM part of the specification. Android calls the Main of ActivityThread to start an app. In order to avoid multi-threading problems caused by memory sharing, most UI applications, such as Win32,Android,Java Swing, use message queues to maintain a Main thread for separate control view.

There is an initialization of the message loop in ActivityThread.main.

class ActivityThread{
 	
	public static void main(String[] args) {
		// Create an instance of Looper corresponding to the main thread and store it in looper.smainLooper
	 	Looper.prepareMainLooper();
	 	/ /...
	 	// Create a Handler object instance
	 	 if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        // Start the event loop
        Looper.loop();
	}
	/ / handler instance
	 final H mH = new H();
	 final Handler getHandler(a) {
        return mH;
    }
    static volatile Handler sMainThreadHandler;
}
Copy the code

Which analysis 1

To avoid the annoyance of analyzing all the code at once, this chapter only parses parts

Stars. PrepareMainLooper analysis

class Looper{
 	// Hold thread-specific Looper instances
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

	 @Deprecated
    public static void prepareMainLooper(a) {
        // A main thread instance is constructed
        prepare(false);
        synchronized (Looper.class) {
        	// Save the mainLooper instancesMainLooper = myLooper(); }}// Get the Looper instance corresponding to the current thread
  public static @Nullable Looper myLooper(a) {
        return sThreadLocal.get();
    }

   public static void prepare(a) {
        prepare(true);
    }
    
    // Construct lopper instance into a ThteadLocal
    private static void prepare(boolean quitAllowed) {
        sThreadLocal.set(new Looper(quitAllowed));
    }
    // Construct a message queue
	 private Looper(boolean quitAllowed) {
        mQueue = newMessageQueue(quitAllowed); mThread = Thread.currentThread(); }}Copy the code

Prepare constructs an instance of Looper for the calling thread and puts it into a ThreadLocal.

Stars. Loop analysis

class Looper{
	 public static void loop(a) {
      	// Get an instance of the current thread
        final Looper me = myLooper();
       
		// Get Looper corresponding to MessageQueue
        final MessageQueue queue = me.mQueue;

        boolean slowDeliveryDetected = false;

        for (;;) {
        	// Fetch the message queue
            Message msg = queue.next(); // might block
            // Exit the loop if there is no message
            if (msg == null) {
                return;
            }

            // In Looper you can customize a logger instance, and then you can count
            // The main thread is taking too long
            final Printer logging = me.mLogging;
            if(logging ! =null) {
                logging.println(">>>>> Dispatching to " + msg.target + "" 
                        msg.callback + ":" + msg.what);
            }
			  // Assign the message to the callback
           	  msg.target.dispatchMessage(msg);
                if(observer ! =null) {
                    observer.messageDispatched(token, msg);
                }

             // You can customize a log print
            if(logging ! =null) {
                logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); }}}}Copy the code

We can roughly figure out that Looper’s loop function keeps retrieving Message objects from MessageQueue and distributing them.

A technique that can be used to count the time spent on the main thread

class MainActivity : AppCompatActivity() {
    class MyPrinter : Printer {
        var startTime = System.currentTimeMillis()
        override fun println(x: String) {
            // The event distribution handler starts
            if (x.contains(">>>>> Dispatching to")) {
                startTime = System.currentTimeMillis()
            }
            // The event distribution handler starts
            if (x.contains("<<<<< Finished to ")) {
                val endTime = System.currentTimeMillis()
                // The processing time of a main thread is duration ms
                val duration = endTime - startTime    
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // Get the main thread Looper and mount your own print
        var myLooper = Looper.getMainLooper();
        myLooper.setMessageLogging(MyPrinter())
    }
}
Copy the code

MessageQueue analysis 1

I know from the above that Looper will constantly call MessageQueue’s Next

class MessageQueue{

	// a class that identifies the state used by native code,
	//所以java
	private long mPtr; // used by native code
	
    Message next(a) {
        // Return here if the message loop has already quit and been disposed.
      	// If the flag bit is 0, then the message loop has ended
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
		
		// Number of pending messages
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // Next polling time
        int nextPollTimeoutMillis = 0;
        // Fetch messages in an infinite loop
        for (;;) {
        	// If the latest message now takes some time to run
            if(nextPollTimeoutMillis ! =0) {
            	// Refresh all binder commands into the kernel
                Binder.flushPendingCommands();
            }
			// Call the Linux epoll function to hibernate
			// Epoll is a Linux API, so we don't need to go into the details here
			// When there is information to process or sleep expires or is awakened by the kernel
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // Message queue header
                Message msg = mMessages;
                // The synchronization barrier will be skipped later in the tutorial
                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 the expected running time of the current message is not reached, set a sleep time
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // The list returns after the chain is broken
                        mBlocked = false;
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue"."Returning message: " + msg);
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

               / /... slightly}}}Copy the code

Message is the data mechanism of the queue node. Let’s look at its properties:


public final class Message implements Parcelable {
   	// User-defined message id
    public int what;
    /** * Target message delivery time. This time is based on {@link SystemClock#uptimeMillis}.
     */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;
	
	// The corresponding handler identifies the message as a synchronization credential if this is null
    @UnsupportedAppUsage
    /*package*/ Handler target;
    
	// If you want to call a custom function directly, then assign this function
    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // The next node in the list
    @UnsupportedAppUsage
    /*package*/ Message next;
}
Copy the code

Its internal linked list structure is roughly as follows:



One particular thing to note is that linked lists are sorted by priority,whenThe smaller, the more in front.whenIndicates the time to execute)

Let’s look at the operation function for MessageQueue joining the team

class MessageQueue{
    PollOnce () is passed a non-zero timeout parameter to indicate that the message queue is blocked when calling the next function
    private boolean mBlocked;
   final long ptr = mPtr; // Exit the message loop when mPtr is 0, and initialize a non-zero value when constructing MessageQueue
	//when indicates the time when the message is to be delivered
    boolean enqueueMessage(Message msg, long when) {
    	// A synchronous lock prevents multiple threads from being placed concurrently
        synchronized (this) {
        
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // The current queue is empty and has not been initialized
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
            In most cases we do not need to wake up the queue unless there is a synchronization barrier at the head of the queue and the message is the first asynchronous message
            	// More on the synchronization barrier
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
              
               // Inserts into the queue according to the priority of the message
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                  
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr ! = 0 because mQuitting is false.
            // Since we previously judged that the current message queue did not exit (McOntract is false), we assume that
            //mPtr! = 0
            if (needWake) {
           	   //mPtr==0 Indicates exit
           	   //nativeWake uses Linux underlying epool to wake up the queue and uses mPtr to inform the status of the woken message queuenativeWake(mPtr); }}return true; }}Copy the code

Handler analysis

To build a bridge to communicate with other threads,Android provides a Handler that lets you communicate and switch threads using Looper.

Directly look at the following cases (online wheel too much is not introduced) :

class MainActivity : AppCompatActivity(a){
    private class MyHandler(activity: MainActivity) : Handler(a) {
        private val mTarget: WeakReference<MainActivity>
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
        init {
            mTarget = WeakReference<MainActivity>(activity)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // The child thread communicates with the main thread
// val myHandler = MyHandler(this)
// val thread = Thread(Runnable {
// myHandler.sendEmptyMessage(1)
/ /})

        // The main thread communicates with the child threads
        var myHandler: MyHandler? = null
        val thread = Thread(Runnable {
            myHandler = MyHandler(this) Looper.prepare() Looper.loop() }).start() myHandler? .sendEmptyMessage(0);
        // Please destroy the activity or exit it if you don't need itmyHandler? .looper? .quit() }fun onDestry(a){}}Copy the code

The constructor


public interface Callback {
    public boolean handleMessage(Message msg);
}

class Handler{
  //callback: When the Message object Message has no specific callback, handler. callback is used first, and then Handler's own handleMessage function is called if the return value is false
  //async: whether to enable synchronization barrier after the text is described
  public Handler(Callback callback, boolean async) 
}
Copy the code

We know that message distribution calls the following code:

class Looper{
	 public static void loop(a) {
			  // Assign the message to the callback
			  // MSG is Message, target is Handlermsg.target.dispatchMessage(msg); }}}Copy the code

So we end up sending messages to handlers

class Handler{
 public void dispatchMessage(Message msg) {
 		If Message has a specified callback, call it directly
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
        	// The constructor passes the callback if it is set to continue calling its own handleMessage based on the return value
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; }}// This function is usually overwritten by developershandleMessage(msg); }}// Call the callback defined by Message and return directly
     private static void handleCallback(Message message) { message.callback.run(); }}Copy the code

So you get the following callback chain:

Synchronization barrier

Android can only update the UI on the main thread. We normally redraw the screen at 16ms for flow, but what if there are an infinite number of event queues in the event loop? This leads to UI drawing delays, which can seriously impact user experience issues.



Consider this situationAndroidBy default, all of our messages are synchronous.

Simply call message.setasynchronous to become an asynchronous Message.

class Message{
  public boolean isAsynchronous(a) {
        return(flags & FLAG_ASYNCHRONOUS) ! =0;
    }
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else{ flags &= ~FLAG_ASYNCHRONOUS; }}}Copy the code

When asynchronous messages become asynchronous, synchronization barriers need to be inserted so that all synchronous messages are not executed and only asynchronous messages are executed first.

Let’s go back to the asynchronous processing part of MessageQueue

class MessageQueue{
  Message next(a) {
      
       / /... slightly
                // If the current header is target is null then a synchronization barrier flag is inserted
                // Then poll to an asynchronous message and return processing
                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());
                }
               

               / /... slightly}}}Copy the code

Schematic diagram:



We look at theAndroidHow does the system use this mechanism to refreshuithe

class ViewRootImpl{
	// This function starts to facilitate root view rendering operations
  void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Place a barrier
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if(! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); }}}Copy the code

Specific insert barrier code, because only simple linked list operation is not explained

class Looper{
 public int postSyncBarrier(a) {
        returnmQueue.enqueueSyncBarrier(SystemClock.uptimeMillis()); }}class MessageQueue(a){
  int enqueueSyncBarrier(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;
            if(when ! =0) {
                while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            returntoken; }}/ / remove
 void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while(p ! =null&& (p.target ! =null|| p.arg1 ! = token)) { prev = p; p = p.next; }if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if(prev ! =null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null|| mMessages.target ! =null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr ! = 0 when mQuitting is false.
            if(needWake && ! mQuitting) { nativeWake(mPtr); }}}}Copy the code

IdleHandler

MessageQueue Let’s revisit the next function of this class

//MessageQueue.java
Class MessageQueue{
    private IdleHandler[] mPendingIdleHandlers;

  Message next(a) {
 
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
               
               
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! =null && msg.target == null) {
 					// If there is a message, distribute it and return it
 				}

             // If there is no message then the object in the IdleHandler collection is called
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
        		// Call the function
               keep = idler.queueIdle();
               
				// If the function returns false, it will be removed
                if(! keep) {synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

          
            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0; }}}Copy the code