This is the fourth day of my participation in the More text Challenge. For details, see more text Challenge
This article is based onAndroid 9.0.0Source code, to analyze the use of Handler
framework/base/core/java/andorid/os/
- Handler.java
- Looper.java
- Message.java
- MessageQueue.java
Copy the code
Handler function
- Task scheduling: Yes
post()
和send()
To specify when a task will be executed - Thread switching: Perform time-consuming operations, such as network requests, IO operations, etc., that need to be run in child threads or otherwise block the main thread. You usually need to update the UI after a time-consuming operation such as a network request, and if you update the UI in a child thread, the program will crash. Because Android’s UI is thread-unsafe. Use Rxjava in Android, but also with RxAndroid to use, RxAndroid internal use Handler to achieve thread switching.
Common mistakes
Common child thread update UI, reproduce code, more specific see Android child thread and update UI issues
textView = (TextView) findViewById(R.id.txt);
new Thread(new Runnable() {
public void run(a) {
SystemClock.sleep(3000);// This sentence is not incorrect. See the link above for details
textView.setText("From from child thread");
}
}).start();
Copy the code
Running exception information
ErrorInfo: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6903)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1050)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
Copy the code
Can see error occurred in the android. View. ViewRootImpl# checkThread
void checkThread(a) {
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
So we’re going to see if mThread is equal to the current thread and we’re going to see what mThread is and where it’s assigned
public ViewRootImpl(Context context, Display display) {... mThread = Thread.currentThread(); . }Copy the code
So the thread that was assigned in the constructor, which is the thread that created the ViewRootImpl where was the ViewRootImpl created? I’m not going to go into it here, it’s in the main thread and you can refer to this for more specific exception analysis
Basic usage
android.os.Handler handler = new Handler(){// Retrieve handler from the main thread
@Override
public void handleMessage(final Message msg) {
// Here the message is accepted and processed}};new Thread(() -> {
try {
Thread.sleep(2000);// Perform time-consuming operations in the child thread
// Send the message
Message message = Message.obtain();
message.what=1;
message.obj=new Object();
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Handler().post(new Runnable() {
@Override
public void run(a) {
//doSomething}});Copy the code
Instantiating a Handler that overwrites the handleMessage method and then calls its Send and post methods as needed is easy to use and supports delayed messages. (See the API documentation for more information)
But we don’t see how Handler relates to MessageQueue and Looper
Handler source code Analysis
Handler is instantiated
Starting with the constructors, which we usually create from the main thread, let’s look at what the Handler constructors are
-
Handler()
-
Handler(Callback callback)
-
Handler(Looper looper)
-
Handler(Looper looper, Callback callback)
-
Handler(boolean async)
-
Handler(Callback callback, boolean async)
-
Handler(Looper looper, Callback callback, boolean async)
Just look at the last two constructors, because the previous ones also call the latter in turn
Callback Callback, Boolean async
public Handler(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;
}
Copy the code
Handler(Looper Looper, Callback Callback, Boolean async)
Which to instantiate
If mLooper == null, an exception will be raised. If mLooper == null, an exception will be raised
Can’t create handler inside thread that has not called Looper.prepare()
We’ve probably seen this mistake before. In fact, when instantiating the Handler, we will check whether the Looper of the current thread exists. If not, an exception will be raised. That is to say, Looper must be created before Handler is created. If looper.myLooper () does not retrieve a Looper object, this error will be reported. If looper.myLooper () does not retrieve a Looper object, this error will be reported. If looper.myLooper () does not retrieve a Looper object, this error will be reported.
Let’s trace the Looper#myLooper method in to figure out why this exception was thrown.
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
With only one line of code to pull the Looper object out of the thread, it is reasonable to believe that the ThreadLocal set the Looper object in through the set method. For ThreadLocal, refer to ThreadLocal source analysis.
Think about where ThreadLocal sets the Looper object. Can’t create handler inside thread that has not called looper.prepare (). Could that be Looper’s prepare method?
public static void prepare(a) {
prepare(true);
}
// Call the private constructor
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));
}
Copy the code
ThreadLocal does set Looper in the Looper#prepare method, and we can see from the first line that there is only one Looper object per thread.
To create a Handler, looper.mylooper () must be non-null. To create looper.mylooper () must be non-null.
At this point, Looper is associated with the ThreadLocal.
MessageQueue instantiation
Let’s look at how Looper is constructed
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
Every time we instantiate a Looper, we call its constructor and instantiate a MessageQueue. Compared to Looper and Handler, MessageQueue is more complicated. Because of the internal use of JNI programming. Initialization, destruction, and enqueueing events all use native methods. You can see the definition of the source code at android_os_MessageQueue. For more information, see MessageQueue instantiation
Let’s move on to the Handle constructor
mQueue = mLooper.mQueue
We know that the message is stored in the MessageQueue MessageQueue, and the MessageQueue is in the above Looper constructor new, so that Handler through Looper and MessageQueue also established the association.
Looper.mylooper (); looper.myLooper (); looper.prepare (); looper.prepare (); Looper.prepare() ¶ Looper.prepare() does not throw an exception. This is actually a special case, because we usually create handlers in the main thread, the UI thread. In the main thread, a Looper object has been created for us, so no exception will be thrown. In cases where an exception will be thrown, the Handler created in the child thread does not call looper.prepare () to create a Looper object. Let’s see when the main thread creates the Looper object.
In ActivityThread’s main method, this method is the entry point to the application.
public static void main(String[] args) {... Looper.prepareMainLooper();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();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
Looper.prepareMainLooper();
public static void prepareMainLooper(a) {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code
You can see that the first line still calls the method prepar(false) (false means no exit). So the main thread has already created a Looper object.
The Handler creation process has been analyzed, and now it is clear.
The relationship between Handler, MessageQueue and Looper
In conclusion, Handler creation is dependent on Looper. The main thread creates a Looper object by default. Each Looper is associated with a thread (a ThreadLocal encapsulates a Looper). Each Looper encapsulates a message queue. In this way, Handler, Looper, MessageQueue and Thread are related. The Handler is created in the main thread because it is associated with the main thread’s message queue so that the Handler#handleMessage method executes in the main thread and is thread-safe to update the UI.
Handler Sends messages
Recall from the beginning of our basic usage we mentioned that Handler is usually sent through the following two methods
handler.sendMessage(message); handler.post(runnable);
Sending process
Let’s analyze handler.sendMessage(Message) first.
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);
}
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);
}
Copy the code
SendMessage will call the sendMessageDelayed method and pass in the message object. The second parameter is the delay time, which defaults to 0 when using sendMessage. Finally, sendMessageAtTime will be called. As analyzed above, when Looper object is created, a MessageQueue is created, so as long as Looper is created normally, the MessageQueue is not empty. So the enqueueMessage method on the last line, the source code is as follows
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
Assign the handler itself to MSG. Target
Msg.setasynchronous (true sets whether a message is asynchronous, which is a property of message. A Thread can have only one Looper and one MessageQueue, but it can have many handlers. If async is true when the Handler is initialized, then all messages posted by this Handler will carry the asynchronous property. MessageQueue’s postSyncBarrier(long when) can be used to insert a synchronous barrier into the queue. A synchronous barrier is a special message whose target=null is like a card that when inserted, All synchronous messages after this will be stuck, and only asynchronous messages will be retrieved. You can also remove the synchronization barrier by removeSyncBarrier(int token) on the MessageQueue. The token is the value returned by the postSyncBarrier method. But both methods are currently hidden. So what you usually use is just plain messages. (Note: From the source code to understand Handler)
Then we finally call queue.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;
}
// Whether the message is in use
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// Obviously enqueueMessage needs to be synchronized, because there are scenarios where multiple threads insert messages into a Loop thread's MessageQueue.
If mMessages is empty, there is no Message yet. If the current inserted Message does not require a delay, or the delay is smaller than the delay of the mMessages header, the Message is inserted into the queue. The message currently being inserted needs to be placed in the header
// If you need to wake up the queue, you need to determine the status of the current Loop thread.
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// In this case, insert the Message before the first non-empty Message whose Delay event is smaller than the current Message. Insert the Message before the first non-empty Message whose Delay event is smaller than the current Message into the queue. Let's not discuss it.
// Finally, look at key 3. If you need to wake up the Loop thread, wake up by nativeWake.
// 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
Messagequeue has an object mMessage that points to the MSG currently passed, that is, the latest message. In the sendMessageAtTime(Message MSG, long uptimeMillis) method, the second parameter specifies the time, and then the uptimeMillis is used to sort the messages, so that each Message is correlated by time. The first message points to the next message.
The Handler calls sendMessage and finally passes the message object to Messagequeue.
Take out the message
So how does the message get out of the message queue? At this point we’ll go back to the Main method of ActiviryThread and look for some clues. The source code is posted above. Looper.loop(), the second-to-last line, is found, which simply means that the message performs a loop operation. android.os.Looper#loop
public static void loop(a) {
// Make sure the MessageQueue is ready
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
finalMessageQueue queue = me.mQueue; .for (;;) {
//for an infinite loop, blocking the next() method of the message queue;
// Read messages from the queue and remove them. If the queue is empty, block and wait
Message msg = queue.next(); // might block
if (msg == null) {// Break out of the loop. Looper exit takes advantage of this
// No message indicates that the message queue is quitting.
return; }...try{ msg.target.dispatchMessage(msg); . }finally{... }...// Clean up, recycle to the cache poolmsg.recycleUnchecked(); }}Copy the code
The loop method is an infinite loop, but why doesn’t it get stuck on the main thread
Why doesn’t the main thread get stuck in looper.loop () on Android?
Why can’t the Loop() method in Looper cause the main thread to get stuck?
In-depth understanding of MessageQueue
Loop is a bit complicated, so let’s borrow a picture
When we call Looper#loop(), the entire Looper loop starts processing messages. In the figure above is a loop that we have marked in green. When we call MessageQueue#next() ‘ ‘in the loop to get the next message, we will call the nativePollOnce() method, which may cause the thread to block and non-block. When the thread is non-blocking, it will go back from the Native layer to the Java layer. A message is retrieved from MessageQueuue and then processed by Looper. If fetching causes a thread to block, there are two ways to wake up the blocked thread. One is when a new message is added to the queue and is triggered before all the previous messages in the queue, and the timeout is reset. If the timeout period is reached, it can also be returned from the sleep state, returning to the Java layer to continue processing. Therefore, the function of Looper at the Native layer is to block Looper by blocking the process of obtaining messages from the message queue.
Take a look at the key Message MSG = queue.next() for further analysis see MessageQueue for Message execution and MessageQueue for Message management
Message next(a) {...int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if(nextPollTimeoutMillis ! =0) {
Binder.flushPendingCommands();
}
// Whether to block and wait, the first time must not block
// Call the nativePollOnce() method of the Native layer for precise time blocking.
// At the Native layer, you go to the pullInner() method and use epoll_wait to block the wait to read the pipe notification.
// If there is no message from the Native layer, then this method will not return. At this point, the main thread releases CPU resources and enters the hibernation state.
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {// Mutex synchronization
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// Whether barier exists
if(msg ! =null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// There is a synchronous delimiter, find the MSG behind the asynchronous property
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
// Whether the first message needs to block wait, and calculate the blocking wait time
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.
// Wait indefinitely
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 there is no Message that can be executed immediately, check whether there is an IdleHandler that needs to be processed. If there is no IdleHandler, return, block and wait. If there is, execute IdleHandler
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);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// If there is no message, then idler. QueueIdle is executed
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 {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
NextPollTimeoutMillis: nextPollTimeoutMillis: nextPollTimeoutMillis: nextPollTimeoutMillis
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0; }}Copy the code
MSG. Target is a handler, so when we loop the message, we throw it to the handler#dispatchMessage method
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
Since this method does not pass a callback, it eventually calls handleMessage, as we’ll see
/** * Subclasses must implement this to receive messages. */
public void handleMessage(Message msg) {}Copy the code
Now, I’m sure you’re familiar with this, but this is the way we rewrite it.
Let’s look at another method of sending messages, handler.post(runnable)
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
Copy the code
Receive an object that implements the Runable interface and pass it to the getPostMessage() method. Follow up with the getPostMessage() method
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
Wrap Runable as message callback. So, if we use the POST method to send a message and the callback field is not empty when the dispatchMessage is executed, then the handleCallback() method will be executed instead of the handleMessage method.
private static void handleCallback(Message message) {
message.callback.run();
}
Copy the code
The addition and processing of idle handlers
What is a idle processor
From the above analysis, we can see that MessageQueue obtains the next Message to be processed through an infinite loop through the next method. If there is no Message to be processed at the current time, the next loop will perform the sleep operation
- The interval between no executable message is fetched and the next for loop goes to sleep is called idle time
- An object that processes transactions at idle time is called a idle handler
The addition of idle handlers
public static interface IdleHandler {
/** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */
boolean queueIdle(a);
}
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) { mIdleHandlers.add(handler); }}Copy the code
The following information can be obtained from the above code
- Idle handlers are described using the IdleHandler interface
- Idle processor by MessageQueue. AddIdleHandler () to add
- Idle processing using MessageQueue. MIdleHandlers maintenance
Processing of idle messages
public final class MessageQueue {
// Idle message set
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// An array of idle message handlers
private IdleHandler[] mPendingIdleHandlers;
Message next(a) {...for(;;) {...synchronized (this) {
// Omit the code to get MSG.// 1. Get the number of idle handlers from the idle message set mIdleHandlers
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 2 If there is no idle handler, the next for loop is performed
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue; }...// 3. Convert the collection of idle message handlers into an array
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 4. Process idle messages
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];// Get the idle handler for the i-th location
mPendingIdleHandlers[i] = null; / / empty
boolean keep = false;
try {
// 4.1 Processing idle messages
keep = idler.queueIdle();
} catch (Throwable t) {
......
}
if(! keep) {synchronized (this) {
// 4.2 Going here means it is a one-time handler, removed from mIdleHandlersmIdleHandlers.remove(idler); }}}... }}}Copy the code
If MessageQueue. Next fails to obtain MSG, it will process some idle messages
- Gets the number of idle handlers from the idle message set mIdleHandlers
- If there is no idle handler, the next for loop goes through
- If there are idle handlers, the collection of idle message handlers is converted to an array of mPendingIdleHandlers
- The for loop processes idle messages
- Call idleHandler. queueIdle to process idle messages
- If MessageQueue. Next does not get the MSG idle time, the processing will continue
- Returning false means it is a one-time handler, removed from mIdleHandlers
- Call idleHandler. queueIdle to process idle messages
conclusion
We found that whether we used the POST method or the sendMessage method to send the message, the sendMessageDelayed method would eventually be called. The process of the handler appending the message to the MessageQueue is the same, and then Looper constantly takes out the message from the MessageQueue, and the handler distributes and processes the message, which constitutes a perfect Android message mechanism system.
Handler extension
Handler is simple and easy to use, but it still needs some attention.
Because of the Handler feature, it is widely used in Android, such as: AsyncTask, HandlerThread, Messenger, IdleHandler, IntentService and so on.
Common memory Leaks
This Handler allows us to send delayed messages, and if the user closes the Activity during the delay, the Activity will leak.
This leak is caused by the fact that the Message holds the Handler, and because of the nature of Java, the inner class holds the outer class, which causes the Activity to be held by the Handler, and ultimately causes the Activity to leak.
The most effective way to solve this problem is to define the Handler as a static inner class that holds a weak reference to the Activity inside and removes all messages in time.
The example code is as follows:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if(activity ! =null) { activity.handleMessage(msg); }}}Copy the code
And then remove the message before activity.onDestroy (), adding a layer of assurance:
@Override
protected void onDestroy(a) {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
Copy the code
In this way, the double protection can completely avoid memory leaks.
Note: Simply removing messages on onDestroy is not safe because onDestroy is not necessarily executed.
The Callback function in Handler
There are several requests in the Handler constructor to pass the Callback, so what is it and what can it do?
Take a look at the handler.dispatchMessage (MSG) method:
public void dispatchMessage(Message msg) {
// Callback is Runnable
if(msg.callback ! =null) {
handleCallback(msg);
} else {
If the callback handles the MSG and returns true, the handleMessage will not be called back
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}Copy the code
When a message is processed by the Callback and intercepted (returning true), the Handler’s handleMessage(MSG) method is not called. If the Callback handles the message but does not intercept it, that means a message can be handled by both the Callback and the Handler.
That’s interesting. What does that do?
We can use the Callback interception mechanism to intercept the Handler’s message!
Scenario: Hook activityThread.mh. ActivityThread has a member variable, mH, which is a Handler and an extremely important class. Almost all plug-in frameworks use this method.
The best way to create Message instances
Since Handler is very commonly used, In order to save overhead, Android has designed a recycling mechanism for Message, so we try to reuse messages when using to reduce memory consumption.
There are two methods:
- through
Message
Static method ofMessage.obtain();
To obtain; - through
Handler
Public method ofhandler.obtainMessage();
。
Looper mechanism
We can use Looper’s mechanics to help us do a few things:
-
Post Runnable to the main thread for execution
Activity.runOnUiThread(Runnable)
public final void runOnUiThread(Runnable action) { if(Thread.currentThread() ! = mUiThread) { mHandler.post(action); }else{ action.run(); }}Copy the code
View.post(Runnable)
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if(attachInfo ! =null) { // Send the Post message directly through handler return attachInfo.mHandler.post(action); } When attachInfo is assigned, a message is sent via handler. getRunQueue().post(action); return true; } Copy the code
-
Use Looper to determine whether the current thread is the main thread
public final class MainThread { private MainThread(a) {}private static final Handler HANDLER = new Handler(Looper.getMainLooper()); public static void run(@NonNull Runnable runnable) { if (isMainThread()) { runnable.run(); }else{ HANDLER.post(runnable); }}public static boolean isMainThread(a) { returnLooper.myLooper() == Looper.getMainLooper(); }}Copy the code
Do Looper and Handler have to be on the same thread? Can MainLooper be used to create Handler in a child thread?
Looper and Handler do not need to be in the same thread. By default, the Looper of the current thread is fetched from a ThreadLocal, but we can create a Handler by explicitly specifying a Looper. For example, if we want to send a message from a child thread to the main thread, then we can
Handler handler = new Handler(Looper.getMainLooper());
Copy the code
A method for performing UI operations in a child thread
-
Handler’s post() method
-
View’s post() method
-
The runOnUiThread() method of the Activity
How to understand the asynchronous Handler
See The Handler post ii: How to understand the “asynchronous” Handler?
Messagequeue.next () blocks because a delayed message has been found. So why aren’t the non-delayed messages added later blocked? What’s inside the messagequeue.next () method?
When the messagequeue.next () method is called, the nativePollOnce() method of the Native layer is called for precise time blocking. At the Native layer, you go to the pullInner() method and use epoll_WAIT to block the wait to read the pipe’s notification. This method will not return if the message is not received from the Native layer. At this point, the main thread releases CPU resources and enters the hibernation state.
When we join Message, will call MessageQueue. EnqueueMessage () method, after adding the Message, if the Message queue blocked, will call Native layer nativeWake () method to wake up. It ends the blocking by writing a Message to the pipe, triggering the nativePollOnce() method mentioned above to return so that the added Message can be distributed.
MessageQueue. EnqueueMessage () USES synchronized code block to synchronize.
Data: Research on Native layer of Handler in Android
Looper’s exit method?
What’s the difference between quit() and quitSafely() in a child thread that creates a Looper and terminates the message loop after using it? What is the essence of quit() and quitSafely()?
The essence of quit() and quitSafely() is to quit looper.loop () by having next() of the message queue return null. Quit () directly terminates Looper, no Message is processed, and all attempts to put messages into the Message queue will fail. For example, Handler. SendMessage () will return false, but it is unsafe. The Looper is terminated because there may be a Message in the queue that has not been processed. The quitSafely() call will not terminate Looper until all messages have been handled, and any attempt to put the Message on the Message queue will fail.
Summary of knowledge
Some knowledge points can be drawn from the previous text, summarized, convenient memory.
Handler
Behind aLooper
,MessageQueue
Support,Looper
In charge of message distribution,MessageQueue
Responsible for message management- When creating a
Handler
You have to create it firstLooper
Looper
There’s an exit function, but for the main threadLooper
Not allowed to exit- Asynchronously threaded
Looper
You need to call it yourselfLooper.myLooper().quit();
exit Runnable
It’s encapsulated inMessage
It’s kind of a specialMessage
Handler.handleMessage()
The thread isLooper.loop()
The thread on which the method is calledLooper
The thread is not createdHandler
The thread- Using inner classes
Handler
May cause memory leaks, even if removing delayed messages in Activity. OnDestroy must be written as a static inner class
reference
The messaging mechanism in Android
Android point will Taiwan: beacon smoke [-handler -]
Handler didn’t get it. Why jump ship?
Android Advanced Interview -1: Handler related
Android message mechanism: Handler, MessageQueue and Looper
(Android P)