preface

As an Android developer, I believe I am familiar with the use of Handler. Handler is very important for Android. Without it, The Android App would be a pile of “scrap metal”. It is like the blood of Android, shuttling through every corner of the App, delivering nutrients.

Understanding how the Handler works helps us better understand the essence of the Android system.

And Handler as a very important tool for Android, its source code is very clear, concise, very suitable for [Android source code reading] a starting point.

This article can learn

How does Handler work internally?

How does it relate to the NDK layer?

How does Android system use Handler to drive App running?

Answer some classic questions about handlers

A, how to use

You are familiar with the use of handlers in the main thread and child thread.

Let’s briefly take a look at how Hanlder is used, and what questions and conclusions you can draw from it. Look at the source code with doubt, there will be more to gain.

In the main thread

class HandlerActivity: AppCompatActivity() {

    private val mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        
        // 1. Use a customized Handler to send messages
        mHandler.sendEmptyMessageDelayed(1.1000)

        // 2. Use the default Handler to send messages
        Handler().post {
            Log.i("HandlerActivity"."run postDelay")}}// Create a custom Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Main thread: handleMessage:${msg.what}")}}}Copy the code

As you can see, there are two different ways to send messages, one in the Handler and the other in the Runnable.

One conclusion can be drawn:

The Handler can either process the message itself or send it to the Runnable for processing.

And there’s a question:

If two exist at the same time, which one will receive the message?

The child thread

  • Implementing thread switching
  1. Create the Handler in the main thread
class HandlerActivity: AppCompatActivity() {
    private var mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        
        Thread {
            mHandler.sendEmptyMessageDelayed(1.1000)
        }.start()
    }

    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Main thread: handleMessage:${msg.what}")}}}Copy the code
  1. Create a Handler in a child thread
class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState) Thread { mHandler = MyHandler(Looper.getMainLooper()) mHandler!! .sendEmptyMessageDelayed(1.1000)
        }.start()
    }

    class MyHandler(looper: Looper): Handler(looper) {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Main thread: handleMessage:${msg.what}")}}}Copy the code
  • Create a Handler that belongs to the child thread
class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        Thread {
            / / to the starsLooper.prepare() mHandler = MyHandler() mHandler!! .sendEmptyMessageDelayed(1.1000)
            // Start the loop
            Looper.loop()
        }.start()
    }

    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."Child thread: handleMessage:${msg.what}")}}}Copy the code

The first two methods initialize the Handler in a different location, but the effect is the same. The second method initializes the Handler in a child thread, but takes looper.getMainLooper () as an argument. Indicates that its message callback processing is the same Looper as the main thread.

The third case is different. First, a Looper is prepared in the child thread and the loop is opened at the end. Also, the Handler’s message processing is ultimately handled by callback in the child thread.

From the above analysis, a conclusion can be drawn:

Looper is directly related to which thread Handler messages are ultimately processed.

There is also a question:

Why do child threads need to pass the Main thread’s Main Looper, or prepare a Looper of their own, in order to use Handler properly when the Main thread doesn’t?

With the above conclusions and questions, openHandlerA journey of discovery.

Two, how to operate

Although Handler and Message are the two most commonly used classes in daily development, in fact, these two classes are not the core of the entire Message processing mechanism, but two other classes: the poller Looper and the Message queue MessageQueue.

Let’s take a look at the dependencies of these classes:

Looking at the Java section on the left, the classes used by the entire messaging mechanism are actually interlocking,

  • As an exposed tool, Handle contains a Looper.

  • As the core of the message loop, Looper contains a MessageQueue MessageQueue, which is used to record all messages to be processed.

  • MessageQueue, as a Message queue, contains a series of linked messages.

  • Message is the body of the Message, which in turn contains a target Handler, the target that will ultimately process it.

Why the NDK layer part on the right?

In fact, the Java layer’s four classes make up the body of the entire message loop, and the loop’s wait blocking is implemented through the NDK layer with the help of Linux’s EPoll.

What is epoll?

From Baidu Encyclopedia

Epoll is a poll modified by the Linux kernel for handling large numbers of file descriptors. It is an enhanced version of the SELECT/Poll multiplex IO interface under Linux, which can significantly improve the system CPU utilization of programs with only a small amount of activity in a large number of concurrent connections.

Epoll knowledge is beyond the Handler core, so this article will not go into depth, please do your own Google

Epoll is a tool used in Linux to listen for I/O events. An incoming wait handle wakes up to continue execution once the event is heard.

In accordance with this idea, they are analyzed one by one.

Message Handler: Handler

Handler is the entry point to the message, see how it is constructed.

The default structure
// Handler.java

public Handler(a) {
    this(null.false);
}

public Handler(@Nullable Callback callback, boolean async) {
    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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

The final method called, the core code of this method:

// Handler.java

public Handler(@Nullable Callback callback, boolean async) {

    // omit other code...
    
    mLooper = Looper.myLooper();
    
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

Here you can see the basic relationship between the core classes: Handler holds a Looper, and Looper holds a MessageQueue. MLooper = looper.mylooper ();

// Looper.java

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper(a) {
    return sThreadLocal.get();
}
Copy the code

ThreadLocal is a thread specific Looper, which means that there can only be one Looper in a thread.

Another message from the comment is that to get a non-empty Looper, you must call looper.prepare () once in the thread.

Here, one of the above conclusions is demonstrated:

Looper is directly related to which thread Handler messages are ultimately processed

From the above analysis, Looper is indeed directly associated with threads.

Pass in the Looper construct
// Handler.java

public Handler(@NonNull Looper looper) {
    this(looper, null.false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

In this way, it is easier to just save the Looper passed in, but note that Looper cannot be null.

Send a message
// Handler.java

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Whichever method sends the Message ends up calling the enqueueMessage method and putting the Message into the MessageQueue queue.

So how does a message, once queued, get called after its arrival time? Which brings us to Looper.

Poller: Looper

Let’s start with how Looper is initialized.

Looper.mylooper () must call looper.prepare () in order to get Looper.

// Looper.java

public static void prepare(a) {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if(sThreadLocal.get() ! =null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code

As you can see, a thread can only call looper.prepare () once before a RuntimeException is reported.

If it is called for the first time, a Looper is created and placed in sThreadLocal.

When constructing a Looper, a MessageQueue MessageQueue belonging to the Looper is created.

Start message loop

To start the message loop, go through looper.loop ().

Let’s look at this method (only the core code is posted here, with some exception detection code removed) :

// Looper.java

public static void loop(a) {

    final Looper me = myLooper();
    
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    final MessageQueue queue = me.mQueue;

    // omit some code...
    
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        try {
            msg.target.dispatchMessage(msg);
            // omit some code...
        } catch (Exception exception) {
            // omit some code...
            throw exception;
        } finally {
            // omit some code...
        }

        // omit some code...msg.recycleUnchecked(); }}Copy the code

Although the whole method is quite long, the main code is actually quite simple: once you get the message queue, you go into an infinite loop.

The loop does one thing: it gets an executable Message from the Message queue, then calls the dispatchMessage method of the Message’s target (i.e., Handler), and the Message is dispatched.

How to get an executable message from the queue depends on MessageQueue.

MessageQueue: MessageQueue

As mentioned earlier, when a Looper is constructed, a MessageQueue, MessageQueue, is created.

// MessageQueue.java

private native static long nativeInit(a);

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}
Copy the code

In the constructor, nativeInit creates an object called mPtr, which is the NativeMessageQueue of the NDK layer. I won’t talk about it here. I’ll talk about it in section 3.

The message into

Before we can understand how to get executable messages, we need to know how messages are pushed into queues.

The Hanlder message queue is a one-way list queue, linked from the head of the queue to the end of the queue.

Remember when the Handler sent the message, the last call was enqueueMessage of MessageQueue? Take a look at this method (note: the code omits some exception detection snippets) :

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // omit some code...
    
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        // [1] get the head of the queue
        Message p = mMessages;
        boolean needWake;
        
        // [2] If the message does not need delay, or the execution time of the message is earlier than the header message, it is inserted into the queue head
        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 {
            [3] The message is inserted into the middle of 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;
        }

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

There are three main steps (see code annotation above).

  1. mMessagesIs the first message in the queue, fetch it
  2. Check whether the message queue is empty, if yes, put the current message to the queue head; If the current message does not need to be delayed, or the execution time of the current message is earlier than the header message, it is also put to the queue head.
  3. If this is not the case, the current queue is not empty and the header message is executed earlier than the current message and needs to be inserted into the middle of the queue.

How do you determine this position? It is still the time that the message is executed.

By traversing the queue, a message is inserted in front of a message in the queue when its execution time is later than the current message.

As you can see, a message queue is a one-way linked list connected by the order of execution of the messages.

To get an executable message, you simply walk through the list and compare the current time to the message’s execution time to see if the message needs to be executed.

Gets executable messages

In the looper.loop () loop, queue.next() is used to get the executable message. Take a look at this method directly.

// MessageQueue.java

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();
        }
        
        // [1] call the NDK layer method, the thread blocks and hangs, and enters the wait
        When nextPollTimeoutMillis = -1, enter infinite wait until someone wakes up
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // [2] Check whether a synchronization barrier is inserted into the queue. If yes, only asynchronous messages are executed
            if(msg ! =null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());
            }
            if(msg ! =null) {
                if (now < msg.when) {
                    // [3] The message does not reach the time, recalculate the waiting time
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    [4] Rejoin the linked list and return the message
                    mBlocked = false;
                    if(prevMsg ! =null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    
                    returnmsg; }}else {
                // No message, enter infinite wait
                nextPollTimeoutMillis = -1;
            }

            if (mQuitting) {
                dispose();
                return null;
            }

            // [5] Check whether there are idle listeners, if so, perform a callback
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            
            // Only the first for loop is -1,
            PendingIdleHandlerCount becomes 0 after executing it once.
            // No more idle listening callbacks are performed
            if (pendingIdleHandlerCount <= 0) {
                // mBlocked is true, indicating that the thread is blocked and suspended
                mBlocked = true;
                continue;
            }

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

        // Callback the idle listener
        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); }}}// If this is set to 0, the idle listener above is no longer executed
        pendingIdleHandlerCount = 0;
        
        nextPollTimeoutMillis = 0; }}Copy the code

There are five main steps to get the message (see code annotation above) :

  1. Enter the for loop to traverse the message queue, callingNDKmethodsnativePollOnceSuspended thread, the first time it enters, the wait time is0, continue to execute;
  2. If a synchronization barrier is inserted into the queue, only asynchronous messages will be executed. If there is no synchronization barrier, the queue header message will be retrieved.
  3. Compare the current time with the message execution time. If the time is not up, calculate the waiting time and enter the waiting time again.
  4. If the message execution time is up, rejoin the linked list and return the message, at this point,LooperA message will be received and distributed toHandlerTo deal with.
  5. Finally, there is an idle listening process, which means that when there are no messages in the queue that need to be executed, the thread is idle so that it can perform other tasks.

Wake up the thread

When the message queue is empty, the Loop enters an infinite blocking suspension. If the user sends a message, how does the thread wake up?

If you are careful, you may have noticed that there is a place in the above enqueueMessage that wakes up the thread. Look at this method:

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // omit some code...
    
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        // [1] get the head of the queue
        Message p = mMessages;
        boolean needWake;
        
        // [2] If the message does not need delay, or the execution time of the message is earlier than the header message, it is inserted into the queue head
        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 {
            [3] The message is inserted into the middle of 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;
        }

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

Notice the needWake flag at the end? If this variable is true, the pending thread is woken up by the NDK method nativeWake.

There are two ways to wake up a thread:

  1. The queue is empty | | message without delay | | or message execution time earlier than the message queue head] && [thread is in a pending state (mBlocked = true).
  2. The thread is blocked (mBlocked = true) & the message loop is in a synchronous barrier state. If an asynchronous message is inserted, the thread needs to be awakened

Above, basically is the whole message mechanism process, concise, clear.

A quick summary:

  1. Prepare Looper: looper.looper ()
  2. Create MessageQueue MessageQueue: created when Looper is constructed
  3. Create Handler: User-defined Handler
  4. Start the Loop: looper.loop (). Loop through the message queue to determine whether execution time has reached
  5. User sends messages: Messages are queued to be iterated and executed by Looper using methods such as sendMessageDelay of Handler

Message body: Message

The structure of the message body
public final class Message implements Parcelable {
    public int what;

    public int arg1;

    public int arg2;
    
    public Object obj;
    
    Handler target;

    Runnable callback;

    Message next;
    
    // omit other...
}
Copy the code

The main members of Message are: Target and callback, which are where messages are processed.

Let’s take a look at where these two are assigned, back to where the Handler sent the message:

// Handler.java

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Before being pushed into the message queue, target is assigned the value this, which is the Handler that sent the message.

Callback assignment:

// Handler.java

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
Copy the code

When using the POST method, the Handler also constructs a message body and assigns a Runnable value to the callback.

The first section raises the question: When a message is sent via POST, both target and POST are assigned, so which one will process the message last?

As we know from the message loop analysis above, when Looper retrieves an executable message, Handler’s dispatchMessage method is called:

// Handler.java

public void dispatchMessage(@NonNull Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}private static void handleCallback(Message message) {
    message.callback.run();
}

/** * Subclasses must implement this to receive messages. */
public void handleMessage(@NonNull Message msg) {}Copy the code

As you can see, the message is first distributed to callback, or Runnable processing;

If callback == null and Handler’s own mCallback! = null is distributed to mCalllback for processing;

The last thing to handle is the handleMessage, which the user can inherit and rewrite to handleMessage events.

So what is this mCallback? This is an interface to Handler:

// Handler.java

public interface Callback {
    / * * *@param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}
Copy the code

How do you use it? Let’s look at one of the Handler constructors:

// Handler.java

public Handler(@Nullable Callback callback) {
    this(callback, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    // omit irrelevant code...

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

That is, when you use Handler, you don’t have to inherit the Handler to process messages. You can also implement the interface Callback to process messages.

The message pool

Message.obtain() When generating information, it is best to obtain information using message.obtain (). Take a look at this method first:

// Message.java

public static Message obtain(a) {
    synchronized (sPoolSync) {
        if(sPool ! =null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            returnm; }}return new Message();
}
Copy the code

As you can see, this is a message pool, and if the message pool is not empty, a message is fetched from the pool for reuse.

How are messages recycled into the pool?

At the end of the Looper message loop is a line of code:

// Looper.java

public static void loop(a) {
    
    // omit irrelevant code...
    
    for (;;) {
    
        // omit irrelevant code...
        
        try {
            msg.target.dispatchMessage(msg);
        } catch (Exception exception) {
            // omit irrelevant code...
        } finally {
            // omit irrelevant code...
        }
        
        // omit irrelevant code...msg.recycleUnchecked(); }}Copy the code

After the message is processed, the message is recycled.

// Message.java

private static final int MAX_POOL_SIZE = 50;

void recycleUnchecked(a) {
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this; sPoolSize++; }}}Copy the code

It turns out that the message pool is also a message linked list queue, and the message is added to the queue header after all the information is removed.

You can also see that the maximum number of caches for the message pool is 50.

Message pooling prevents large numbers of messages from being created and saves memory. So, try to create messages using message.obtain ().

Third, NDK layer message tools

As mentioned earlier, the Java layer implements the core functionality of the entire message loop, and the pending of the message loop thread is implemented through the NDK’s ePoll.

The NDK layer interacts with MessageQueue, which has a local method:

// MessageQueue.java

private native static long nativeInit(a);
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); 
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
Copy the code

Focus on initialization, suspension, and wake up methods.

private native static long nativeInit(a);
private native void nativePollOnce(long ptr, int timeoutMillis); 
private native static void nativeWake(long ptr);
Copy the code

Message queue: NativeMessageQueue

AOSP/ platform_frameworks_base-Android10-c2F2-release /core/jni/ android_OS_Messagequeue.cpp

//android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if(! nativeMessageQueue) { jniThrowRuntimeException(env,"Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}
Copy the code

In the NDK layer, a NativeMessageQueue was created, and you must be wondering, what does this message queue have to do with the Message queue in the Java layer?

The Java layer MessageQueue “borrows” the thread suspension function of NativeMessageQueue.

// MessageQueue.java

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}
Copy the code

After the Java layer MessageQueue is initialized, the NativeMessageQueue pointer reference of the NDK layer is saved, and subsequent threads are suspended and woken up through this pointer.

Initialize the
// NativeMessageQueue.cpp

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false); Looper::setForThread(mLooper); }}Copy the code

Here the Java layer is reversed and NativeMessageQueue implements the message loop with Looper.

⚠️ Looper is not the Java layer, but the NDK layer looper.cpp.

Suspend and wake up
// NativeMessageQueue.cpp

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    // Enter the wait
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL; }}void NativeMessageQueue::wake() {
    / / wake
    mLooper->wake();
}
Copy the code

The epoll part is encapsulated in Looper, so let’s look at the implementation.

Poller: Looper

Take a look at epoll. Epoll is a tool used by the Linux platform to listen for I/O events.

It’s very simple to use in three steps:

#include <sys/epoll.h>

// Create handle
int epoll_create(int size);
// Add/delete/modify listening events
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// Enter the wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Copy the code

Here’s a look at how Android implements these three steps.

Stars. CPP in: AOSP/platform_system_core – android10 – c2f2 – release/libutils/stars. The CPP

structure
// Looper.cpp

Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false),
      mPolling(false),
      mEpollRebuildRequired(false),
      mNextRequestSeq(0),
      mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0."Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    if (mEpollFd >= 0) {
        mEpollFd.reset();
    }

    [1] create epoll handle
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));

    // [2] Create a wake up listener
    struct epoll_event eventItem;
    memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
    // Set the listener readable event
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd.get();
    
    // [3] register the wake up listener event to the handle
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);

    // Register other listening events. Handle is null
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s", request.fd, strerror(errno)); }}}Copy the code

RebuildEpollLocked () is called in the constructor, which creates a handle to epoll and registers the wake up listener event, as described in the code comments above.

Thread hanging

When the Java layer calls NativeMessageQueue’s pollOnce, Looper’s pollOnce is ultimately called.

// Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
                
                if(outFd ! =nullptr) *outFd = fd;
                if(outEvents ! =nullptr) *outEvents = events;
                if(outData ! =nullptr) *outData = data;
                returnident; }}if(result ! =0) {
            if(outFd ! =nullptr) *outFd = 0;
            if(outEvents ! =nullptr) *outEvents = 0;
            if(outData ! =nullptr) *outData = nullptr;
            returnresult; } result = pollInner(timeoutMillis); }}Copy the code

After entering the for loop, mResponses are empty if only used by handlers, so they can be ignored. And then finally, it goes to the pollInner method.

As Looper of NDK layer is also a message mechanism function similar to Handler of Java layer, similar functions need to be realized. Here, some codes in pollInner are removed, and it will be more intuitive to only see functions related to suspension/awakening:

int Looper::pollInner(int timeoutMillis) {

    // omit some code...
    
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    // [1] Enter the wait and suspend
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    mPolling = false;

    mLock.lock();

    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = POLL_ERROR;
        goto Done;
    }

    if (eventCount == 0) {
        result = POLL_TIMEOUT;
        goto Done;
    }

    // [2] traverses all monitored events
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        // [3
        if (fd == mWakeEventFd.get()) {
            if (epollEvents & EPOLLIN) {
                // [4] Clears the contents of the wake up event
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); }}else {
            // Handle other events, Handler does not
            // omit some code...
        }
    }
Done: ;

    // omit some code...
    
    // Release lock.
    mLock.unlock();

    // omit some code...
    
    return result;
}
Copy the code

Epoll_wait is first called to wait, where

If the value of timeoutMillis is 0, the system returns immediately and continues. If -1, enter infinite wait until external wake up; If the value is greater than 0, the system automatically wakes up after a specified time and continues.

When timeoutMillis == 0 or a timeout wakes up, it jumps to Done, terminates the call, and returns. At this point, the Java layer’s Next method of MessageQueue continues to execute, checking to see if there are any executable messages.

When timeoutMillis == -1 enters infinite wait, if the external wake up, need to determine whether the event is the specified event. If it is the mWakeEventFd event, you need to clear the event and continue, end the call and return.

Please refer to the above code annotation for details.

Thread to wake up

The function of epoll is to listen for events. If you want to wake it up, it actually initiates an event. The following events can be monitored:

  • EPOLLIN: indicates that the corresponding file descriptor can be read (including that the peer SOCKET is normally closed).
  • EPOLLOUT: indicates that the corresponding file descriptor can be written.
  • EPOLLPRI: indicates that the corresponding file descriptor has urgent data to read (it should indicate that out-of-band data arrives).
  • EPOLLERR: indicates that an error occurs in the corresponding file descriptor.
  • EPOLLHUP: the corresponding file descriptor is hung up.
  • EPOLLET: Set EPOLL to Edge Triggered mode, as opposed to Level Triggered.
  • EPOLLONESHOT: monitors only one event. If you want to continue monitoring the socket after this event, you need to add the socket to the EPOLL queue again.

EPOLLIN events are listened for in rebuildEpollLocked() :

void Looper::rebuildEpollLocked() {

    // omit irrelevant code...
    
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));

    struct epoll_event eventItem;
    memset(& eventItem, 0.sizeof(epoll_event));
    
    // Listen for readable events
    eventItem.events = EPOLLIN;
    
    eventItem.data.fd = mWakeEventFd.get();
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);

    // omit irrelevant code...
}
Copy the code

So, just write something random to mWakeEventFd and you can wake up.

Take a look at Looper’s Wake method:

// Looper.cpp

void Looper::wake() {
    uint64_t inc = 1;
    // Write a 1 to wake up the wait
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if(nWrite ! =sizeof(uint64_t)) {
        if(errno ! = EAGAIN) { LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s", mWakeEventFd.get(), nWrite, strerror(errno)); }}}Copy the code

It is also easy to clear the contents of the write, just read out:

// Looper.cpp

void Looper::awoken() {
    uint64_t counter;
    // Read what was written
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}
Copy the code

This is the entire implementation of the Handler from the Java layer to the NDK layer.

Now, let’s look at some of the more interesting things in Handler.

Handler synchronizes message barriers

What is a synchronous message barrier

What exactly is a synchronous message barrier?

A barrier. It blocks some messages. What messages? Sync the message!

That is, synchronization barriers are used to block synchronous message execution.

In everyday use, there is little concern about whether the Handler’s messages are synchronous or asynchronous, because the default messages are synchronous. Let’s see how synchronous and asynchronous messages are set up.

Set by Handler
// Handler.java

public Handler(a) {
    this(null.false);
}

public Handler(@Nullable Callback callback, boolean async) {
    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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    
    // Set it to asynchronous
    mAsynchronous = async;
}
Copy the code

You can see that all handlers created by the user are synchronized, and whether synchronization is preserved in mAsynchronous.

There is of course a system that can be used, but not visible to the developer, to create asynchronous handlers.

// Handler.java

@UnsupportedAppUsage
public Handler(boolean async) {
    this(null, async);
}
Copy the code

So how is mAsynchronous used?

Let’s look at the way messages are queued:

// Handler.java

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

If the Handler’s mAsynchronous == true, the message is set to asynchronous.

Therefore, there is no way for developers to implement asynchronous messaging through handlers.

But it can be set directly by message

Configure by message body

In fact, it is the above method:

msg.setAsynchronous(true);
Copy the code
How does the synchronous message barrier work

Let’s go back to where the message queue gets the executable message and look at the code annotation [2] :

// MessageQueue.java

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();
        }
        
        // [1] call the NDK layer method, the thread blocks and hangs, and enters the wait
        When nextPollTimeoutMillis = -1, enter infinite wait until someone wakes up
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // [2] Check whether a synchronization barrier is inserted into the queue. If yes, only asynchronous messages are executed
            if(msg ! =null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());
            }
            if(msg ! =null) {
                if (now < msg.when) {
                    // [3] The message does not reach the time, recalculate the waiting time
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    [4] Rejoin the linked list and return the message
                    mBlocked = false;
                    if(prevMsg ! =null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    
                    returnmsg; }}else {
                // No message, enter infinite wait
                nextPollTimeoutMillis = -1;
            }

            if (mQuitting) {
                dispose();
                return null;
            }

            // omit irrelevant code...
        }

        // omit irrelevant code...}}Copy the code

As you can see, synchronous message barrier is enabled when a message is inserted into the header of the message queue and the target of the message is == NULL.

At this point, it determines whether the message is synchronous, and if it is, it skips and continues looking for asynchronous messages in the queue.

In other words, synchronous messages are blocked and only asynchronous messages can be executed.

How do I start/clear synchronous message barriers

Again in MessageQueue:

/ * * *@hide* /
public int postSyncBarrier(a) {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if(when ! =0) {
            while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        returntoken; }}/ * * *@hide* /
public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while(p ! =null&& (p.target ! =null|| p.arg1 ! = token)) { prev = p; p = p.next; }if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if(prev ! =null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null|| mMessages.target ! =null;
        }
        p.recycleUnchecked();

        if(needWake && ! mQuitting) { nativeWake(mPtr); }}}Copy the code

The synchronous message barrier is simply inserted into the message queue at the time the barrier is started, similar to normal message push.

As you may have noticed, none of these methods are visible to the developer, with the @hide tag, meaning that the developer is not allowed to enable the synchronous message barrier.

Synchronous message barrier role

So what’s the point of knowing synchronous message barriers? “Only distant view, not obscene play” ah! Wait, read on

For the front end, the UI can be said to be the most important thing in the system, and no one can delay the UI work.

If you’ve seen the Android View drawing process, you know that View drawing is also driven by a Handler.

If the user (developer) inserts a very time-consuming message into the queue before starting drawing, the UI will not draw on time, resulting in frame lag.

Synchronous message barriers can be used to ensure thisUIPriority of drawing.

When you request to draw a View, the final call is in the system’s ViewRootImpl. Take a look at this code:

// ViewRootImpl.java

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

Synchronous message barriers are started, then messages in mChoreographer are asynchronous to ensure the flow of messages, eventually calling back mTraversalRunnable. Finally, the synchronization message barrier is removed when drawing, as shown in the following code:

// ViewRootImpl.java

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
    @Override
    public void run(a) { doTraversal(); }}void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        
        // Clear the synchronization message barrier
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        
        // Start the drawing process
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false; }}}Copy the code

Specific is no longer launched, it is a source of the ocean -_-! .

Only by understanding the synchronization message barrier of Handler can we fully understand the drawing process of Android View.

5. Classic questions about Hanlder

There are many classic questions about Handler, after understanding the entire source code, basic can be answered, here is a look at a few often encountered problems.

Why is Android message-driven

When you open Apk, the system creates a process for Apk, and as we know, Linux processes contain at least one thread. In Apk, this thread is the main thread, or UI thread.

The entry to the Apk main thread is in the main() method in ActivityThread.java.

// ActivityThread.java

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    AndroidOs.install();

    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    [1] prepare the main thread Looper
    Looper.prepareMainLooper();

    long startSeq = 0;
    if(args ! =null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if(args[i] ! =null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    // [2] Get Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

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

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    
    [3] Start the message loop
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

This is basically the most basic way to start a Handler. Take a look at the getHandler() method:

It simply returns H

// ActivityThread.java

final H mH = new H();

final Handler getHandler(a) {
    return mH;
}
Copy the code

Take a look at some of the code for H:

// ActivityThread.java

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    @UnsupportedAppUsage
    public static final int EXIT_APPLICATION        = 111;
    @UnsupportedAppUsage
    public static final int RECEIVER                = 113;
    @UnsupportedAppUsage
    public static final int CREATE_SERVICE          = 114;
    @UnsupportedAppUsage
    public static final int SERVICE_ARGS            = 115;
    @UnsupportedAppUsage
    public static final int STOP_SERVICE            = 116;
    
    // omit some code...
    
    public static final int EXECUTE_TRANSACTION = 159;
    public static final int RELAUNCH_ACTIVITY = 160;
    
    // omit some code...
    
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                break;
            case EXIT_APPLICATION:
                if(mInitialApplication ! =null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case CREATE_SERVICE:
                handleCreateService((CreateServiceData)msg.obj);
                break;
                
            // omit some code...
            
            // Handle events related to the Activity lifecycle
            / / such as onCreate/onResume/onPause, etc
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                
                // omit some code...
                break;
            case RELAUNCH_ACTIVITY:
                handleRelaunchActivityLocally((IBinder) msg.obj);
                break;
            case PURGE_RESOURCES:
                schedulePurgeIdler();
                break;
        }
        
        // omit some code...}}Copy the code

As you can see, almost everything from App startup to activity-related life cycles, services-related life cycles, App exit, and so on, is driven by handlers.

As you can see, Android is based onHandlerMessage driven.

It also answers the question in section 1: Why does the main thread not need to call looper.prepare () to prepare a Looper to send a message?

Because when the App starts, the system has already created Looper.

Why doesn’t loop.looper () block the main thread

After the above analysis, the main thread of the App starts the message “infinite Loop” through loop.looper (), so why can the App still run normally and not block the main thread?

Loop.looper() is not dead at all. Instead, it runs dynamically driven by events, and the thread “comes alive” whenever a message arrives.

On the contrary, loop.looper () keeps the App alive, because if there are no “dead loops”, activityThread.main () will exit after execution and no more messages will be received. Instead, it will “die”!

How does Handler cause a memory leak

Usually memory leaks caused by handlers are caused by memory leaks in activities.

Handler might be used like this on a daily basis:

class HandlerActivity: AppCompatActivity() {

    private val mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        
        mHandler.sendEmptyMessageDelayed(1.1000)}// Create a custom Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity"."handleMessage: ${msg.what}")}}}Copy the code

MyHandler is an inner class of HandlerActivity that holds a reference to the HandlerActivity.

After entering the page, a Message is sent that is delayed for 1s. If the HandlerActivity exits within 1s, since the Handler will be held by Message in its target variable, and Message will be stored in the Message queue, This sequence of associations causes the HandlerActivity to be held when it exits and therefore cannot be collected by the GC. This is a memory leak!

Will the HandlerActivity be recycled after the 1s delay message is executed? The answer is yes!

Because after the Message is executed, the Target is cleared before the Message is put into the cache pool, the holding chain is broken and eventually the HandlerActivity is recycled.

However, this does not mean that we can ignore the problem of memory leak, memory leak is always a memory leak, if it is a large memory page is not recycled in time, may lead to OOM.

There are two ways to solve this problem:

  1. willMyHandlerChange to a static class so that it no longer holds references to external classes. Can beHandlerActivityPut as a weak referenceMyHandlerPage exit can be recycled in time.
  2. Page exit when inonDestroyIn the callHandlerremoveMessagesMethod to remove all messages, which also removes the holding chain.