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:

  1. 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
  2. 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!