Analysis on The SourceCode of Handler, Looper and Message Queue

Introduce

Android can be updated through the Handler UI to change the main thread, the update UI can only be updated in the main thread, and in order to allow other threads can control UI changes, Android provides a mechanism Handler, Looper and MessageQueue together Collaboration to achieve the purpose of other threads update the UI.

For example of handler:

1
2
3
4
5
6
7
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
tv.setText("mHandler change UI");
super.handleMessage(msg);
}
};

Generally do not see Looper and MessageQueue, then they are where to call and how to collaborate in the main thread will not explicitly call Looper but in the ActivityThread.main method default call.

SourceCode

ActivityThread.main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();

// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);

Environment.initForCurrentUser();

// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());

// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);

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

Looper.prepareMainLooper();//创建Looper

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

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

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();//开启Looper循环

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

As shown above,

is called.
1
2
3
4
5
6
7
8

In the prepareMainLooper method called prepare and will be found through the preparation of it is to create a Looper, and assign it to the sThreadLocal. At the same time through the myLooper method to obtain the current thread Looper. Let's look at what new Looper (quitAllowed) initializes

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

Here we finally see the MessageQueue, it created a MessageQueue. The message queue is used to save the subsequent Message. Go back to ActivityThread.main method, found that it calls Looper.loop () is used to open Looper loop, listening to Message Queue MessageQueue the news.

Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static void loop() {
final Looper me = myLooper();//获取Looper
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.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);//通过Handler分发消息
} 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();
}
}

Looper in the first loop to obtain the current thread, but also to the Looper in the MessageQueue, that Looper has been bound with the current thread. In the back to open a for the death cycle and found it to do the event is constantly removed from the message queue message, and finally to the msg.target call its dispatchMessage method, then the target is what? We enter Message

The struct of Message as follows:

1
2
3
4
5
6
7
8
9
10
11
/*package*/ int flags;
/*package*/ long when;

/*package*/ Bundle data;

/*package*/ Handler target;

/*package*/ Runnable callback;

// sometimes we store linked lists of these things
/*package*/ Message next;

deep it , and found dispatchMessage call handler

Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

Through the Handler initialization, it acquires the Looper of the thread it is in, and also gets the message queue in Looper. Of course, if the thread Looper is empty, then it will throw an exception, which explains why the non-main thread to create a Handler to call Looper.prepare and Looper.loop and the main thread is not required, because it has been called by default .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void dispatchMessage(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();
}

The struct between handler and message can be discribled as a figure:
handlermessageloop

Summary

To summarize the flow between them. First of all to create a Handler in the thread where the Handler must have a Looper, if the default in the main thread to help us achieve, and other threads must call Looper.prepare to create a Looper call Looper.loop open the handling of the message. Each Looper has a MessageQueue it is used to store the Message, Handler through post or send .. and a series of operations through Looper message into the message queue, and Looper by opening an infinite loop to monitor the Message processing, and constantly from the MessageQueue out of the message, and to the current Looper handler dispatchMessage binding distribution, the final call Runnable the Run or Handler HandlerMessage method on the message for the final treatment.

ref:

source code of loop, handler and message(Used in the article)