Android source code analysis Handler mechanism (1)

Foreword:

In actual development, Looper, Handler, and Message are often used, or are often mentioned in interviews. Here, we deeply understand the Handler mechanism from source code analysis, and we know why it is.

I. Overview

Handler is a class provided by the Android SDK for developers to facilitate asynchronous message processing. We know that we cannot handle time-consuming operations on the main thread, such as network requests, read and write operations... At this time, we need today's protagonist Handler, the child thread to perform time-consuming operations, and send the request results through the Handler's sendMessge**() method Go out, process the request result through the handleMessage method of Handler in the main thread, and update the UI.

(After the asynchronous message processing thread is started, it will enter an infinite loop body. Each time it loops, a message is taken from its internal message queue, and then the corresponding message processing function is called back. After executing a message, the loop continues. If If the message queue is empty, the thread will block. Here we need Handler, Looper, Message, and MessageQueue, so what is the connection between them?)

2. Source code analysis:

1、Message (intercepted part of the source code of the Message class):    

public final class Message implements Parcelable {

    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    Handler target;
    Runnable callback;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
     */
    public Message() {
    }
}

There are four commonly used and familiar attributes extracted. :

   1. Message implements the Parcelable interface, which means that it implements serialization, and Message can be passed between different processes.
   2. Contains a Handler object named target
   3. Contains a Runnable object named callback
   4. Use the obtain method to obtain an instance of Message from the message pool, (recommended to use this)

2、Looper:

2.1. Looper.prepare() method:

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

sThreadLocal.set(new Looper(true)), where sThreadLocal is a ThreadLocal object, stores variables in a thread. In lines 2-4, determine whether sThreadLocal is null, otherwise an error will be reported, that is to say, the Looper.prepare() method can only be called once in a thread, which ensures that a Looper instance is saved in a thread . Let's take a look at new Looper (true)

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

You can see that line 2 new MessageQueue(quitAllowed) creates a MessageQueue message queue, because Looper.prepare() can only be called once in a thread, so there will only be one MessageQueue in a thread .

2.2, Looper.loop() method:

public 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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

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

Line 2 myLooper() method:

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

The Looper instance stored in sThreadLocal is returned. If me is null, an exception will be thrown, so the Looper.prepare() method must be executed before Looper.loop(), otherwise an error will be reported.

final MessageQueue queue = me.mQueue;

Get the mQueue (message queue) in the looper instance

for (;;) {

    ...

}

Here is the core code entering an infinite loop, taking out a message and handing it to msg.target.dispatchMessage(msg) for processing, (where msg.target is our associated handler, which will be described below).

3、Handler:

As I said before, a lot of big guys should appear!

It may be better to explain with code, before we create a handler object

Handler handler = new Handler() {

    @Override
    public void handleMessage(Message msg) {
        if (msg.arg1 == 1) {
            Toast.makeText(ThreadPoolActivity.this, "handler", Toast.LENGTH_SHORT).show();
        }
        super.handleMessage(msg);
    }
};

Let's look at the handler constructor again:

public Handler() {
    this(null, false);
}

public Handler(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 that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

The code looks a lot, don't panic, let's find the key points:

mLooper = Looper.myLooper();

This line of code gets the Looper of the current thread (as mentioned earlier, a thread can only save one Looper, if it is null, it will report an error, so don't worry about whether the Looper object is what you want!)

mQueue = mLooper.mQueue;

This line of code is to get the MessageQueue (message queue), so the handler is associated with our MessageQueue (message queue).

So where did the news come from? Here comes the familiar handler.sendMessage():

The handler.sendMessage() method will finally call the sendMessageAtTime() method,

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

Look at the last method enqueueMessage(); look at the implementation of the enqueueMessage() method:

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

In the method, msg.target = this; msg.target is assigned to this (that is, the current handler is used as the target attribute of msg), and finally the enqueueMessage method of queue is called to add the message sent by the handler to the MessageQueue message queue. (In the previous Looper.loop() method, each msg is taken out and then handed over to msg.target.dispatchMessage(msg) to process the message, that is, handler.dispatchMessage(msg))

handler.dispatchMessage(msg):

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

You can see that handleMessage(msg) is called at the end; we are very familiar with the method. In this method callback, we can update the UI according to msg.what message processing.

 

Final summary:

1. Looper.prepare() saves a Looper instance in this thread, creates a MessageQueue object, and binds it to the current thread.

2. Looper.loop() is responsible for reading messages from the MessageQueue message queue and calling back the msg.target.dispatchMessage(msg) method.

3. The Handler construction method will be associated with Looper and MessageQueue.

4. The Handler's sendMessage method, through msg.target = this; msg.target, assigns the target of msg to the handler itself, and then adds it to the MessageQueue.

5. When constructing a Handler instance, we will rewrite the handleMessage method, which is equal to the method finally called by msg.target.dispatchMessage(msg).

6. Looper.loop() The message loop is a thread that starts the loop mode, continues to monitor and sequentially processes messages sent to it by other threads.

 

 

 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324148095&siteId=291194637