1.Handler source code overall framework

Core principles of Handler

Why is There little thread problem in Android, and how to implement communication between threads?

What development issues will data communication present?

  1. How do threads communicate with each other?

The scheme implemented by Handler communication is actually a memory sharing scheme

  1. Why don’t threads interfere with each other?

Excellent memory management design

  1. whywait/notifyNot much use?

This is because the Handler has already wrapped this functionality into the Linux layer

The entire process of Handler

Handler. sendMessage sends messages to MessageQueue, and Looper calls its loop() function to drive MessageQueue to poll each Message in MessageQueue. Execution begins when Message reaches the time it can execute, and then message-bound handlers are called to process the Message. The general process is shown in the figure below:

The handler mechanism is a conveyor belt operation mechanism.

  1. MessageQueue is like a track.
  2. Thread is like the driving force behind it, which is that all of our communication is based on threads.
  3. The rolling of the conveyor belt requires a switch to power the motor, which is equivalent to our loop function, and the for loop in this loop will poll with continuous rollingmessageQueue .
  4. Message is our shipment.

Things to do in child threads:

handler->sendMessage->messasgeQueue.enqueueMessage   // Message queue Insert node of queue
Copy the code

Things to do in the main thread:

Looper.loop ()-> messasgequeue.next ()-> handler.dispatchMessage()->handler.handleMessage ()Copy the code

The architect sees the Handler

Producer-consumer design pattern, memory sharing principle, Theadlocal principle

Producer-consumer model: Producers and consumers share the same storage space for the same period of time. Producers add data to storage space and consumers remove data from storage space.

Advantages of Design

Ensure the order in which data is produced and consumed (via MessageQueue, first in, first out) – both producers (child threads) and consumers (main thread) rely only on the buffer (handler), and producers and consumers do not hold each other, leaving no coupling between them.

Handler analysis mission

  1. How is thread crossing implemented?
  2. How does Handler manage that shared memory?
  3. What design thinking can architects learn from this?

2. Source code analysis

  • Hanlder: sends and receives messages
  • Looper: Used to poll message queues, and only one Looper per thread
  • “Message” : Message entity
  • MessageQueue: Message queues are used to store and manage messages

Handler

Handerl’s main functions:

1. Send the MESSAGE

2.dispatchMessage

Looper

The key way to

 / / 1. Initialization
Looper.prepare() 
 //2. Start the loop
Looper.loop()   
for (;;) {
    // Get the message
    queue.next();

}
Copy the code
  1. Looper’s initialization method

    Prepare (Boolean quitAllowed) specifies whether a MessageQueue can be destroyed when a MessageQueue is created.

//1. Method 1: initialize the current thread. The child thread must call this method to start Looper
public static void prepare(a) {
        prepare(true);  // Message queue can quit
}

private static void prepare(boolean quitAllowed) {
    if(sThreadLocal.get() ! =null) {  // Not null indicates that the current thread has created a Looper
        throw new RuntimeException("Only one Looper may be created per thread");  // Each thread can create only one Looper
    }
    sThreadLocal.set(new Looper(quitAllowed));  // Create Looper and set it to sThreadLocal so that get is not null
}

//2. Method 2: Initializing the main thread is called in ActivityThread
@Deprecated
public static void prepareMainLooper(a) {
    prepare(false);  // Message queues cannot quit. The main thread cannot be destroyed
    synchronized (Looper.class) {
        if(sMainLooper ! =null) {
            throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

The Looper object is a TheadLocal, that is, there is only one Looper object per thread to ensure the uniqueness of Looper. ThreadLocal thread isolation utility class:

  1. Create MessageQueue and the binding of Looper to the current thread

    private Looper(boolean quitAllowed) { 
        mQueue = new MessageQueue(quitAllowed);// Create MessageQueue
        mThread = Thread.currentThread(); // The current thread binding
    }
    Copy the code
  2. Open loop

     public static void loop(a) {
            final Looper me = myLooper();   // sthreadLocal.get () is called to get the Looper object
            if (me == null) {  // If Looper is empty, an exception is thrown
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            if (me.mInLoop) {
                Slog.w(TAG, "Loop again would have the queued messages be executed"
                        + " before this one completed.");
            }
    
            me.mInLoop = true;
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            // Allow overriding a threshold with a system prop. e.g.
            // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow".0);
    
            boolean slowDeliveryDetected = false;
            // This is an infinite loop, with messages continually fetched from the message queue
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // There is no message in the queue until Handler sendMessage enqueueMessage
                    // There are messages in the queue
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if(logging ! =null) {
                    logging.println(">>>>> Dispatching to " + msg.target + "" +
                            msg.callback + ":" + msg.what);
                }
                // Make sure the observer won't change while processing a transaction.
                final Observer observer = sObserver;
    
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
    
                if(traceTag ! =0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                Object token = null;
                if(observer ! =null) {
                    token = observer.messageDispatchStarting();
                }
                long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
                try {
                    /// MSG. Target is the bound Handler, as described in the Message section below, where Handler begins
                    msg.target.dispatchMessage(msg);
                    if(observer ! =null) {
                        observer.messageDispatched(token, msg);
                    }
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } catch (Exception exception) {
                    if(observer ! =null) {
                        observer.dispatchingThrewException(token, msg, exception);
                    }
                    throw exception;
                } finally {
                    ThreadLocalWorkSource.restore(origWorkSource);
                    if(traceTag ! =0) { Trace.traceEnd(traceTag); }}if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false; }}else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true; }}}if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                if(logging ! =null) {
                    logging.println("<<<<< Finished to " + msg.target + "" + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if(ident ! = newIdent) { Log.wtf(TAG,"Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + ""
                            + msg.callback + " what="+ msg.what); } msg.recycleUnchecked(); }}Copy the code

MessageQueue

The main function of MessageQueue

  1. Constructor of MessageQueue

    MessageQueue(boolean quitAllowed) { 
        //mQuitAllowed Determines whether the queue can be destroyed. The queue cannot be destroyed. False is passed in the quit() method of MessageQueue
        mQuitAllowed = quitAllowed;        
        mPtr = nativeInit();    
    }
    Copy the code
  2. Adds messages to the message queue

    MessageQueue.enqueueMessage();
    Copy the code
  3. Get the message from the message queue, using the for loop

    MessageQueue.next();
    Copy the code

Queue: Sorted by time, when the queue is full the queue (a priority queue) blocks until the user retrieves the message via Next. When the next method is called, MessagQueue is notified that messages can be queued.

Queue exit: Looper.loop() starts the poller to poll the queue. The message is fetched when it reaches execution time; When messageQueue is empty, the queue will be blocked. When the Message queue calls enQueue Message, the queue will be notified to fetch the Message and stop blocking.

The insertion method when a message is queued

The insertion sort algorithm is used to sort the priority queue

Message

Create a Message

You can directly new Message, but there is a better way to message.obtain. Because you can check if there are any messages that can be reused, overreuse avoids creating and destroying too many Message objects to optimize memory and performance.

public static Message obtain(Handler h) {         
    Message m = obtain();// Call the overloaded obtain method
    m.target = h;// and bind to the handler that creates the Message object
    return m;     
} 
 
public static Message obtain(a) {         
    synchronized (sPoolSync) {//sPoolSync is an Object Object used for thread safety synchronization
        if(sPool ! =null) {//sPool is the type of Message that is passed by handler dispatchMessage and recycleUnchecked for reuse
             Message m = sPool;                 
             sPool = m.next;                 
             m.next = null;                 
             m.flags = 0; // clear in-use flag                 
             sPoolSize--;                
             returnm; }}return new Message();     
} 
Copy the code

Binding of Message to Handler

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.

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

Handler sends messages

There are many overloaded ways for the Handler to send messages, but there are only two main ways. SendMessage (Message) calls sendMessageDelayed through a series of overloaded methods, sendMessage calls sendMessageDelayed, sendMessageAtTime continues, enqueueMessage continues, The enqueueMessage method of messageQueue is called and the message is stored in the messageQueue, which is eventually fetched by Looper and sent to Handler’s dispatchMessage for processing.

We can see in the dispatchMessage method:

  1. messageIn thecallbackCallback is a Runnable object that is called if callback is not nullcallbacktherunMethods;
  2. Otherwise the judgemCallbackIs it empty,mCallbackClass in the Handler constructor;
  3. In the main thread, the new constructor is null, so the following constructor is executed directlyhandleMessage()Methods.
public void dispatchMessage(Message msg) {     
    if(msg.callback ! =null) {Callback is not null when initialized in the message constructor or when handler.post(Runnable) is used
         handleCallback(msg);     
    } else {         
         if(mCallback ! =null) {//2. MCallback is a Callback object created by a Callback handler with no arguments. This attribute is null and this section is not executed
              if (mCallback.handleMessage(msg)) {                 
                  return;             
              }         
         } 
         handleMessage(msg);//3. Finally execute the handleMessage method}}private static void handleCallback(Message message) {         
    message.callback.run();     
}
Copy the code

In the handleMessage(Message) method, we can take the Message object and process it according to our needs, and the whole process of the Handler mechanism ends.

3. Design highlights

Share the root of handler delay

Highlight 1: Enjoy yuan mode

RecycleUnchecked Calls in looper.

If it is new Message (), memory will be allocated and then reclaimed, resulting in memory fragmentation. If objects are frequently generated, memory jitter will occur and OOM will be generated. Message of Handler adopts the element design to achieve memory overcommitment

The difficulties in one: nativePollOnce/NativeWake

Question # 2: When will Looper quit

Creating a Looper in a child thread often causes a memory leak because the Looper is not released

4. How to ensure thread safety?

The Handler is used for communication between threads, but it is not just used for UI processing at all. It is the framework for communication throughout the app, as you can see in ActivityThread. The Handler is so important that its thread safety is critical. How does it keep its thread safety?

Handler mechanism in the most important class MessageQueue, this class is all the message storage warehouse, in this warehouse, how we manage messages, this is a key point. There are two points about message management:

  1. Message entry (enqueueMessage)
  2. Message out (NEXT), so these two interfaces are the primary thread safe gateways.

EnqueueMessage source code:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // Where the lock starts
        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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 {
                // Inserted within the middle of the queue. Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                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.
            if(needWake) { nativeWake(mPtr); }}// Where the lock ends
        return true;
    }
Copy the code

A synchronized lock is a built-in lock, that is, the timing of lock unlock is controlled by the system. This lock means that all threads calling the same MessageQueue object are mutually exclusive, whereas in our Handler, one thread corresponds to a unique Looper object. Looper, in turn, has only one unique MessageQueue (also described above). Therefore, our main thread has only one MessageQueue object, that is, when all the child threads send messages to the main thread, the main thread will only process one message at a time, and the rest will wait, so that the MessageQueue will not be cluttered.

Also look at the next function

 @UnsupportedAppUsage
    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) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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 (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();
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0; }}Copy the code

Next function:

I fetch messages from the thread, and every time I fetch messages from the head of the queue, does it make sense for it to be locked? No, we must lock next because synchronized (this) protects all blocks that this is accessing, ensuring that next and enqueueMessage are mutually exclusive. In this way, the messagequeue can be truly ordered when multiple threads access.

Summary: This area is often asked by interviewers, and they will extend the question to you based on this point, so, this area please pay attention to.

5. Synchronization barriers for message mechanisms

Messages are sorted by execution time and then stored in queues, so they can only be retrieved from the queue head. So here’s the problem! What about messages that need urgent processing?

Synchronous barrier, view drawing using juejin.cn/post/684490… The concept of a synchronization barrier is easy to overlook in Android development because it’s so rare in our normal development that it’s easy to overlook.

After the above learning, we should know that all the messages of the thread are put into the same MessageQueue. When retrieving the message, it is mutually exclusive, and can only fetch the message from the header, while adding the message is sorted according to the order of execution of the message. Then the problem comes, the elimination within the same time range, If it needs to be executed immediately, then what do we do? Normally, we wait until the queue polls me, and then the day lily is cold. Therefore, we need to give urgent messages a green channel, and this green channel is the concept of synchronization barrier.

What is the 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?

//MessageQueue.postSyncBarrier()

    @UnsupportedAppUsage
    @TestApi
    public int postSyncBarrier(a) {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            // Get messages from the message pool
            final Message msg = Message.obtain();
            msg.markInUse();
            // Here it is!! When the Message object is initialized, target is not assigned, so target==null
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if(when ! =0) {
                while(p ! =null && p.when <= when) {
                    // Prev is not null if T is not 0 and some time in the current synchronization message is less than Tprev = p; p = p.next; }}// Insert MSG chronologically to the appropriate position in the message queue (linked list), depending on whether prev is null
            if(prev ! =null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            returntoken; }}Copy the code

You can see that the Message object is initialized without assigning a value to target, so the source of target == null is found. The insertion of the message above is commented accordingly. 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 messaging mechanism, you should know that the final processing of the message is in the message poller looper.loop (), and the loop() loop calls messagequyue.next () to fetch the message from the MessageQueue.

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

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1. If nextPollTimeoutMillis=-1, the block will not timeout.
        // 2. If nextPollTimeoutMillis=0, it will not block and return immediately.
        If nextPollTimeoutMillis>0, block nextPollTimeoutMillis for milliseconds (timeout).
        // Returns immediately if there is a program wake up during
        int nextPollTimeoutMillis = 0;
        // Next () is also an infinite loop
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                // Get the time since system boot
                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 and needs to loop back to find the first asynchronous message
                if(msg ! =null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null && !msg.isAsynchronous());
                }
                if(msg ! =null) {
                    // If there is a message that needs to be processed, check whether the time is up. If not, set the blocking time
                    // Scenarios such as common postDelay
                    if (now < msg.when) {
                         // Calculate how long until the execution time to assign to nextPollTimeoutMillis,
                         // Indicates that the nativePollOnce method will wait for nextPollTimeoutMillis to return
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Get the message
                        mBlocked = false;
                        // List operation, get MSG and delete the node
                        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 the received message
                        returnmsg; }}else {
                    NextPollTimeoutMills resets without a message
                    nextPollTimeoutMillis = -1; }.../ / to omit}}Copy the code

As can be seen from the above, when the synchronization barrier is enabled on the message queue (marked as msg.target == NULL), the message mechanism preferentially processes asynchronous messages. In this way, synchronization barriers act as filters and prioritizers.

Here is a simple illustration with a schematic diagram:

As shown in the figure above, there are synchronous and asynchronous messages (yellow) and a wall —- synchronization barrier (red) in the message queue. With synchronization barriers, msG_2 and msg_M asynchronous messages are processed preferentially, while msG_3 synchronous messages are not. So when can these synchronous messages be processed? Remove the synchronization barrier by calling removeSyncBarrier().

Synchronization barrier usage scenarios

It seems that synchronization barriers are rarely used in everyday application development. So what are the use scenarios of synchronization barriers in system source code? UI update messages in the Android system are asynchronous messages and need to be processed first.

For example, ViewRootImpl#scheduleTraversals() is called in many places such as draw, requestLayout, invalidate, etc.

//ViewRootImpl.java
    void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Enable synchronization barrier
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Send asynchronous messages
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
Copy the code

PostCallback () eventually reached the ChoreographerpostCallbackDelayedInternal () :

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);   // Send asynchronous messagesmHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

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.

Finally, ViewRootImpl#unscheduleTraversals() is called when the synchronization barrier is removed.

void unscheduleTraversals(a) {
        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 Summary

Synchronization barriers are set up to easily handle asynchronous messages with higher priority. When we call handler.getlooper ().getQueue().postsyncBarrier () and set the setAsynchronous(true) of the message, target is null and the synchronization barrier is enabled. When the message poller Looper loops through messages in loop(), if synchronization barriers are turned on, asynchronous messages are processed in preference to synchronous messages.

Interview questions

  1. How many handlers are there in a thread?

    A thread can have N handlers

  2. How many loopers are there in a thread? How to guarantee?

    A thread has only one Looper, as determined by ThreadLocal.

  3. Handler memory leak cause? Why didn’t any of the other inner classes say there was a problem?

    An external method, the method that executes the Activity, is executed in the Handler’s handlerMessage method, which holds the Activity object.

    Why is there no memory leak in recycleVIew adpater’s ViweHolder static inner class? Life cycle issues.

    Because the message tag in handler’s messageQueue holds the handler, it holds the Activity. Next at MesageQueue is always executing and has a long life cycle.

    enqueueMessage{ msg.target = this; } GC: JVM reachability algorithm

  4. Why can the main thread use new Handler? What does the new Handler have to do if I want to prepare it in a child thread?

    Looper is created in ActivityThread. Looper.prepare and looper.loop() are required in the child thread.

  5. What is the Looper maintained in the child thread when there is no message in the message queue? What’s the use?

    Looper.quit() can exit the loop

    Quit: Wake up thread -> messagequeue -> NULL -> exit loop

    Blocked when there is no message

    Two aspects of blocking in handler? 2) messageQueue is empty, infinite wait, add a message can wake up

  6. Since there can be multiple handlers to add data to a MessageQueue (each Handler may be on a different thread when sending messages), how is it internally thread-safe? What about getting messages?

    Synchronized: is a built-in lock, determined by the JVM.

    Synchronize (this) : All functions and code blocks within the same Messagequeue object are restricted.

    There is only one place a thread can operate on a MessageQueue: because a thread has only one MessageQueue.

    Fetching messages, adding messages, and exiting all use locking

  7. How should we create Message when we use it?

    Message.obtain()

  8. Why does Looper loop death not cause apps to freeze?

    The thread does not process any operations, has released CPU resources, and uses wait at the bottom. It is time for the bottom layer to wake up again when there is a message.

  9. Why are messages sent on the child thread and received on the main thread?

    Child thread: The thread in which the calling function that sends the message resides. This function resides in the child thread

    Thread: handler. SendMessage (MSG) – > MessageQueue. EnequeMessage (MSG)

    Main thread Loop () : polls MessageQueue -> MSG

    static final ThreadLocal sThreadLocal = new ThreadLocal();

    All thread sThreadLocal is the same.

7. What is HandlerThread? Why does it exist?

As an Android developer, it’s important to understand the Handler mechanism. In my interview, I found that many people had a good understanding of Handler and Looper mechanisms, but did not know what HandlerThread was.

HandlerThread is a subclass of Thread, which is technically a Thread, except that it creates Loope for us in its own Thread.

Why HandlerThread exists

Easy to use

  • Easy initialization
  • Easy access to thread looper

This ensures thread safety

Prepare (); Loop. Loop ();

@Override public void run(a) {    
    Looper.prepare();    
    Looper.loop(); 
}
Copy the code

What about the way we use Looper in child threads? Look at the code below

Thread thread = new Thread(new Runnable() {    
    Looper looper;    
    @Override    
    public void run(a) {      
        // Log.d(TAG, "click2: " + Thread.currentThread().getName());
        Looper.prepare();        
        looper =Looper.myLooper();        
        Looper.loop();    
    }
    
    public Looper getLooper(a) {        
        returnlooper; }}); thread.start(); Handler handler =new Handler(thread.getLooper());
Copy the code

Is there anything wrong with this code?

  1. When initializing the handler of the child thread, we cannot pass the looper of the child thread to the handler.

    • A. You can initialize the Handler to a Thread
    • B. You can create a separate class that inherits Threads from the class’s objects.

    Either way, but HandlerThread does the work for us.

  2. If thread.getLooper () is called in the above code, the looper may not have been initialized yet.

The above two problems have been solved perfectly by HandlerThread, which is why HandlerThread exists.

Let’s look at HandlerThread source code:

public void run(a) {    
    mTid = Process.myTid();    
    Looper.prepare();     
    synchronized (this) {        
        mLooper = Looper.myLooper();        
        notifyAll();  // At this point, other pending locks are awakened, but the other threads' locks are not executed until the execution is complete
    }    
    Process.setThreadPriority(mPriority);    
    onLooperPrepared();    
    Looper.loop();    
    mTid = -1; 
} 
Copy the code
 public Looper getLooper(a) {
        if(! isAlive()) {return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();  // The lock is released and looper is initialized
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
Copy the code

Its advantage lies in its multithreaded operations, which ensure that we are safe to use Thread handlers.

8.IntentService

Service is used to process time-consuming tasks in the background.

Application requirements: A task is divided into multiple sub-tasks, and the sub-tasks are executed in sequence. The task is considered as a result only after all the sub-tasks are executed

This requirement can be handled by multiple threads, one thread finishing -> the next thread -> the next thread

IntentService will do that for us. And can very good management thread, ensure that a child thread processing work, and is a task to complete, orderly.

-> Service automatically stops: memory is released

Is there anywhere else to use it? So many places have to use fragments what life cycle management, refer to: www.jianshu.com/p/317b2d6bd…