Android HandlerThread message loop mechanism source code parsing

Source: Internet
Author: User

Android HandlerThread message loop mechanism source code parsing

For HandlerThread, some people may put their fingers on the keyboard, and then they will be able to knock out some gorgeous code immediately:

HandlerThread handlerThread = new HandlerThread("handlerThread");handlerThread.start();Handler handler = new Handler(handlerThread.getLooper()){    public void handleMessage(Message msg) {        ...    }};handler.sendMessage(***);

A closer look, no problem (I didn't say the code was wrong). Please allow me to say, "This code has been knocked out, why ?"

There is no reason for this. I did not carefully read the Android message loop mechanism source code analysis in this article. Xuejie said, the source code is not looked at, no analysis, dare say you understand. I still thought I understood it. After reading this sentence, I was not sure. Therefore, we opened the...

Preface

First, let's take a look at the official website. There may be a lot of things to talk about later. The classes involved include HandlerThread, Thread, Handler, logoff, Message, and MessageQueue, some people may be unable to cope, and they have the urge to close the webpage. Don't be flustered. At the beginning, I didn't know that such a string would be involved when I looked at the source code. But after careful consideration, this is actually the case.

I. HandlerThread

The source of this incident is because of it. Don't look for it first.

First, what is HandlerThread gui? It feels like a combination of Handler and Thread. Click the source code to see:public class HandlerThread extends Thread {}There is nothing to say. It turns out to be a sub-class of a thread. Next, let's take a look at what is special about HandlerThread.

HandlerThread handlerThread = new HandlerThread("handlerThread");handlerThread.start();

Like the creation and startup steps of a normal thread, if the thread has been started, the run () method will be executed. To facilitate the analysis of the following process, useCode Block 1Indicates:

#HandlerThread.javapublic void run() {    mTid = Process.myTid();    Looper.prepare();    synchronized (this) {        mLooper = Looper.myLooper();        notifyAll();    }    Process.setThreadPriority(mPriority);    onLooperPrepared();    Looper.loop();    mTid = -1;}

Among them, logoff represents the message loop we often talk about, and logoff. prepare () represents some preparations before the message loop is executed.

Ii. Arrest various younger siblings (Looper, MessageQueue, and Message)

Since we have already talked about logoff, let's take a look at several of its methods: logoff. prepare () and logoff. loop ().
Code Block 2

#Looper.javapublic static void prepare() {    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();}static final ThreadLocal
  
    sThreadLocal = new ThreadLocal
   
    ();final MessageQueue mQueue;final Thread mThread;
   
  

The above steps are quite clear. To sum up, because a Thread can only correspond to one logoff, The logoff object will be stored in the class attribute of ThreadLocal only when the conditions are met. Before that, of course, we should first create a new logoff object, and create two objects in the logoff constructor, namely mQueue (Message Queue) and mThread (current thread ). The entire prepare process mainly involves creating three objects: logoff, MessageQueue, and Thread.
Okay, The logoff. prepare () process has been analyzed. Let's look at it again.Code Block 1There is a synchronization code block to obtain the logoff object. Method jump to the past, it turns out that it is to take out the logoff object previously saved in ThreadLocal.

#Looper.javapublic static Looper myLooper() {    return sThreadLocal.get();}

Next, the most important thing isLooper.loop()This code is also the value of the HandlerThread class. Once this code is executed, it indicates that the real message loop begins:Code block 3

#Looper.javapublic static void loop() {    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;    // Make sure the identity of this thread is that of the local process,    // and keep track of what that identity token actually is.    Binder.clearCallingIdentity();    final long ident = Binder.clearCallingIdentity();    for (;;) {        Message msg = queue.next(); // might block        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        Printer logging = me.mLogging;        if (logging != null) {            logging.println(">>>>> Dispatching to " + msg.target + " " +                    msg.callback + ": " + msg.what);        }        msg.target.dispatchMessage(msg);        if (logging != null) {            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);        }        // Make sure that during the course of dispatching the        // identity of the thread wasn't corrupted.        final long newIdent = Binder.clearCallingIdentity();        if (ident != newIdent) {            Log.wtf(TAG, "Thread identity changed from 0x"                    + Long.toHexString(ident) + " to 0x"                    + Long.toHexString(newIdent) + " while dispatching to "                    + msg.target.getClass().getName() + " "                    + msg.callback + " what=" + msg.what);        }        msg.recycleUnchecked();    }}

This method starts to retrieve messages from the Message Queue cyclically. Where did this message queue come from? Remember that we are in logoff. when you create a logoff object in prepare (), you can create two new objects in its constructor: MessageQueue and Thread. To use the message queue, you must first obtain the logoff instance. After all, the MessageQueue of the Message Queue exists as its member attribute, and then obtain the object of the message queue, and enter a seemingly endless control flow. This for statement is used to retrieve messages from the MessageQueue of the message queue and send them out. Who will handle these messages. The following code retrieves a message from a message queue:

#Looper.javaMessage msg = queue.next()

The subsequent content may require you to understand the data structure. Let's take a look at it step by step.Code block 4

#MessageQueue.javaMessage next() {    ...    for (;;) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }        nativePollOnce(ptr, nextPollTimeoutMillis);        synchronized (this) {            // Try to retrieve the next message.  Return if found.            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;            if (msg != null && msg.target == null) {                // Stalled by a barrier.  Find the next asynchronous message in the queue.                do {                    prevMsg = msg;                    msg = msg.next;                } while (msg != null && !msg.isAsynchronous());            }            if (msg != null) {                if (now < msg.when) {                    // Next message is not ready.  Set a timeout to wake up when it is ready.                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                } else {                    // Got a message.                    mBlocked = false;                    if (prevMsg != null) {                        prevMsg.next = msg.next;                    } else {                        mMessages = msg.next;                    }                    msg.next = null;                    if (false) Log.v("MessageQueue", "Returning message: " + msg);                    return msg;                }            } else {                // No more messages.                nextPollTimeoutMillis = -1;            }            // Process the quit message now that all pending messages have been handled.            if (mQuitting) {                dispose();                return null;            }            // If first time idle, then get the number of idlers to run.            // Idle handles only run if the queue is empty or if the first message            // in the queue (possibly a barrier) is due to be handled in the future.            if (pendingIdleHandlerCount < 0                    && (mMessages == null || now < mMessages.when)) {                pendingIdleHandlerCount = mIdleHandlers.size();            }            if (pendingIdleHandlerCount <= 0) {                // No idle handlers to run.  Loop and wait some more.                mBlocked = true;                continue;            }            if (mPendingIdleHandlers == null) {                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];            }            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);        }        ...    }}

You can skip the other steps first. We will jump directly to the synchronized synchronization code block. We can see thatMessage msg = mMessage;This mMessage object is a Message, but this Message class contains a Data Structure: queue. Let's take a look at this class:

public final class Message implements Parcelable {    ...    // sometimes we store linked lists of these things    /*package*/ Message next;       ...}

It is easy to see that the Message class contains a Message type attribute, which is used to point to the next Message, and so on, and form a Message queue at the most queue. However, the messages in the queue must be specially processed. Not every incoming message is directly added to the end of the queue. Because messages in Android have a time mechanism, each message will be appended with a time, which is alsohandler.sendMessageDelayed()Meaning of existence.

SeeCode block 4First, determine msg and msg.tar get. As long as the message in the message queue is not empty, the trigger time for the message is earlier than the current system time, the message is retrieved as the object to be sent. Here msg.tar get is very important. The reason why we can use handleMessage all depends on it is analyzed below.

Then returnCode block 3, Through

Message msg = queue.next(); // might block

After receiving the message, msg.tar get will distribute the message.

msg.target.dispatchMessage(msg);

So what is msg.tar get. View attribute Definitions

/*package*/ Handler target;

Actually a Handler that distributes messages. Let's take a look at how the messages are distributed:

#Handler.java/** * Handle system messages here. */public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}

Is there a feeling of excitement. In this case, I will reveal that the callback in the Message is actually a Runnable object,handleCallback(msg);Is to execute its run () method. This is why we can usehandler.post(new Runnable(){...})In fact, the Runnable object is assigned to the Callback attribute of the Message. If it is normalhandler.sendMessage()Then it must be executing the following statement. We can specify a Callback interface Callback when creating a Handler object. Of course, it's okay if you don't specify it. We can still usehandleMessage(msg)To obtain the message to be processed. Finally, we still need to recycle this message.msg.recycleUnchecked();

Now, we will introduce the lorule. prepare () and lorule. loop () methods. Let's add that the message queue has a message, and someone must provide it. The answer is Handler. Our common operation ishandler.sendMessage(msg);,Code block 5

#Handler.javapublic final boolean sendMessage(Message msg) {    return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(Message msg, long delayMillis){    if (delayMillis < 0) {        delayMillis = 0;    }    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}
#MessageQueue.javaboolean enqueueMessage(Message msg, long when) {    if (msg.target == null) {        throw new IllegalArgumentException("Message must have a target.");    }    if (msg.isInUse()) {        throw new IllegalStateException(msg + " This message is already in use.");    }    synchronized (this) {        if (mQuitting) {            IllegalStateException e = new IllegalStateException(                    msg.target + " sending message to a Handler on a dead thread");            Log.w("MessageQueue", e.getMessage(), e);            msg.recycle();            return false;        }        msg.markInUse();        msg.when = when;        Message p = mMessages;        boolean needWake;        if (p == null || when == 0 || when < p.when) {            // New head, wake up the event queue if blocked.            msg.next = p;            mMessages = msg;            needWake = mBlocked;        } else {            // Inserted within the middle of the queue.  Usually we don't have to wake            // up the event queue unless there is a barrier at the head of the queue            // and the message is the earliest asynchronous message in the queue.            needWake = mBlocked && p.target == null && msg.isAsynchronous();            Message prev;            for (;;) {                prev = p;                p = p.next;                if (p == null || when < p.when) {                    break;                }                if (needWake && p.isAsynchronous()) {                    needWake = false;                }            }            msg.next = p; // invariant: p == prev.next            prev.next = msg;        }        // We can assume mPtr != 0 because mQuitting is false.        if (needWake) {            nativeWake(mPtr);        }    }    return true;}

I don't think I have much to say. I simply paste the process one by one. In a word, encapsulate the incoming Message, and you can fix msg.when=msg.tar get here. Now let's look at the processing of msg in logoff. loop? No. At most messages will be added to the queue. As I mentioned earlier, messages will be added to the queue according to the time of msg. when, and then inserted to the appropriate position.

Summary

Now, the entire message loop mechanism has been analyzed. The original code:

HandlerThread handlerThread = new HandlerThread("handlerThread");handlerThread.start();Handler handler = new Handler(handlerThread.getLooper()){    public void handleMessage(Message msg) {        ...    }};handler.sendMessage(***);

Let's take a look at the specific operation process:

HandlerThread. start ()-> logoff. prepare ()-> logoff. loop ()-> queue. next ()-> msg.tar get. dispatchMessage (msg)-> handleMessage (msg) handler. sendMessage (msg)-> queue. enqueueMessage (msg)

Since all the above operations are executed in the run () method of A New thread, the UI thread is not blocked and the analysis is complete.
At this time, some people may have stood up, and this HandlerThread does not feel like it. I can use Thread directly to do everything. Imagine that there are now 10 background tasks to be executed. The traditional approach is to execute 10 times.New Thread (a Runnable object). start ()First, you have created 10 anonymous objects. You cannot control the resource consumption, which may cause memory leakage. If you package these background tasks into messages and then send them out, the thread can be reused first. In addition, you can remove the messages in the Message queue, to some extent, memory leakage is avoided. All right, I have to say everything. You may need to make up your mind and digest it.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.