background


In Android development, we almost always rely on threads. But how much do you know about threads? How many unknown secrets are hidden behind its perfect operation? How is it possible for threads to communicate with each other? How Looper, Handler, and MessageQueue work behind the scenes. In this installment, let’s explore the secrets behind the perfect Thread chain, starting with Thread. Note that most of the analysis is in the code, so pay close attention to the code!

Start with the Tread creation process

In this section, we will analyze the Thread creation process step by step. No more talking. Just look at the code.

The starting point for thread creation init()

The private init() method is used to create Thread public constructors. Let's see what we did.
/ * * * *@paramThread group *@paramRunnable is the Runnable student we contact most@paramSpecify the name of the thread *@paramSpecifies the thread stack size */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        // Get the thread currently running
        // The currently running thread is the parent of the thread we want to create
        // The thread we are creating inherits some parameters from the parent thread
        // Note that the thread is still in the original thread, the new thread has not been created.
        Thread parent = currentThread();  
        if (g == null) {
            g = parent.getThreadGroup();            // If ThreadGroup is not specified, the TreadGroup of the parent thread is obtained
        }

        g.addUnstarted();                           // Increment the ready thread counter in ThreadGroup by one. Note that the thread is not actually added to the ThreadGroup at this point.
        this.group = g;                             // Assign the group of Thread instances. From here the thread owns the ThreadGroup.

        this.target = target;                       // Set Runnable to Thread instances. This is what will be executed later when start() is executed.
        this.priority = parent.getPriority();       // Sets the priority weight of the thread to the weight of the parent thread
        this.daemon = parent.isDaemon();            Thread instances are daemons based on whether the parent Thread is a daemon Thread.
        setName(name);                              // Set the thread name

        init2(parent);                              / / what? Another initialization, again the parent thread. No hurry. I'll see you later.

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;                 // Set the stack size for the thread
        tid = nextThreadID();                       // The thread id. This is a static variable that is incremented by calling this method and then used as the id of the thread.
    }
Copy the code

In the init() method of Thread, it is important to get the current running Thread from the virtual machine via a native function such as currentThread().

So when Thread initializes, it is still in the Thread that created it. It’s not hard to guess that Threads in the Java layer simply encapsulate the underlying layer.

The second init2 ()

private void init2(Thread parent) {
        this.contextClassLoader = parent.getContextClassLoader();           // Sets the ClassLoader member variable
        this.inheritedAccessControlContext = AccessController.getContext(); // Set the access control environment
        if(parent.inheritableThreadLocals ! =null) {
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(  // Create ThreadLoacaleMap for Thread instance. ThreadLocaleMap of the parent thread is required in order to make a copy of the variables in the parent thread into the current thread.
            ThreadLocaleMap is an Entry array in which Thread instances store copies of variables.parent.inheritableThreadLocals); }}Copy the code

At this point, our Thread is initialized and several important member variables of Thread are assigned values.

Start thread, go!

Normally, we start a thread like this.

Thread threadDemo = new Thread(() -> {

    });
threadDemo.start();
Copy the code

So what’s the dirty laundry behind Start ()? Is it a distortion of human nature? Or moral decay? Let’s start with start(). Discover the secrets behind Start ().

// As we can see, this method is locked. The reason is to prevent the developer from calling this method on the same Thread instance in another Thread, thereby minimizing the need to throw an exception.
// This method can execute the run() method we passed in Runnable because the JVM called the Run () method of the Thread instance.
public synchronized void start(a) {
        // Check whether the thread state is 0, which means it is a new state, i.e. it has not been started () yet. Throw an exception if it is not 0.
        That is, we can only call the start() method once for each Thread instance.
        if(threadStatus ! =0)
            throw new IllegalThreadStateException();

        // This is where the actual thread is added to the ThreadGroup. Again, I just incremented the nUnstartedThreads counter, not adding threads.
        // At the same time, the nUnstartedThreads counter will be -1 when the thread is started. There is one less thread in the ready state.
        group.add(this);

        started = false;
        try {
            // Is a Native method. This is left to the JVM, which calls the Run () method of the Thread instance.
            nativeCreate(this, stackSize, daemon);  
            started = true;
        } finally {
            try {
                if(! started) { group.threadStartFailed(this);  // If the Thread is not started successfully, the ThreadGroup will be removed and the nUnstartedThreads counter will be incremented by 1.}}catch (Throwable ignore) {
               
            }
        }
    }
Copy the code

NativeCreate (this, stackSize, daemon) calls the underlying JNI function Thread_nativeCreate(), Further calls at the bottom of the Thread class Thread: : CreateNativeThread () function.

Thread: : CreateNativeThread () function in the/art/runtime/Thread. Cc file (note: CoorChice 6.0.0 – r1 source). It creates a C/C ++ layer Thread object and associates it with the Java layer Thread object (in effect, saves a reference to the Java layer Thread object). A new thread is then created and started using the C/C ++ layer’s pthread_create() function. This code must be looked at:

pthread_create_result = pthread_create(&new_pthread, &attr, 
    Thread::CreateCallback, child_thread);
Copy the code

Note that Thread::CreateCallback, the third parameter, returns a pointer to the Run () method of the Java layer Thread class, which will be called after the Linux layer pThread Thread is created. This is why the run() method is called after we call the start() method.

From the above analysis, we can know that Java threads still use Linux pThreads, and a Thread is actually created and run in the virtual machine after calling the start() method. So, if you create a Thread but never call its start() method, no new Thread is created, and the Thread object is no different than a normal object in the main Thread. If you try to call the run() method to try to start your thread, you’re making a big mistake! This is equivalent to calling a Java method on the main thread.

As a result, Java threads in Android actually follow the same path as Linux pThreads.

// Yes, it is that simple! Only the run() method of the target member variable of type Runnable is called. At this point, the code we need to execute is executed.
// The @overrid exists only because Thread is a Runnable! That is, our Thread can also be used as a Runnable.
@Override
public void run(a) {
        if(target ! =null) { target.run(); }}Copy the code

See, if you don’t call the start() method, you can use Thread as a Handler!

public void test_1(a) {
    Thread thread1 = new Thread(() -> {
      System.out.println(Thread.currentThread().getName());
    }, "Thread_1");


    Thread thread2 = new Thread(thread1, "Thread_2"); thread2.start(); } -- Output: Thread_2Copy the code

Several common threading tools (operations)

Thread.sleep() the dirty laundry

We use thread.sleep () a lot, so let’s find out what happens when thread.sleep () is called. Before we get started, let’s introduce a concept — nanoseconds. 1 nanosecond = billionth of a second. It can be seen that it will be very accurate timing. This value is sometimes less accurate due to device limitations, but it is still much smaller than the control granularity of milliseconds.

Thread.sleep(long) is the last parameter to call thread.sleep (long).
// You can control threads at the nanosecond level.
public static void sleep(long millis, int nanos)
    throws InterruptedException {
        // Check whether the milliseconds and nanoseconds Settings are valid.
        if (millis < 0) {
            throw new IllegalArgumentException("millis < 0: " + millis);
        }
        if (nanos < 0) {
            throw new IllegalArgumentException("nanos < 0: " + nanos);
        }
        if (nanos > 999999) {
            throw new IllegalArgumentException("nanos > 999999: " + nanos);
        }

    
        if (millis == 0 && nanos == 0) {
            if (Thread.interrupted()) {   // When the sleep time is 0, check whether the thread is interrupted and clear the interrupt status flag of the thread. This is a Native method.
              throw new InterruptedException();  // If the Thread is set to true (call thread.interrupt ())). Then he will throw an exception. If the thread returns after catching the exception, the thread is stopped.
              After thread.sleep (), isInterrupted() always returns False. Don't forget that Thread. Interrupted () clears the mark while checking.
            }
            return;
        }

        long start = System.nanoTime();  Similar to system.currentTimemillis (). But we're getting nanoseconds, so maybe not.
        long duration = (millis * NANOS_PER_MILLI) + nanos;  

        Object lock = currentThread().lock;  // Get the lock of the current thread.

        synchronized (lock) {   // Synchronize the lock object of the current thread
            while (true) {
                sleep(lock, millis, nanos);  // Here again is a Native method that also throws InterruptedException.
                // As far as I can tell, the duration of sleep is indeterminate.

                long now = System.nanoTime();
                long elapsed = now - start;  // Count how long the thread has been asleep

                if (elapsed >= duration) {   // If the current amount of sleep has met our needs, we exit the cycle and sleep is over.
                    break;
                }

                duration -= elapsed;   // Subtract the amount of time you have slept and recalculate the amount you need to sleep.
                start = now;
                millis = duration / NANOS_PER_MILLI;  // recalculate the milliseconds part
                nanos = (int) (duration % NANOS_PER_MILLI); // Recalculate the microsecond portion}}}Copy the code

As can be seen from the above analysis, the core method to make the thread sleep is a Native function sleep(Lock, Millis, Nanos). The sleep() corresponds to an underlying JNI function that eventually calls TimedWait() on the condition variable of Thread in C/C ++. The TimedWait() function is also implemented by Android itself. In this function, Android directly uses the Linux futex() function. The futex() function calls the syscall() function, which performs the locking with a type of lock called the Fast user area mutex. Futex () is much more efficient than phtread_cond_wait().

Android also uses a while() loop to ensure proper hibernation, which checks to see if it has been hibernation for a sufficient amount of time each time the thread is awakened from the underlying layer. If not, let it continue to sleep.

Also, note that if the thread’s interruTED state is set to true when the sleep() method is called, InterruptedException is thrown before the sleep cycle begins.

What exactly does Thread.yield() hide?

This method is Native. Calling this method will tell the CPU that the current thread will relinquish its CPU rights and compete with other threads for new CPU rights. The current thread may or may not be executed again. Sauce.

What is the ubiquitous wait()?

It is common to see several methods called wait() at the bottom of any instance of an object. Waiting for? What kind of existence they are, let’s click to see. Oh, my God, they’re Native functions.

image


Take a look at the documentation for what it is.


Wait () works with notify() and notifyAll() to communicate between threads, called synchronization. In the thread calling wait () must be invoked in the synchronized code block, otherwise you will be thrown IllegalMonitorStateException anomalies. Because wait() needs to release the lock on the corresponding object. When a thread performs wait(), the object puts the current thread into its own thread pool, releases the lock, and blocks there. Until the object calls notify() or notifyAll(), the thread can regain, or possibly acquire, the lock on the object and proceed with subsequent statements.


Uh… All right, let’s explain the difference between notify() and notifyAll().

  • After notify() is called, the object wakes up by randomly selecting a thread from its own thread pool (that is, the thread that called wait() on the object). That is, only one thread can be awakened at a time. If notify() is called only once in a multi-threaded case, only one thread can be woken up and the others will stay
  • NotifyAll () When a notifyAll() is called, the object wakes up all threads in its own thread pool, which then collectively snatch the lock on the object.

The love and hate between Looper, Handler and MessageQueue

We’ve probably all written code in the past that looks like this:

new Thread(()->{

    ...
    Looper.prepare();
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
          super.handleMessage(msg); }}; Looper.loop(); }).start()Copy the code

Many of you know that when you use Handler in a thread (other than the Android main thread) you must place it between looper.prepare () and looper.loop (). Otherwise RuntimeException will be thrown. But why do it? Let’s take a look at the inside story.

image

From the stars. Prepare ()

What happens when looper.prepare () is called?

public static void prepare(a) {
        prepare(true);  // The logic in the private prepare(Boolean quitAllowed) method is executed
    }

    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {   // Try to find if there is already a Looper in the current thread and throw an exception if there is.
        // This is why we cannot call looper.prepare () twice in a Thread.
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));  // A new Looper is created for the first time.
    }
    
    //Looper's private constructor
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);   // Create a new MessageQueue, which will be accessed later.
        mThread = Thread.currentThread();         // Assign the current thread to the mThread.
    }
Copy the code

After the above analysis, we already know what happens after the looper.prepare () call. But here’s the problem! SThreadLocal is a static instance of ThreadLocal

(the Android ThreadLocal stereotype is always Looper). So, since looper.prepare () is a static method, how does Looper determine which thread to bind to? Let’s keep digging in. Take a look at ThreadLocal’s get() and set() methods.

public void set(T value) {
        Thread t = Thread.currentThread();  // Get the current thread
        ThreadLocalMap map = getMap(t);     // Get the ThreadLocalMap of the thread
        if (map! = null)map.set(this, value);           // Store key-value pairs
        else
            createMap(t, value);
    }    

public T get(a) {
        Thread t = Thread.currentThread();   // What's the point? Got the current running thread.
        ThreadLocalMap map = getMap(t);      // Fetch the ThreadLocalMap of the current thread. This is an important thing, which has already been mentioned. Those of you who forget will look at it in the front.
        if (map! = null) { ThreadLocalMap.Entry e =map.getEntry(this);  
            // Each thread has a 
       
         key-value pair in its ThreadLocalMap. The binding relationship is established through this key-value pair.
       ,looper>
            if(e ! = null)return (T)e.value;
        }
        return setInitialValue();
    }
Copy the code

ThreadLocal is a static constant in the Looper class, so it is visible to all threads. If you call a ThreadLocal set/get method, you are manipulating the ThreadLocalMap of a Thread. If you call a ThreadLocal set/get method, you are manipulating the ThreadLocalMap of a Thread. This design allows each thread to invoke the Get/SET method of ThreadLocal even in the case of concurrency, but it is thread-safe because each thread is actually operating on its own ThreadLocalMap. About threads of memory, you can be found in these two articles CoorChice several clues: “ask to meng 】 【 refused to the necessary of separate about thread: https://juejin.cn/post/6844903844648845320”, “to ask to meng 】 【 refused to how much you want to know some of the memory recovery mechanism: https://juejin.cn/post/6844903844627873800 “.

Consider this: given that each Thread owns a Thread’s own map, why not use ThreadLocal instead of directly retrieving the Thread and then retrieving its ThreadLocalMap?

Again, this is primarily designed to separate the logic, because the implementation may change. If the management logic of ThreadLocalMap is changed later, Looper and Thread are not affected as long as the interface functions remain unchanged. This logic can also be applied directly to other schemes. Easy to modify, easy to reuse.

Create a Handler

A Handler can be used to implement traffic between threads. In Android, we often need a Handler to tell the main thread to update the UI when the child thread has finished processing data. Normally we use new Handler() to create Handler instances in a thread, but how does it know which thread it should handle? Let’s take a look at the Handler.

public Handler(a) {
        this(null.false); 
}
    
public Handler(Callback callback, boolean async) {      // As you can see, this method is finally called.
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) = =0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();                    // What's the point? Here the Handler is bound to the Looper of the current Thread. Looper.mylooper () is the Looper of the current thread from ThreadLocale.
        if (mLooper == null) {
            // If looper.prepare () has not been called before new Handler() in the child thread, the Looper for the current thread has not been created. I'm going to throw this exception.
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;  // Assign Looper's MessageQueue to Handler.
        mCallback = callback;
        mAsynchronous = async;
    }

Copy the code

Looper.loop()

Looper.loop() is called after the Handler is created, otherwise sending messages to Handler is useless! Next, find out what kind of magic Looper has to send messages exactly to the Handler.

public static void loop(a) {
        final Looper me = myLooper();   // This method is already mentioned, which is to get the Looper object from the current thread.
        if (me == null) { 
            // No Looper. Prepare () is an error!
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;       // Get Looper's MessageQueue member variable, which was new when Looper was created.

        // This is a Native method that checks whether the current thread belongs to the current process. And keep track of who they really are.
        // In the IPC mechanism, this method is used to clear IPCThreadState PID and UID information. And returns an identity, which can be easily restored using restoreCallingIdentity().
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {  // Point (tap the blackboard)! This is an infinite loop, waiting to extract and send messages.
            Message msg = queue.next(); // Extract a message from MessageQueue. As for how to get it, we'll see later.
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                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);
            }

            final long traceTag = me.mTraceTag;   // Get the tag of MessageQueue
            if(traceTag ! =0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));  // Start tracking the current message in the MessageQueue of the thread, which is a Native method.
            }
            try {
                msg.target.dispatchMessage(msg);   // Attempt to dispatch a Message to the Handler bound to Message
            } finally {
                if(traceTag ! =0) {
                    Trace.traceEnd(traceTag);      // This works with trace.tracebegin ().}}if(logging ! =null) {
                logging.println("<<<<< Finished to " + msg.target + "" + msg.callback);
            }

            
            final long newIdent = Binder.clearCallingIdentity();   / / what? Calling this Native method again. The main purpose here is to verify again that the process in which the thread resides has not changed.
            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();   // Retrieve the release message.}}Copy the code

From the above analysis, when looper.loop () is called, the thread is called by a for(;). An infinite loop blocks, waiting for MessageQueue’s next() method to fetch one Message at a time before proceeding. The Message gets the corresponding Handler (the target member variable), which dispatchMessage() to handleMessage() for processing.

Note here that when a thread is looped, it is always in the loop. This means that the code after looper.loop () cannot be executed. To execute, you need to exit loop.

Looper myLooper = Looper.myLoop();
myLooper.quit();        // Common exit mode.
myLooper.quitSafely();  // Secure exit.
Copy the code

How did MessageQueue’s next() method block the thread? Next, take a look at MessageQueue.

MessageQueue was behind it

MessageQueue is a single-linked data structure that maintains a list of messages.

Message next(a) {
        // Check whether the loop is already in the exit state. MPrt is the address of MessageQueue of Native layer. This address allows you to interact with the MessageQueue of the Native layer.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;      // Time flag, 0 if and only if the message is first fetched. Because it's outside the loop!
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();     
                // If it is not the first time to fetch a message, call Native's function and have the virtual machine flush all Binder commands to ensure that the process releases the previous object before performing a task that might block.
            }

            // This is a Native method.
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {       / / lock the MessageQueue
                // Get the current system time for later comparison with msg.when.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;        // Get the first message in the current MessageQueue
                if(msg ! =null && msg.target == null) {
                
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null && !msg.isAsynchronous());
                }
                if(msg ! =null) {
                    if (now < msg.when) {  // The meaning of this judgment is that the Message should be sent only when it is sent, otherwise the loop continues.
                        // Calculate the time of the next message. Note that the maximum is integer.max_value.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {  // It is time to send a message.
                        // 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();   // Convert the message to a used one
                        return msg;         // Return a message to Looper.}}else {
                    // If Message is null, set the timestamp to -1.
                    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

You can see that. MessageQueue fetches messages (calling next()) in an infinite loop until a Message is retrieved and returned. That’s why looper.loop () waits at queue.next().

Notice one parameter in this method, mPtr, which is the address of the underlying MessageQueue object. This means that the Android C/C ++ layer also has a corresponding Handler mechanism for the Java layer, and our MessageQueue, because it holds a reference to the underlying layer, becomes the communication bridge between the Java layer Handler mechanism and the underlying layer.

A nativePollOnce(PTR, nextPollTimeoutMillis) appears in the above method; Function call. The thread will block at this point. This native method calls the underlying JNI function android_os_MessageQueue_nativePollOnce(), and further calls the pollOnce() function of nativeMessageQueue in C/C ++. In this function, the pollOnce() function of the underlying Looper is called again, and then the pollInner() function is called. The epoll_wait() function is called within the pollInner() function, which blocks the thread until it is timed out or an event is detected in the PIPE. So how does the block wake up here? We’re going to say.

So how does a Message get added to a MessageQueue? To get to the bottom of this, we need to investigate the mhandler.post () method.

What exactly does Handler do with Message?

Handler’s post() family of methods ends up calling the following method:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;      // Assign the target of Message here.
        if (mAsynchronous) {
            msg.setAsynchronous(true);      // If it is asynchronous, mark it as asynchronous
        }
        return queue.enqueueMessage(msg, uptimeMillis);     // This method adds Message to the thread's MessageQueue.
    }
Copy the code

Let’s look at what enqueueMessage() of MessageQueue does.

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {   // No Handler calls will throw exceptions
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {        // Cannot use a Message that is in use.
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {       // Lock MessageQueue to add messages.
            if (mQuitting) {        Return if MessageQueue is marked as exit.
                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();        // Change the usage state of Message to unused.
            msg.when = when;        // The time we set to delay sending.
            // Message will be "stored" in MessageQueue after the following logic. In fact, the way messages are stored in MessageQueue,
            // Message.next is stored in a single list, pointing backwards. For example: a.ext = B, b.ext = C...
            Message p = mMessages;  // Try to get the current Message
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // If the value is null, the value is the first one.
                msg.next = p;   
                mMessages = msg;    // Set the current Message as the incoming Message, i.e., as the first Message.
                needWake = mBlocked;
            } else {
            
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // If the condition is not satisfied as the first Message, place it last by the following stepwise transformation. This "stores" the Message in MessageQueue.
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }

          
            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

MessageQueue blocks at nativePollOnce() in the next() method, blocking at the underlying Looper’s epoll_wait() to wake up. See the end of this code up here? NativeWake (), naked representation is awakening. In fact, the nativeWake() function indicates that a write event occurred on the pipe writer side, making epoll_wait() exit the wait.

So far, we have uncovered the hidden secrets of Looper, Handler, and MessageQueue.

Another question?

You may have noticed that you can use Handler directly in the main thread without looper.prepare () and looper.loop (). Why is this possible? Looper.prepare() and looper.loop () must be present in the main thread. So why isn’t the main thread blocked by loop()? Take a look at ActivityThread to figure out what’s going on.

// This main() method can be considered the starting point for an Android application
public static void main(String[] args) {. . . Looper.prepareMainLooper();// The main effect is similar to that of looper.prepare ()

        ActivityThread thread = new ActivityThread();           // Create this class instance
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();           // What's the point? Here's the Handler that handles the main thread thing.
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();                                          // Start the loop. As you can see, the main thread is essentially blocked!. . . }Copy the code

Note that ActivityThread does not inherit from Thread; its Handler is the private inner class H.class that inherits from Handler. In H. Class’s handleMessage(), it accepts and executes various lifecycle status messages in the main thread. The 16ms drawing of the UI is also done through handlers. That is, all operations in the main thread take place between looper.prepareMainLooper () and looper.loop (). More specifically, in the main Handler.

conclusion

  1. In Android, Thread is initialized when it is created, using the current Thread as the parent Thread and inheriting some of its configuration.
  2. Threads are added to the specified/parent ThreadGroup for management when they are initialized.
  3. Thread true startup is done by a native function.
  4. In Android interthread communication, Looper needs to be created first by calling looper.prepare (). This process automatically relies on the current Thread and creates a MessageQueue. After the previous step, you can create a Handler, which by default automatically depends on the current thread’s Looper, and therefore on the corresponding MessageQueue, to know where to put the message. MessageQueue implements a single linked list structure through message.next to cache messages. The message needs to be sent to Handler for processing, and looper.loop () must be called to start the thread’s message pumping loop. Loop () is an infinite loop inside and blocks on MessageQueue’s next() method because next() is also an infinite loop inside until a message is successfully extracted from the list and returned. The processing then continues in the loop() method, which essentially dispatches the message to the target Handler. It then enters the next loop and waits for the next message. Because of this mechanism, the thread is blocked at loop().

Through the above Revelations, we have learned some secrets about threads and how they communicate with each other. After mastering these, I believe that in the future development process we can think clearly about the use of threads, and can absorb the essence of Android in the design process.

If you feel good, follow me. If you don’t, give me a thumbs-up! 😘