Handler is often used in our daily development. It is mainly used to process asynchronous messages. When a message is sent, it first enters a message queue, and the function that sends the message can return.
Although it is often used, using it in the wrong way can cause some problems. Let’s look at some of the Handler related issues and resolve them
Handler causes memory leakage
Andorid typically executes time-consuming business logic in child threads, and then sends a message from the Handler to the main thread to update the UI.
When we create a Handler using an inner class or an anonymous inner class, it implicitly holds a reference to an external object, typically an Activity. If we shut down the Activity while it’s still running, the thread holds a reference to the Handler, The handler holds a reference to the Activity, causing the Activity to be unable to be reclaimed and thus causing a memory leak.
Also, if we had used the handler.postdelayed () method, which would have encapsulated the handler as a Message object and placed the Message object on the MessageQueue queue, there would have been a chain of references held until the delay was reached: MessageQueue – > Message – > Hanlder – > Activity. As a result, the Activity cannot be reclaimed, causing a memory leak
Solutions:
- Stop the thread in the Activity’s onDestroy method and empty the Handler directly. Check whether the Hanlder is empty before executing the following logic
- By setting Hanlder as a static object, Java static classes do not hold references to external classes, and the Activity can be reclaimed. The Handler can’t update the UI because it doesn’t hold a reference to the Activity. You need to pass the Activity into the Handler, which uses weak references to store the Activity to ensure that it can be recycled.
Why can’t you just create an empty constructor Handler in a child thread
It is an error to directly new a Handlernew Handler() in a child thread
java.lang.RuntimeException: Can't create handler inside thread[Thread..] that has not called Looper.prepare()Copy the code
Why is that? We can follow up the code and take a look
public Handler(a) {
this(null.false);
}
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
MyLooper () this method returns null. Why is it null
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
So you can see that it’s going to fetch this looper from a ThreadLocal, ThreadLocal is like a Map, the key is the current thread, the value is looper, New a Handler directly from the child thread. If the thread has no object looper in the ThreadLocal, an error is reported
The main thread already has a looper object in it. We know that the Activity’s main function is in the ActivityThread class. We see this statement in the ActivityThread main method
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(); }}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));
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
As you can see, the prepare method places Looper in ThreadLocal, where the key is the current thread
We can pass a Looper to Hanlder to prevent errors such as new Handler(looper.getMainLooper ()); Or call looper.prepare () before the new Handler;
Does textView.settext () execute only in the main thread?
Textview.settext () is a function of a new Thread called in the run method of a Thread, and it succeeds without error.
When our setText refreshes the layout, it executes the checkForRelayout() method, which finally executes the requestLayout() and invalidate() methods to request a new layout and redraw, If you follow these two methods, you’ll end up in requestLayout() of the ViewRootImpl class, which contains a checkThread() method.
private void checkForRelayout(a) {... requestLayout(); invalidate(); }/ / the View of requestLayout ()
public void requestLayout(a) {...//mParent其实就是ViewRootImpl
if(mParent ! =null&&! mParent.isLayoutRequested()) { mParent.requestLayout(); }... }/ / the ViewRootImpl requestLayout ()
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}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
CheckThread () checks whether the current thread is the main thread, and raises this exception if it is not.
Why does setText() not throw this exception? MThread is assigned in the constructor of ViewRootImpl, which is created after the Activity object has been created. RequestLayout () checks to see if ViewRootImpl is null before it is called.
If the setText() method is fast enough to refresh the ViewRootImpl before it is created, no errors will be reported.
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {... ViewRootImpl impl = decor.getViewRootImpl();if(impl ! =null) { impl.notifyChildRebuilt(); }... }Copy the code
So if we execute our setText faster than we create ViewRootImpl, we won’t execute the thread check method. It will draw successfully
What’s the difference between the two ways of writing new Handler()?
Two kinds of writing
Handler mHandler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false; }});Copy the code
Handler mHandler2 = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); }};Copy the code
If you write these two methods in AndroidStudio, you will see a yellow warning for the second method, so I recommend the first method. In the dispatchMessage method of the Hanlder
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
MSG. Callback is a Runnable, and the handleCallback(MSG) method is the run method that executes it. If the mCallback is not null, its handleMessage method is executed. This mCallback is the Callback passed by the first method. HandleMessage’s own methods are executed only when neither of the previous cases is true.
The second method is equivalent to creating a subclass of Handler and implementing the handleMessage method of the parent class. The first method is equivalent to creating a Handler object and passing in a callback.
The principle of ThreadLocal
public class ThreadLocal<T> {... }Copy the code
ThreadLocal is a local line utility class that maps a private thread to its replica object. Variables between threads do not interfere with each other. In high concurrency scenarios, stateless calls can be implemented
A ThreadLocal is a Map whose key is the current thread, whose value is T, and we can specify any type to hold it.
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
Copy the code
Get () : ThreadLocalMap<ThreadLocal, Object> from ThreadLocalMap; If not, call the setInitialValue() method.
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
The initialValue method returns null by default. You can subclass the object that you want to save.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
The set method also takes ThreadLocalMap<ThreadLocal, Object> from the current thread and stores the value into ThreadLocalMap. Create one if ThreadLocalMap is empty
Handler source code analysis
Handler Looper Message MessageQueue Handler Looper Message MessageQueue
(1) Create main thread Looper
Looper.prepareMainLooper();
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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(); }}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
PrepareMainLooper, a static Looer method, is called from the Main method of the ActivityThread class, creating a Looper and storing it in a ThreadLocal. You’ve already seen that each thread has its own ThreadLocal that holds its own private variables, in which case the ActivityThread is the main thread.
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
When Looper is created, a private message queue is created
(2) Create Handler
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); }};Copy the code
Now that we’re pretty familiar with this step, handling messages in handleMessage, let’s look at the Handler constructor
public Handler(a) {
this(null.false);
}
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;
}
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
Take the current thread’s Looper object, assign the message queue in Looper to its member variable mQueue, and copy the callback object mCallback if passed in.
(3) Send messages
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
EnqueueMessage indicates that messages are queued, and mQueue is the queue retrieved from Looper
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
This assigns itself, the Hanlder, to the target in the Message, and then calls the method that processes the Message through a reference to this Handler. The enqueue method of the MessageQueue MessageQueue is then called
boolean enqueueMessage(Message msg, long 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 {
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.nextprev.next = msg; }... }Copy the code
As you can see, Message is a list structure, where the Message is placed in the next of the list.
(4) Consumption news
Looper.prepareMainLooper();
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();
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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if(traceTag ! =0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0)?0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0)?0 : SystemClock.uptimeMillis();
} finally {
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg="+ msg.what); }}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
In the above code, get the current thread’s Looper, then get the Message queue in the Looper, and then start an infinite loop by saying Message MSG = queue.next(); Constantly take out the message and then call MSG. Target. DispatchMessage (MSG); Method to process messages.
MSG. Target is the Handler object. So we’re calling the dispatchMessage method in this Handler
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
There are three ways to handle messages, the second and third of which correspond to the two ways to create handlers at the beginning of this article. The first MSG. Callback is a Runnable object
handler.post(new Runnable() {
@Override
public void run(a) {}})public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
When we call handler.post(), we pass in a Runnable object, which is the Runnable we passed in.
OK, the process analysis of Handler is complete.
Handwritten Handler exercises
Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler
According to the previous analysis, we can know here involves a few classes, ActivityThread, Message, MessageQueue, Handler, stars.
The main method of the ActivityThread class completes after the Activity starts
So we create an ActivityThread directly under the project’s Test folder to simulate the entry to the Activity.
public class ActivityThread {
@Test
public void main(a){
/ / to the stars
Looper.prepareMainLooper();
/ / create a Handler
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// Process the messageSystem.out.println(msg.obj.toString()); }};// Start a thread to send messages from child threads
new Thread(){
@Override
public void run(a) {
super.run();
// Send a slave message
Message msg = new Message();
msg.obj = "Hello Handler";
handler.sendMessage(msg);
}
}.start();
// Start the loopLooper.loop(); }}Copy the code
It is easy to follow the four steps described above: (1) prepare the Looper, (2) create a Handler to override the handleMessage method to process the message, (3) send the message, and (4) start a loop to process the message.
The Message class:
public class Message {
public int what;
public Handler target;
/**
* 消息对象
*/
public Object obj;
@Override
public String toString(a) {
returnobj.toString(); }}Copy the code
MessageQueue class, simulated using a blocking queue, ArrayBlockingQueue.
public class MessageQueue {
private ArrayBlockingQueue<Message> mMessages = new ArrayBlockingQueue<Message>(50);
// The message enters the queue
public void enqueueMessage(Message msg) {
try {
mMessages.put(msg);
} catch(InterruptedException e) { e.printStackTrace(); }}/ / take a message
public Message next(a) {
try {
return mMessages.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null; }}Copy the code
Which class
public class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
MessageQueue mQueue;
public Looper(a) {
mQueue = new MessageQueue();
}
public static void prepareMainLooper(a) {
prepare();
}
private static void prepare(a) {
if(sThreadLocal.get() ! =null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static Looper myLooper(a) {
return sThreadLocal.get();
}
// Start the loop
public static void loop(a) {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true){
Message msg = queue.next();
if(msg! =null) {if(msg.target! =null){
msg.target.dispatchMessage(msg);
}
}
}
}
}
Copy the code
OK, run the code directly in the ActivityThread test class and you can see Hello Handler successfully printed in the log!