What is Handler
Message handling mechanism in Android.
Handler sends and processes thread-specific Message and Runnable objects.
So what can we do with this Handler?
Second, the purpose of Handler
Handler is mainly used for delayed message processing and message communication between threads
Now that we know what this Handler is for, how can we use this Handler to implement this function?
3. Use of Handler
What is Handler
Handler sends and processes thread-specific Message and Runnable objects
So let’s start with sending the Handler
First, let’s look at the official Handler API’s Public methods for sending
methods | meaning |
---|---|
post(Runnable r) | Add Runnable R to the message queue. |
postAtTime(Runnable r, long uptimeMillis) | Runnable R is added to the message queue and uptimeMillis runs at a specific time. |
postDelayed(Runnable r, long delayMillis) | Adds Runnable R to the message queue to run after the specified time. |
sendEmptyMessage(int what) | Send a message that contains only the what value. |
sendMessage(Message msg) | After all pending messages up to the current time, the message is pushed to the end of the message queue. |
sendMessageAtTime(Message msg, long uptimeMillis) | Queues messages after all pending messages before uptimeMillis in absolute time (in milliseconds). |
sendMessageDelayed(Message msg, long delayMillis) | After all pending messages prior to time (Current time + delayMillis), the message is queued. |
* Only the key methods are listed in the table above. Refer to the official documentation for more information
It can be seen from the above table that there are two main sending methods: POST and sendMessage.
Let’s analyze the use of these two methods:
Post adds a Runnable object to a message queue. After the message arrives, the Runnable object can receive a callback (processing) from the run method.
2. SendMessage is the handleMessage that adds the Message object to the Message queue. At the same time, it needs to implement the handleMessage of the Handler to process the Message.
Here are common examples of both methods:
The first is the POST method:
// Create a handler instance
private Handler handler = new Handler();
// use post to send messages and run to receive messages
handler.post(new Runnable() {
@Override
public void run(a) {
// Handle accordingly}});Copy the code
Then sendMessage uses:
// create a static inner class subclass of Handler
private static class MyHandler extends Handler {
// Implement handleMessage to receive processing messages
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// process the message according to msg.what}}// create a Handler instance
private Handler mhandler = new MyHandler();
// 3, send message - usually in thread
new Thread(){
@Override
public void run(a) {
try {
Message message = Message.obtain();
message.what = 1;
message.obj = message;
mHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Copy the code
- * The above usage examples are only examples of common usage methods. There are more usages depending on the specific business
Ok, we know how to use Handler to send and receive messages. So let’s debunk Handler and look at how it implements message processing.
How does Handler implement communication between threads
Let’s start by sending messages.
1. Send a message
Pick out the key POST and sendMessage methods in Handler:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public 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);
}
Copy the code
As you can see from the above code, all post and sendMessage methods eventually call the sendMessageAtTime method.
The Runnable in the POST method is encapsulated in the Message via getPostMessage() :
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
Copy the code
Next look at 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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// The current Handler object is assigned to Message's target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
The main thing that sendMessageAtTime does is to add a message to a message queue. The current Handler object is also assigned to Message’s target.
MessageQueue is taken from mQueue, which is initialized in the Handler constructor:
// Use the current thread looper
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()); }}// 1. Initialize mLooper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// get the mQueue variable
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
// Provide looper at initialization
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
// Get the mQueue variable
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
Here we need to know that the mQueue is provided by Looper.
MessageQueue enqueueMessage (MessageQueue enqueueMessage)
boolean 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(TAG, 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;
}
Copy the code
The enqueueMessage method mainly inserts messages into linked lists: 1. If mMessages is null or when is 0, or when is less than mMessages, insert MSG into the head of the linked list and assign the value to mMessages
At this point, the MSG message is in the message queue (actual linked list structure).
Let’s look at the process of processing messages.
Process the message
We know from the process of sending the message that the final message is put into the mMessages linked list structure in MessageQueue, so we study the process of reading the message and handing it to the Handler according to mMessages.
The next() method in MessageQueue handles the read process:
Message next(a) {...int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1; }... }... }}Copy the code
The key part of next() is an infinite for loop that removes and returns from the node with a message, and blocks and waits without a message.
Then we need to look at the timing of the next() call. We know that the mQueue in the Handler is taken from Looper, so we need to look at the timing of the next() call from Looper.
The loop() method in Looper calls MessageQueue’s next() method
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
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;
// 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow".0);
boolean slowDeliveryDetected = false;
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;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if(traceTag ! =0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false; }}else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true; }}}if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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(); }}Copy the code
Loop () first fetches the looper object through the myLooper() method, which fetches the current thread looper through ThreadLocal and is key to processing the message back to the original thread.
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
Loop () is an infinite for loop. The first sentence of the loop is to call MessageQueue’s next() method and may block.
Message msg = queue.next(); // might block
After the Message MSG is read, the target.dispatchMessage(MSG) method is called. msg.target.dispatchMessage(msg);
The target in Message is the current Handler object. Now look at the dispatchMessage() method in Handler:
/** * 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); }}Copy the code
If (1) callback is not null, handleCallback is called
private static void handleCallback(Message message) {
message.callback.run();
}
Copy the code
As we saw earlier, message’s callback is the Runnable object in the POST method.
The message is processed in the run method of the Runnable object.
(2) If callback is null and mCallback is not null, call McAllback. handleMessage
public interface Callback {
/ * * *@param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
Copy the code
The message is processed in the mCallback callback. (3) If mCallback is null or the handleMessage of mCallback returns false, the handleMessage method of the Handler is called
/** * Subclasses must implement this to receive messages. */
public void handleMessage(Message msg) {}Copy the code
A subclass implements handleMessage and processes messages.
At this point, the Handler implementation of the message sending and processing process is complete.