Just so you know

Above my own Handler mechanism Handler mechanism for some exploration and thinking, the design intention of the Handler has its own understanding and thinking, so this article we will start from the Android system Handler mechanism source, Take a look at the android system Handler mechanism design, compared to the above article I wrote the Handler mechanism.

The logic is as follows:

  1. Analyze Handler from source code.
    1. Understand the nature of synchronous barriers and asynchronous messages.
    2. How do I use ThreadLocal to keep threads unique?
  2. Developer’s use of the Handler mechanism
  3. The usual main thread problem
  4. This section describes problems when using the Handler.
  5. Common interview Handler questions.
  6. conclusion

Main thread Handler creation and message handling

Do you know why Activity lifecycle functions such as onCreate() and onStart() run in the main thread? This question will be answered in the creation of the main thread Handler and the analysis of the message processing process below.

Analysis main thread Handler creation and message processing source code

The main thread is created and used in the same way that the Handler mechanism is used in the article on customizing a Handler mechanism: Looper is created first, then messages are sent to the message container in Looper, and finally Looper starts an infinite loop to fetch/process messages.

//ActivityThread.java
public static void main(String[] args) {.../ / to create stars
    Looper.prepareMainLooper();
  	Add a series of messages to Looper's message queue to start the App.
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
		/ / get the Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
		// Start looper to start an infinite loop of processing messagesLooper.loop(); . }Copy the code

Main thread Looper creation process

Here’s a quick look at the looper creation process, with two key points.

  1. A Looper is created using the constructor and the allow exit argument is passed false to indicate that the Looper does not allow the developer to call the active exit function.
  2. Take advantage of ThreadLocal objects to ensure that a single thread has only one Looper object instance. The features of ThreadLocal are described later.
	// Looper.java
	// Create Looper for the current thread.
	public static void prepareMainLooper(a) {
   		// Create Looper. False indicates that Looper does not allow developers to call exit functions to exit the Looper loop.
        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 the thread already has one Looper, it is not allowed to create multiple Looper.
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
      	// Create a Looper instance, pass in whether to allow the Looper to exit the loop, and add it to the thread's ThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

		// Looper constructor
    private Looper(boolean quitAllowed) {
      	// Initialize MessageQueue, passing in whether to allow exit function
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

		// Get the thread's Looper
    public static @Nullable Looper myLooper(a) {
        return sThreadLocal.get();
    }
Copy the code

Loop () process analysis

The loop() method also has a few key points.

  1. MessageQueue. Next () method. The main function of this method is to obtain messages from MessageQueue.
  2. Logging, which logs before and after message processing, is a mechanism used by many performance monitoring frameworks to monitor FPS values.
  3. Message handling, the handler.dispatchMessage () function, is often asked about in interviews. This is mainly about the order in which messages are processed when a Callback is defined in Message and when a Callback is defined in Handler.
public static void loop(a) {
		/ / death cycle
    for (;;) {
      	// key !!!! Key !!!! Key !!!!!! This queue is the MessageQueue in the Looper constructor
      	// The next() method gets the message from MessageQueue. If there is no message in this function, Linux will be called
        // The epoll mechanism falls asleep and wakes up waiting for messages to join MessageQueue.
        Message msg = queue.next(); // might block
        if (msg == null) {
            MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue: MessageQueue.
            return;
        }
        // Outputs messages before the message is executed, which can be used in FPS monitoring
        final Printer logging = me.mLogging;
        if(logging ! =null) {
            logging.println(">>>>> Dispatching to " + msg.target + "" +
                    msg.callback + ":"+ msg.what); }...// key!! Target is the Handler instance that sends messages to MessageQueue.
        // Process MSG messages.msg.target.dispatchMessage(msg); .Println () = println();
        if(logging ! =null) {
            logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); }}}Copy the code

Messagequyue.next () method analysis

There are several highlights in the next() method.

  1. Epoll mechanism. About epoll mechanism, this is mainly Linux knowledge, I also have a little knowledge, here recommended series of articles, written can be worth a look.

  2. How do I handle delayed Message messages and execute them sequentially?

    This process is divided into two steps. The first step is to sort messages in MessageQueue according to the time of adding messages and the delay time. Messages with earlier adding time and short delay time are placed at the front of the queue. The second step is that after MessageQueue gets the Message to be executed, it will judge whether the execution time is current or not. If not, it will calculate the time difference and use the time difference to invoke the epoll mechanism to enter regular sleep.

  3. Synchronization barrier concepts and synchronization message processing.

    Synchronous barriers are designed to prioritize asynchronous messages, such as messages that render UI, respond to user actions, etc. When there is an asynchronous Message that needs to be executed first, insert a Message to the header of the MessageQueue whose target(i.e., the Handler that processes the Message) is null (set synchronization barriers). And set the flag of whether the Message to be executed isAsynchronous to true(isAsynchronous() returns true). In the subsequent next() method, synchronous messages are ignored when synchronization barriers are encountered, and asynchronous messages are selectively executed first.

  4. Handle looper exit.

    The exit is also handled in MessageQueue, where the exit of MQ triggers the exit of Looper. The main process is to remove all MSGS from the queue. Whether to remove the MSG to be processed is the point of de-differentiation. There are two ways to remove the message: one is to keep the MSG to be executed and exit after the message is executed; the other is to remove all MSG and exit directly.

  5. Use of IdleHandler.

    When no subsequent message is executed in the MessageQueue, an IdlerHander is executed if there is an IdlerHander, i.e. when the Handler is idle.

    Also note that the same IderHander can be triggered multiple times and we need to deal with this. For example, the IderHandler interface is implemented. QueueIdle () returns false, and the IderHandler is removed when the execution is complete.

// MessageQueue.java
Message next(a) {
    // mPtr is a c++ layer pointer, which is set to 0 when MessageQueue needs to exit
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
		/ / IdleHandler quantity
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
  	// Use the epoll mechanism to sleep time
    int nextPollTimeoutMillis = 0;
    for (;;) {
				// Use epoll mechanism to go to sleep,
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // Try to get Message
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
          	// If the current Message is not synchronized, MSG. Target is empty
            if(msg ! =null && msg.target == null) {
                // Find synchronous message execution, flag MSG. IsAsynchronous ()
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());
            }
            if(msg ! =null) {
              	MSG. When is assigned when the message is sent
                if (now < msg.when) {
                    // Whether the current message has reached the execution time, not to calculate the epoll sleep time
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    / / to? Return to the target MSG and schedule the next MSG.
                    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();
                    returnmsg; }}else {
                // No more MSG that assigns this value to -1 will fall asleep waiting to be awakened in the nativePollOnce() function.
                nextPollTimeoutMillis = -1;
            }
            // If the exit function is invoked (the main thread does not allow exit), the exit process is processed and Null is returned. In Looper, Null is returned and the thread exits the loop().
            if (mQuitting) {
                dispose();
                return null;
            }
						// Attention!! Can run up to here, when there are no more messages in MQ
            // Count the number of idleHandlers, ready to trigger
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // There is no IdleHandler to execute for the next loop.
                mBlocked = true;
                continue; }... }// Loop through the added IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false; .// Determine whether to remove the IdleHandler
            keep = idler.queueIdle();
            if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}... }}Copy the code

Synchronous barriers and asynchronous messages

Above we have explained what a synchronization barrier is and what it means to add a synchronization barrier. The reason of obstacles and the design is to give priority to perform some news, here is updated in the system is the representative of the UI, it can go to the ViewRootImpl. ScheduleTraversals () function (the View of three steps where oh) and UI update mechanism, here is not to do. Here we see MessageQueue. PostSyncBarrier () function, this is the place where adding synchronization barrier, at the same time combined with a step processing logic of MSG.

Another way to set up asynchronous messages is simply to call message.setasynchronous () and pass in true (logic explained in the previous step).

// MessageQueue.java
private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
      	/// point!! MSG here does not have traget and is reflected in the processing logic.
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
				// Add MSG, which acts as a synchronization barrier, to the queue header
        returntoken; }}Copy the code

ThreadLocal

First, make clear what the function of this class is designed and what kind of problem it solves.

The ThreadLocal class appeared in JDK 1.2 before the locking mechanism provided by Java’s synchronized keyword was optimized (no lock upgrades, direct heavyweight locking). Synchronized provides thread-safety overhead when certain variables in a program can only be used by one thread, while ThreadLocal provides a mechanism to ensure that a variable can only be accessed by one thread without locking. Easy to use, as long as set()/get() one can.

  • ThreadLocal is simply a utility class that provides interfaces to set(), get(), remove() and other operation functions.
  • ThreadLocal. ThreadLocalMap really save the data.
  • Thread.threadlocals member variable, which is accessed by thread.currentThread () by ThreadLocal, ensuring that other threads cannot access it.

About memory leaks in use

This is a common interview question. Because the ThreadLocal. ThreadLocalMap save data build Entry key value (TheadLocal) using weak references, when as a key ThreadLocal is recycled, holding a ThreadLocal hasn’t stopped at the time, If a reference to the value is provided by thread.threadlocals, the value object instance with an empty key cannot be reclaimed, resulting in memory leaks.

Memory leak problem recommended reading > www.cnblogs.com/aspirant/p/…

Detailed analysis Thradlocal > www.cnblogs.com/fsmly/p/110…

How do I use the Handler mechanism?

Using the process

In daily development work, the main thread Handler is used for most of the work, and is rarely used in other threads. Of course, it’s easy to use in just three steps.

  1. Looper.prepare()The Looper/MessageQueue function creates Looper/MessageQueue. Loopers created using this method are exit points.
  2. Create handlers that handle sending and processing messages.
  3. Looper.loop() starts an infinite loop of processing messages.

How to exit Looper? Why can’t the main thread Handler Looper exit?

exit

Just call looper.mylooper ().quit() or looper.mylooper ().quitSafely(). The difference is described above.

Why can’t the main thread Handler exit?

The developer cannot call the main thread Looper exit method, which will throw an exception.

The reason is that Looper’s dead-loop function is not only to process messages, but also to ensure that the main thread is always running without external forces. IOS/Windows and other development frameworks have similar mechanisms.

The exit of the Looper loop means that the main thread of the App exits and the App exits.

Use of the Handler mechanism?

HandlerThread

This class inherits from Thread and creates an internal Handler that uses the Handler mechanism. After start(), Looper is started to process messages in an endless loop. Looper can exit and messages are processed in the Thread. You can use it to perform ordered tasks!

IntentService

When onStart() is triggered, a Message is added to the MessageQueue in the HandlerThread. The built-in Handler immediately calls stopSelf() to stop the Service. You can use it to perform tasks in the background. After the execution is completed, no management is required and the task is automatically stopped. Of course, remember to register the manifest file as usual for a Service.

Those questions about the main thread

The child thread updates the UI

Only the original thread that created a view hierarchy can touch its views. The system throws this error when we manipulate the UI on a child thread. To be clear, this error does not mean that the UI cannot be updated in the main thread, but only in the thread that created the ViewRootImpl instance. This error is raised by the checkThread() function in ViewRootImpl. The purpose of this check is to check whether the current thread is the thread that created the ViewRootImpl. The reason is that after requestLayout () is called, a synchronization barrier is added to the Looper of the current thread. If the current UI update thread is not the one that created the ViewRootImpl, this synchronization barrier will not work. It is highly likely that the subsequent UI will not be updated in response to the user’s actions in the first place.

Child threads can change the UI, either in onCreate() or onResume(). For specific reasons, see the ViewRootImpl creation timing and setContentView() procedure.

TextView child thread checkThread()

Main thread time-consuming operation

Why can’t the main thread perform time-consuming operations? By design, a single thread updates the UI, and if a time-consuming operation is performed on the UI thread, the UI update will be delayed. Therefore, when the UI thread (main thread) performs operations, the system will add a time detection mechanism. If the UI thread does not respond after a certain time, the common ANR warning box will pop up.

ANR is triggered, right? And how did he get caught?

I highly recommend gityuan’s series of articles on this issue.

Understand the ANR trigger principle

What should you be aware of when using the Handler

Handle memory leaks

The problem

The problem with Handler memory leaks is that in the Java language, non-static inner classes and anonymous inner classes hold references to an external class, which is usually an Activity. Memory leaks are most likely to occur when a delayed Message is not fired or when a child thread is running a time-consuming task and holding Handler references.

The solution

  1. Remove time-consuming messages and stop processing time-consuming tasks before Activity destruction.
  2. Do not use handlers as non-static inner classes and anonymous inner classes.

How do I create a Message correctly

Here, we mainly use optimization methods to optimize when Message creation is frequent. There are several ways to optimize objects in the Java language.

The obtain() function in Message already provides a pool of objects for optimization.

Common Interview Questions

Epoll mechanism

This is the recommended article, I am still studying, if I have my own understanding, there will be an article to talk about.

www.jianshu.com/p/dfd940e7f…

Synchronization barrier

Have told before oh, do not remember ????

Slide up for one more look!

What about thread synchronization/thread switching?

First, Synchronized is used in Both Looper and MessageQueue to ensure thread safety.

Thread switching? Threads share resources, add Message to MessageQuque in target thread, and then the target thread completes thread switch.

The mechanism and use of IdleHander?

Add IdleHander to MessageQueue by Looper.

The mechanism is to invoke IdleHander execution when there is no Message in the MessageQueue (i.e., idle) or when a delayed Message is executing. Attention! The timing of this execution is somewhat ambiguous.

If queueIdle() of IdleHander returns true, the IdlerHander is kept. If queueIdle() returns false, the IdlerHander is removed.

The system is used in the following places

Recommended readingwww.wanandroid.com/wenda/show/…

conclusion

The Handler mechanism is designed to provide a single-threaded message processing mechanism, and the single-threaded update mechanism of the UI framework enables the Handler to perform this task naturally. This is the focus of the Handler, the essence of the Handler. Once the essence is clear, the approach to implementing this essence, such as other synchronous barriers, asynchronous messages, and ThreadLocal, can be quickly divide-and-conquer.

In short, FOR the learning path of Adnroid FRmework, I still recommend the way of thinking, relying on source code and stepping on the shoulders of giants to learn.