Android | Handler

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 multiple Handler to process messages. Looper passes the Handler 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 a Looper in the thread that processes the logic. With Looper Only then can you create Handler, and then use the Looper thread’s handler object in another thread to send message.
  • The method for the main thread to obtain Looper is: Looper.getMainprepare(). You can directly create a new Handler in the child thread. You need to first in a thread. >Looper.prepare() and Looper.loop()
  • When Handler is created, it will use the Looper 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 associated Looper.
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.