“
The relevant content of the article has been authorized to “Guo Lin” public account published
“
preface
Nice to meet you ~ welcome to read my article.
This article, the fourth in a series, looks at the internal key classes of Handler: Handler, and HandlerThread.
“
The Handler series is divided into six parts:
- Part 1: Start the series with Handler from 0;
- The second part introduces the internal schema of Handler, and elaborates on the key class ThreadLocal.
- The third part: parsing Handler internal key classes: Message, MessageQueue, Looper;
- Part 4: Handler internal key class: Handler, at the same time introduce HandlerThread;
- The fifth part: summarize Handler, think Handler from the perspective of source code design;
- Part 6: Handler FAQ;
This is the fourth article in a series. Readers can go to the author’s home page and select the sections they are interested in.
“
So, let’s get started.
The body of the
Handler
An overview of the
The whole message mechanism is called the Handler mechanism so that we can know how often we use the Handler. In general, our use is also around the Handler. Handler is the originator and Handler of the whole message mechanism. Messages are sent from different threads to the target thread’s MessageQueue through Handler, and then the Looper of the target thread calls Handler’s dispatchMessage method to process the message.
Create a Handler
In general, we can use Handler in two ways: inherit the Handler and rewrite the handleMessage method, or create a Handler object directly and pass in the callBack, which we discussed in the previous section using Handler.
It is important to note that creating a Handler must specify the Looper argument, rather than using the no-argument constructor directly, as in:
Handler handler = new Handler(); / / 1
Handler handler = new Handler(Looper.myLooper())/ / 2
Copy the code
One is wrong, two is right. Avoid situations where Looper has already exited during Handler creation.
Send a message
The Handler sends messages using two series of methods: Postxx and sendxx. As follows:
public final boolean post(@NonNull Runnable r);
public final boolean postDelayed(@NonNull Runnable r, long delayMillis);
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis);
public final boolean postAtFrontOfQueue(@NonNull Runnable r);
public final boolean sendMessage(@NonNull Message msg); public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis); public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis); public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) Copy the code
I’ve listed only two of the more common types of methods. All methods end up calling sendMessageAtTime except for the two inserted in the queue header. Let’s analyze the post method with source code:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
Copy the code
The POST method wraps the Runnable object as a Message and calls the sendMessageDelayed method. Let’s see how it wraps:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
As you can see, the logic is simple: assign the Runnable object directly to the callBack property. Then go back to sendMessageDelayed:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} public boolean sendMessageAtTime(@NonNull 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
SendMessageDelayed changes the delay less than 0 to 0 and calls sendMessageAtTime. MessageQueue is initialized and enqueueMessage is called:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// Set target to itself
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// Async handler sets the flag bit true if (mAsynchronous) { msg.setAsynchronous(true); } // Finally call MessageQueue to join the queue return queue.enqueueMessage(msg, uptimeMillis); } Copy the code
As you can see, the Handler is also simple to enqueue, setting the Message target to itself so that the Message is processed by itself. Finally, the MessageQueue method is called to join the team, which has been described in the previous section.
Other methods of sending messages are more or less the same, readers interested in their own to track the source code.
Process the message
When Looper processes a message, the last thing to do is call handler’s dispatchMessage method. Let’s look at this method:
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } Copy the code
His logic is not complicated. This is the Runnable object passed in by the POST methods of the handler. Otherwise, the Handler checks whether it has a callBack, executes its method if it does, terminates if it returns true, and goes directly to the Handler’s own handleMessage method if it returns false. This process can be illustrated in the following diagram:
Memory leak problem
When using handlers using inherited Handler methods, be careful to use static inner classes rather than non-static inner classes. Since a non-static inner class holds a reference to an external class, we know from the above analysis that after Message is enqueued, its target attribute points to a Handler. If this Message is a delayed Message, the object referenced in this chain will not be released, resulting in a memory leak.
This is typically the case when we send a delayed message in an Activity and then exit the Activity, but because it cannot be released, the Activity cannot be reclaimed, causing a memory leak.
Handler to summarize
Handler, as the message processing and sender, is the starting point and end point of the entire message mechanism. It is also the class we touch most, because we call this message mechanism Handler mechanism. Handler the most important thing is to send and process messages, as long as you master these two aspects of content. Also be aware of memory leaks and do not use non-static inner classes to inherit handlers.
HandlerThread
An overview of the
Sometimes we need to open up a thread to perform time-consuming tasks. You can create a Thread and initialize the Thread’s Looper in its run method so that the Thread can be used to process messages. Here is the Kotlin code, which is almost as readable as Java:
val thread = object : Thread(){
lateinit var mHandler: Handler
override fun run(a) {
super.run()
Looper.prepare()
mHandler = Handler(Looper.myLooper()!!) Looper.loop() } } thread.start() thread.mHandler.sendMessage(Message.obtain()) Copy the code
But run it and fry it:
Handler has not been initialized. Threads take some time to start, and if a Handler is fetched before the Thread’s run method is called, the problem is that the Handler is not initialized. That’s easy, just wait a moment, on the code:
val thread = object : Thread(){
lateinit var mHandler: Handler
override fun run(a) {
super.run()
Looper.prepare()
mHandler = Handler(Looper.myLooper()!!) Looper.loop() } } thread.start() Thread(){ Thread.sleep(10000) thread.mHandler.sendMessage(Message.obtain()) }.start() Copy the code
Execute, ah, no error sure enough. But!! This code is particularly awkward and bloated, and requires another thread to be opened to delay processing. Is there a better solution? Yes, HandlerThread.
HandlerThread is a Thread that inherits from a Thread. Its code is not complicated. Take a look:
public class HandlerThread extends Thread {
// Thread priority, thread ID, thread looper, and internal handler
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler; // Two constructors. Name is the thread name and priority is the thread priority public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } // The method before Looper starts running protected void onLooperPrepared() { } // Initialize Looper @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); // Notification that initialization is complete notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } // Get the current thread's Looper public Looper getLooper() { if(! isAlive()) { return null; } // If the initialization is not done it will block until the initialization is complete synchronized (this) { while (isAlive() && mLooper == null) { try { // Use the wait method for Object objects wait(); } catch (InterruptedException e) { } } } return mLooper; } // Get handler. This method is marked hide and cannot be obtained by the user @NonNull public Handler getThreadHandler() { if (mHandler == null) { mHandler = new Handler(getLooper()); } return mHandler; } // There are two different types of exits public boolean quit() { Looper looper = getLooper(); if(looper ! =null) { looper.quit(); return true; } return false; } public boolean quitSafely() { Looper looper = getLooper(); if(looper ! =null) { looper.quitSafely(); return true; } return false; } // Get the thread ID public int getThreadId() { return mTid; } } Copy the code
The entire class doesn’t have much code, focusing on the Run () and getLooper() methods. First look at the getLooper method:
public Looper getLooper(a) {
if(! isAlive()) { return null;
}
// If the initialization is not done it will block until the initialization is complete
synchronized (this) { while (isAlive() && mLooper == null) { try { // Use the wait method for Object objects wait(); } catch (InterruptedException e) { } } } return mLooper; } Copy the code
Instead of writing it ourselves, it has a wait(), which is a method provided by Java’s Object class, similar to the MessageQueue block we mentioned earlier. Once the Looper is initialized, it will wake it up, and it will return without the Looper being initialized. Then look at the run method:
// Initialize Looper
@Override
public void run(a) {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) { mLooper = Looper.myLooper(); // Notification that initialization is complete notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } Copy the code
The regular Looper initialization is done and the notifyAll() method is called to wake up, corresponding to the getLooper method above.
The use of the HandlerThread
HandlerThread has a limited scope, with child threads constantly accepting time-consuming tasks for message processing. So his method of use is relatively fixed:
HandlerThread ht = new HandlerThread("handler");
ht.start();
Handler handler = new Hander(ht.getLooper());
handler.sendMessage(msg);
Copy the code
Get his Looper and use it with an external custom Handler.
The last
Handler, MessageQueue and Looper constitute the Android messaging mechanism, each of which plays its own role. Handler is mainly responsible for sending and processing messages, MessageQueue is mainly responsible for sorting messages and blocking codes when there is no message to be processed, Looper is responsible for taking messages from MessageQueue to Handler for processing, and achieve the purpose of switching threads at the same time. Through source code analysis, I hope readers can have a clearer understanding of these concepts.
Hope this article is helpful.
“
Full text here, the original is not easy, feel help can like collection comments forward. I have no talent, any ideas welcome to comment area exchange correction. If need to reprint please private letter or comment area exchange. And welcome to my blog: Portal
“