Overview#
Class | Description |
---|---|
Handler | The place where events are actually processed |
Looper | Loops to check for events to be dispatched, retrieves events from the MessageQueue, and hands them over to the Handler for processing (if the queue is empty, it goes to sleep) |
MessageQueue | Stores tasks to be done, generally only allows saving objects of the same type, a queue structure for storing Messages |
Message | The event that needs to be processed |
One Thread corresponds to one Looper
One Looper corresponds to one MessageQueue
One MessageQueue contains multiple Messages
Each Message corresponds to at most one Handler for processing events
Thread & Handler have a one-to-many relationship
Handler#
Accepts and processes Messages
Stores Messages in the MessageQueue
// Handler.java
public class Handler {
...
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
...
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
// Typically used for users to override event handling
public void handleMessage(@NonNull Message msg) {
}
// Dispatching, there are three methods
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
}
In the above dispatching of events, there are three methods, and these three dispatch methods have a specific order; as long as one is handled first, the subsequent methods will not be used.
Message's own callback
Implement the Callback interface
Override Handler#handleMessage
Function
Enqueue messages to the MessageQueue through Handler, there are two series of methods: Post & Send, and the sending time can be controlled
Post series: Convert scattered information into Messages and then use Send to send out
Function | Functionality |
---|---|
boolean post(Runnable r) | Directly sends information |
boolean postAtTime(Runnable, long updatetimeMillis) | Sends the information at a fixed (specified) time |
Send series: Parameters are directly Messages |
Function | Functionality |
---|---|
boolean sendEmptyMessage(int what) | Directly sends information |
boolean sendMessageArFrontOfQueue(Message msg) | Sends the message to the front of the message queue |
boolean sendMessageAtTime(int what, long updatetimeMillis) | Sends the information at a fixed (specified) time |
boolean sendMessageDelayed(Message msg, long delayMillis) | Sends the information after a delay of a few milliseconds |
Post implementation method
// Handler.java
public class Handler {
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// Wraps the callback and sends it out
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); // Obtain Message resources to avoid constant creation, can reuse resources
m.callback = r; // Directly set the Runnable callback function
return m;
}
// Finally, through calculation of current time + extension, call sendMessageAtTime function
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// Sends information at a specified time
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
// If this Thread does not have a specified MessageQueue, an error will be thrown
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
}
Sending Information Methods#
Name | Parameter : Return | Explanation |
---|---|---|
post | Runnable : boolean | Accepts a Runnable object as a Message |
postAtTime | Runnable, long : boolean | Accepts a Runnable object as a Message and sends it after a specified wait time |
sendEmptyMessage | int : boolean | Sends an int information specified by the user, to be handled by the user themselves |
sendMessageAtTime | Message, int : boolean | Sends an int information specified by the user, to be handled by the user themselves |
sendMessageDelayed | Message, long : boolean | Sends Message after a specified wait time (delay) |
Post & Send#
// Send ...
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
// Post ...
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
// Ultimately all will sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) { // 1.
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
Processing Message#
From dispatchMessage, it can be seen that it has a preset order for processing Messages
msg.callback (Runnable)
mCallback (interface)
handleMessage (method)
public void handleMessage(Message msg) {
// User overrides
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
// callback has the highest priority
if (msg.callback != null) { // msg.callback is Runnable
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue & Message#
As the name suggests, it is a FIFO queue, created through native method (Native) memory pointer
private native static long nativeInit();
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Queue Methods#
Name | Parameter : Return | Explanation |
---|---|---|
enqueueMessage | Message, long : boolean | Push into the queue within the specified time |
next | void : Message | Pull an element from the queue |
removeMessages | Handler, int, Object : void | Remove specific elements |
removeMessages | Handler, Runnable, Object : void | Remove specific elements |
MessageQueue#
Since MessageQueue is a Queue, it has FIFO functionality, thus having queue capabilities
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private native static int nativeInit();
MessageQueue Creation#
Connected to the Looper's constructor, when the Looper is created, the MessageQueue is created together
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper Obtains Message Through MessageQueue#
// MessageQueue.java
Message next() {
// If already disposed, mPtr will be set to 0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// Time for the next message
int nextPollTimeoutMillis = 0;
// Entering an infinite loop, meaning next will cause a block
for (;;) {
... omitted part
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null; // Previous message
Message msg = mMessages; // Current message
if (msg != null && msg.target == null) { // target is the Handler
// Found a message that can be sent
do {
prevMsg = msg;
msg = msg.next; // Switch to the next Message
} while (msg != null && !msg.isAsynchronous());
}
// Check the Message again
if (msg != null) {
// Check the time
if (now < msg.when) { // The time for this message has not yet arrived
// Next message is not ready. Set a timeout to wake up when it is ready.
// Calculate remaining time
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Time is up, and there is a Message
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next; // The previous Message is the current Message
} else {
mMessages = msg.next; // Switch to the next Message to be used
}
// Disconnect !! So that GC can detect and reclaim !!! (reachability analysis)
msg.next = null;
... debug messages
msg.markInUse(); // Mark this Message as already used
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) { // Check if quitting
dispose();
return null;
}
... omitted IdleHandler
}
... omitted part
}
IdleHandler#
IdleHandler is an interface within MessageQueue that can notify the user when there are no messages in the MessageQueue and decide how to handle it
// MessageQueue.java
public static interface IdleHandler {
// Returns true, allowing your set interface to continue to exist
// Returns false to remove your set interface
boolean queueIdle();
}
Similarly, analyzing the next method, it can be seen that a MessageQueue can set up to four IdleHandlers
// MessageQueue.java
Message next() {
// If already disposed, mPtr will be set to 0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// Time for the next message
int nextPollTimeoutMillis = 0;
// Entering an infinite loop, meaning next will cause a block
for (;;) {
... omitted part
synchronized (this) {
... omitted part
// Check if there are IdleHandlers set
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;
}
// At most set 4 Idle Handlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Loop
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 {
// Notify the interface when there are no messages
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// Return false to remove
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
... omitted part
}
}
Looper#
It is a key component of messages (Handler, Message, MessageQueue), its role is to drive
Name | Parameter : Return | Explanation |
---|---|---|
myLooper | void : Looper | Obtains the Looper of the current Thread as the main one |
getMainLooper | void : Looper | Obtains the Looper of the main thread |
Looper - ThreadLocal#
Finally, Looper serves as the driving force for the entire Handler mechanism; with Looper's push, the Handler can normally receive and send messages.
Looper contains a MessageQueue (constructed in the constructor)
// android/os/Looper.java
public final class Looper {
// Private function
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
Whenever you create a thread, you must prepare a Looper for that thread; otherwise, when the Handler sends messages, it will crash (as mentioned earlier, it will check). The main steps for creation are as follows:
Looper preparation (prepare function)
Create Handler to send & receive messages
Looper starts operating (loop function)
class LooperThread extends Thread {
private Handler handler;
public void run() {
// 1
Looper.prepare();
// 2.
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//... Process Message
}
}
// 3.
Looper.loop();
}
}
ThreadLocal is briefly mentioned here; ThreadLocal is for thread isolation, using the thread Thread as the Key to store, and the Value is the data you want to copy to each thread (each child thread holds one data)
// android/os/Looper.java
public final class Looper {
// Copying thread data is Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) // A thread can only prepare once
throw new RuntimeException("Only one Looper may be created per thread");
}
// Set Looper for the current Thread
sThreadLocal.set(new Looper(quitAllowed));
}
}
When you create a Handler, it will obtain the Looper through the current thread during the Handler construction
// Handler
public Handler(@Nullable Callback callback, boolean async) {
...
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;
}
// -----------------------------------------------------------------
// Looper
public static @Nullable Looper myLooper() {
// sThreadLocal is static and will obtain Looper through the current Thread as Key
return sThreadLocal.get();
}
Looper & Handler#
The relationship between Looper and Handler can be seen from the new Handler() constructor; it obtains the current thread's Looper through the myLooper method in the ThreadLocal.
// Handler construct
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
... omitted
// Obtain the Loop of the current thread
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;
}
// Looper...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Looper & UI Thread#
In Android, the main thread of an Activity is ActivityThread’s main()
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //"1. "
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
//"2. "
sMainThreadHandler = thread.getHandler();
}
...
// End of event ActivityThreadMain.
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
The main thread's Looper cannot be closed and uses class synchronization.
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// Properties...
private static Looper sMainLooper; // guarded by Looper.class
Looper#loop
, first obtains the Looper owned by the current thread, then retrieves the MessageQueue within the Looper, and finally enters an infinite loop over the entire message queue.
// Looper.java
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
//
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// Resolving who is calling dispatchMessage, target is the Handler in the MessageQueue
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
ThreadLocal#
The main function of ThreadLocal is to achieve thread isolation (isolate data for threads). Simply put, you can think of ThreadLocal as a Map (but it is not actually a Map); its Key is Thread, and the Value is a generic type.
// The set() method is used in Looper.prepare() internally
public void set(T value) {
Thread t = Thread.currentThread(); //"1. "
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// The get() method is used in the Handler() constructor internally
public T get() {
Thread t = Thread.currentThread(); //"2. "
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
It uses the currently calling Thread.currentThread as the Key to store, and the Value is the generic type.
According to the current thread (currentThread), it retrieves the Value.