Main usage scenarios of Handler
When the child thread completes the time-consuming operation, it sends a message Message
to the main thread through Handler
to update the UI interface. Because Android updates the UI in the main thread, when time-consuming operations occur on the main thread, it will cause the user interface to freeze, so we generally put time-consuming operations (network requests, IO, etc.) into sub-threads. Then let the main thread update the UI through Handler
.
new Handler()
If it is a parameterless constructor, the overloaded constructor is called and null
and false
are passed in respectively. And assign values to two global variables in the constructor, both of which are obtained through Looper
.
@UnsupportedAppUsage final Looper mLooper; final MessageQueue mQueue; @UnsupportedAppUsage final Callback mCallback; final boolean mAsynchronous; public Handler(@Nullable Callback callback, boolean async) {<!-- --> if (FIND_POTENTIAL_LEAKS) {<!-- --> final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) & amp; & amp; (klass.getModifiers() & amp; 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 " + Thread.currentThread() + "that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
Looper
The entry function to start a Java is the main
method. When the main
function is executed, the program will stop running, but when we open an Activity
, as long as If it does not return, it will always be displayed, that is, the process where Activity
is located will always be running.
In fact, Looper
maintains an infinite loop internally to ensure the continuous operation of the APP.
When Activity
starts, the ActivityThread#main
method is the entrance to the new APP process
// ActivityThread#main public static void main(String[] args) {<!-- --> //... //Initialize the Looper object of the current process Looper.prepareMainLooper(); //... if (sMainThreadHandler == null) {<!-- --> sMainThreadHandler = thread.getHandler(); } //... \t //Call Looper#loop method to start infinite loop Looper.loop(); //... } // Looper#prepareMainLooper public static void prepareMainLooper() {<!-- --> prepare(false); synchronized (Looper.class) {<!-- --> if (sMainLooper != null) {<!-- --> throw new IllegalStateException("The main Looper has already been prepared."); } //Assign the Looper object taken from the thread local variable to the sMainLooper object sMainLooper = myLooper(); } } // Looper#prepare private static void prepare(boolean quitAllowed) {<!-- --> // The Looper#prepare method in a thread can only be executed once, otherwise an exception will be thrown if (sThreadLocal.get() != null) {<!-- --> throw new RuntimeException("Only one Looper may be created per thread"); } // Create a new Looper object and set it to thread local variables //The created Looper is bound to the current thread sThreadLocal.set(new Looper(quitAllowed)); } // Looper#Looper constructor private Looper(boolean quitAllowed) {<!-- --> // Initialized the MessageQueue object mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } // Looper#myLooper public static @Nullable Looper myLooper() {<!-- --> // Get the Looper object from the thread local variable return sThreadLocal.get(); }
The Looper#prepare
method can only be called once in a thread (it will determine whether the thread local variable is empty), that is, the constructor of Looper
can only be called in a thread. Once, MessageQueue
in the constructor will only be initialized once in a thread, so a thread will only have one MessageQueue
object.
The task of Looper
is to continuously retrieve Message
from MessageQueue
and then process the tasks specified in Message
. The Looper.loop
method called in the main
method does this. There is an infinite loop in the Looper#loop
method, which is why the Android App process can continue to run.
If the message
taken out from MessageQueue
is not empty, take out the target
object of message
and call its The >dispatchMessage
method handles the Message
method itself. This target
object is the Handler
.
// Looper#loop public static void loop() {<!-- --> final Looper me = myLooper(); //... for (;;) {<!-- --> if (!loopOnce(me, ident, thresholdOverride)) {<!-- --> return; } } } // Looper#loopOnce private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {<!-- --> Message msg = me.mQueue.next(); // might block if (msg == null) {<!-- --> return false; } //... try {<!-- --> msg.target.dispatchMessage(msg); if (observer != null) {<!-- --> observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) {<!-- --> //... } finally {<!-- --> //... } //... return true; } //Message public final class Message implements Parcelable {<!-- --> //... // target is the passed Handler object @UnsupportedAppUsage /*package*/ Handler target; } // Handler#dispatchMessage public void dispatchMessage(@NonNull Message msg) {<!-- --> if (msg.callback != null) {<!-- --> handleCallback(msg); } else {<!-- --> if (mCallback != null) {<!-- --> if (mCallback.handleMessage(msg)) {<!-- --> return; } } //Call handlerMessage method handleMessage(msg); } } // Handler#handlerMessage public void handleMessage(@NonNull Message msg) {<!-- --> //This method is empty //You need to inherit and override this method when creating Handler }
Handler#sendMessage method
Handler
includes several overloaded sendMessage
methods, which finally inserts Message
into the message through the enqueueMessage
method QueueMessageQueue
. This message queue is the MessageQueue
we created through Looper
in the main
method of ActivityThread
.
In the enqueueMessage
method, set the Handler
itself as the target
object. Subsequent Message
will call this Handler
‘s dispatchMessage
to handle. MessageQueue
is an ordered queue sorted according to the execution time of Message
. When using the enqueueMessage
method, it will be based on the Message
Time when
to orderly insert Message
into the queue.
Handler#post method
When Looper#loop
takes out Message
from MessageQueue
, target
‘s dispatchMessage
will be called > to process messages.
If msg.callback
is not empty, handleCallback(msg)
will be executed to process the message. If msg.callback
is empty, will be called. code>handleMessage(msg)
to handle messages (that is, the method we override).
private static void handleCallback(Message message) {<!-- --> message.callback.run(); }
handleCallback
will directly execute the run
method of Runnable
. Runnable
is actually a callback interface that communicates with thread Thread
is irrelevant.
Why does Looper#loop method not block the main thread
The Looper#loop
method is actually an infinite loop, but it will not cause the UI thread to block. Because the native
method nativePollOnce
is called in the next
method of MessageQueue
, the main thread will be released when this method is called The CPU resource enters the dormant state until the next message arrives or a transaction occurs. Write data through the pipe
pipe write segment to wake up the main thread, using the epoll
mechanism, which is a The IO multiplexing mechanism can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), the corresponding program is immediately notified to perform a read or write operation. Essentially synchronous I/O, that is, reading and writing are blocked. of. Therefore, the main thread is in a dormant state most of the time and does not consume a lot of CPU resources.
Handler may cause memory leaks
The Message
of Handler
is stored in MessageQueue
. Some Message
cannot be processed immediately, and the time it exists in the queue Very long (lower sorting or delayed execution of sendMessageDelay
is used to send msg
, resulting in Handler
not being recycled. If Handler
is non-static, then Handler
will also cause the Activity
and Service
that refer to it cannot be recycled.
The possible scenario is: sending a delay msg
to MessageQueue
, and finish
closing Activity
before execution. As a result, the memory of handler
and Activity
will not be released.
The solution is to use a static Handler
internal class to inherit handler
, and the object held by Handler
uses weak references, and in Activity
‘s onDestroy
method removes the message passed into the MessageQueue
.
Handler attributes
- A thread has only one
Looper
and can have multipleHandler
to process messages.Looper
passes theHandler
object’s>handlerMessage
method to handle messages. - If you want to use
Handler
in two threads that are not the main thread, you need to create aLooper
in the thread that processes the logic. WithLooper
Only then can you createHandler
, and then use theLooper
thread’shandler
object in another thread to sendmessage
. - The method for the main thread to obtain
Looper
is:Looper.getMainprepare()
. You can directly create a newHandler
in the child thread. You need tofirst in a thread. >Looper.prepare()
andLooper.loop()
- When
Handler
is created, it will use theLooper
of the current thread to construct a message loop system.Looper
will be bound to which thread it is created in.Handler
processes messages in the thread corresponding to its associatedLooper
.
How to verify whether Handler holds a reference to an external class
Judged by class
.
Method to update UI in child thread
First, you need to create a Handler
object in the main thread and override the handleMessage()
method.
Then when UI operations need to be performed in the child thread, a Message
object is created and the message is sent through Handler
.
The message will then be added to the queue of MessageQueue
to be processed, and Looper
will always try to retrieve pending messages from MessageQueue
, and finally distributed back to the handleMessage()
method of Handler
.
Since we passed in Looper.getMainLooper()
in the constructor of Handler
, the code in the handleMessage()
method will also be in Run in the main thread, and then you can update UI
here.
After a Message is called through the above process, it enters the main thread from the sub-thread, and changes from being unable to update the UI to being able to update the UI.
Summary
Application startup starts from the ActivityThread#main
method. The Looper#prepare
method is first executed, creating a Looper
object and binding it to the current threadMainThread
, and the Looper
object will initialize the MessageQueue
queue when it is created, so we will get a Looper
in the main thread Objects and MessageQueue
queues.
When we create a Handler
sub-object, obtain the bound Looper
object through the ThreadLocal
method in the constructor and obtain this The member variables of the Looper
object MessageQueue
serve as the member variables of the Handler
object.
Call the sendMessage
method of the created Handler
subclass object in the child thread, and set the target
attribute of the Message
object. Set as the Handler
sub-object itself, call the enqueueMessage
method of the MessageQueue
object to insert msg
into the MessageQueue
code> in.
In the Looper#loop
method in the main thread, messages in MessageQueue
will be continuously read. If the message is not empty, msg#target#dispatchMessage will be executed.
method, this method will call the handlerMessage
method that we overridden when creating the Handler
object.