Analyze Handler from a source code perspective. This helps you use Handler and analyze problems related to Handler. Understand the relationship between Looper and Handler.

Introduction of Handler

A Handler allows messages to be sent and processed, executing Runnable objects through the MessageQueue of the associated thread. Each Handler instance is bound to a separate thread and its message queue. You can switch a task to the same thread as the Handler. One use is for a child thread to update the UI through a Handler.

Handler can be used in two main ways:

  • Plan to execute messages and Runnable at some point in the future
  • Thread scheduling, planning and executing tasks in other threads

To use Handler well, you need to know the associated MessageQueue, Message, and Looper. You can’t look at handlers in isolation. A Handler acts like an operator (or like a window open to developers), utilizing MessageQueue and Looper for task scheduling and processing.

Handler holds an instance of Looper and directly holds the message queue of Looper.

Properties and constructors

The Handler class holds instances of looper, messageQueue, and so on.

final Looper mLooper; // Handler holds Looper instance final MessageQueue mQueue; // Handler holds the message queue final Callback mCallback;Copy the code

In the Handler constructor, we can see that the Handler gets Looper’s message queue.

Public Handler(Callback Callback, Boolean async) {// rustfisher handles exceptions mLooper = looper.mylooper (); // Rustfisher handles special cases... mQueue = mLooper.mQueue; } public Handler(Looper Looper, Callback Callback, Boolean async) {mLooper = Looper; mQueue = looper.mQueue; // Get Looper message queue mCallback = callback; mAsynchronous = async; }` </pre>Copy the code

Handler Usage

Handler Several methods used to send and process messages

  • Void handleMessage(Message MSG): Method for processing messages, which is usually overridden.
  • Final Boolean hasMessage(int WHAT): Checks whether the message queue contains messages with the what attribute as the specified value
  • Final Boolean hasMessage(int WHAT,Object Object) : Checks whether the message queue contains a message with the value specified by the WHAT good Object attribute
  • SendEmptyMessage (int what): sends an empty message
  • Final Boolean Send EmptyMessageDelayed(int what,long delayMillis): Specifies how many milliseconds to send an empty message
  • Final Boolean sendMessage(Message MSG): Sends a Message immediately
  • Final Boolean sendMessageDelayed(Message MSG, Long delayMillis): How many seconds to send the Message

Handler.sendEmptyMessage(int what) Process parsing

Get a Message instance and immediately add the Message instance to the Message queue. The brief process is as follows:

SendEmptyMessage (int what) sendEmptyMessage(int what) sendEmptyMessage(int what SendEmptyMessageDelayed (what, 0) // Calculate the planned execution time of the message, SendMessageDelayed (Message MSG, SendMessageAtTime (Message MSG, EnqueueMessage (MessageQueue queue, Message MSG, Java Boolean enqueueMessage(Message MSG, long when) // MessageQueueCopy the code

As you can see, the message is finally added to the messageQueue.

Handler Cancels the task

To cancel tasks, call the following method removeCallbacksAndMessages (Object token)

public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}
Copy the code

Discard the Message associated with this Handler by calling the message.recycleunchecked () method.

The associated message queue performs a cancellation

void removeCallbacksAndMessages(Handler h, Object object)
Copy the code

Message drivers and handlers

Android is message-driven, and there are several elements to implementing message-driven

  • Representation of a Message: Message
  • MessageQueue: MessageQueue
  • Message loop, used to loop out messages for processing: Looper
  • Message processing, which is what the message loop processes after it retrieves a message from the message queue: Handler

Initialize the message queue

A MessageQueue is created in the Looper constructor, and Looper holds the instance of the MessageQueue.

Send a message

After initializing the good news queue with looper. prepare, we can call looper. loop to enter the message loop. We can then send a message to the message queue, and the message loop will retrieve the message for processing.

Message loop

Messages of Java layer are stored in mMessages, a member of Java layer MessageQueue, and messages of Native layer are stored in mMessageEnvelopes of Native Looper, which can be said to have two message queues. And they’re all arranged chronologically.

The Message and MessageQueue

Looper and MessageQueue are components that work with Handler:

  • Handler: This sends messages to MessageQueue managed by Looper and is responsible for processing messages assigned to it by Looper
  • MessageQueue: Manages messages, managed by Looper
  • Looper: Each thread has only one Looper. For example, in UI thread, the system will initialize a Looper object by default, which is responsible for managing MessageQueue, continuously retrieving messages from MessageQueue, and distributing corresponding messages to Handler.

Message

Message belongs to the role that is delivered and used. Message is a “Message” containing a description and any data object that can be sent to Handler. Message contains two int attributes and one additional object. Although the constructor is public, the best way to get an instance is to call message.obtain () or handler.obtainMessage (). This allows the message instance to be retrieved from their pool of recyclable objects. Typically, each Message instance holds one Handler.

Message partial attribute value

/*package*/ Handler target; // Specify Handler /*package*/ Runnable callback; // Sometimes we store linked lists of these things /*package*/ Message next;Copy the code

It’s also clear from here that each Message holds Handler instances. If the Handler holds a reference to the Activity, the Message is still in the queue after the Activity onDestroy. Because of the Handler’s strong association with the Activity, the Activity cannot be collected by the GC, resulting in a memory leak. Therefore, when the Activity onDestroy occurs, the Handler associated with the Activity should clear its queue of tasks created by the Activity to avoid memory leaks.

A method to reset itself, resetting all properties

public void recycle()
void recycleUnchecked()
Copy the code

The common method of getting an instance of Message that is bound to the Handler passed in

/**
 * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
 * @param h  Handler to assign to the returned Message object's <em>target</em> member.
 * @return A Message object from the global pool.
 */
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;

    return m;
}
Copy the code

Sends the message to Handler

/** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field  has not been set. */ public void sendToTarget() { target.sendMessage(this); // Target is the Handler bound to the message}Copy the code

After calling this method, the Handler adds the message to its MessageQueue, MessageQueue.

MessageQueue

Holds a list of messages that can be distributed by Looper. Typically, a Message is added to a MessageQueue by a Handler. The MessageQueue method to get the current thread is looper.myQueue (). Get the main thread Looper by looper.getMainLooper ().

Introduction of stars

Looper is closely associated with MessageQueue. A message loop that runs in a thread. Threads by default do not have a message loop to manage with them. To create a message loop, call Prepare in the thread, then call loop. That is, the message is processed until the loop stops. In most cases, message loops are interacted with by handlers.

A typical example of Handler and Looper interacting in a thread

class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // Create a Handler instance for the current thread. The Handler will fetch the current thread's Looper. // If the current thread has no Looper when instantiating the Handler, RuntimeException mHandler = new Handler() {public void handleMessage(Message MSG) {// Process incoming messages here } }; Looper.loop(); // Looper starts running}}Copy the code

Looper is run after looper.loop () is called. When there is no message in Looper’s messageQueue, what is the state of the relevant thread? Check looper’s source code to see that the loop method has an infinite loop. The queue.next() method can block threads. If null is obtained from the queue, it indicates that the message queue is exiting. The looper loop is also returned.

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
Copy the code

Calling Looper’s quit method actually calls mqueue.quit (false). When the message queue exits, looper’s loop is also exited.

Enter MessageQueue’s next method and find that there is also an infinite loop. This loop blocks the nativePollOnce method when there is no message.

Message next() { // ... for (;;) {/ /... nativePollOnce(ptr, nextPollTimeoutMillis); // Process the message objectCopy the code

We know that the Thread is New, RUNNABLE, BLOCKED, WAITING. When a Thread has a lock, it calls its wait method and waits for another Thread/lock owner to call notify/notifyAll. TIMED_WAITING, TERMINATED states.

There are no messages in the message queue and “wait” in the nativePollOnce method. NativePollOnce is roughly equivalent to Object.wait() in effect, but their implementation is completely different. NativePollOnce uses epoll, while Object.wait uses futex.

When “WAITING”, the related thread is in the WAITING state.

Properties in Looper

Looper owns MessageQueue; Only main thread Looper sMainLooper; Looper The current thread mThread; Store the sThreadLocal of Looper

// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; // The Handler gets the message queue instance (see Handler constructor). // Looper the current threadCopy the code

ThreadLocal is not a thread; it stores data on each thread.

Which method

Prepare method that initializes the current thread as Looper. Call quit on exit

public static void prepare() { prepare(true); } 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)); // the Looper instance is stored in sThreadLocal}Copy the code

Sthreadlocal. set(new Looper(quitAllowed))

ThreadLocal < T > class

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) return (T)e.value; } return setInitialValue(); }Copy the code

When retrieving a Looper object, get it from sThreadLocal

Public static @nullable Looper myLooper() {return sthreadLocal.get (); }Copy the code

Run a message queue in the current thread. Call the quit() method when finished

public static void loop()
Copy the code

Prepare the main thread Looper. The Android environment creates the main thread Looper, and developers should not call this method themselves. UI thread, which is an ActivityThread, initializes Looper when an ActivityThread is created, which is why you can use handlers in the main thread by default.

public static void prepareMainLooper() { prepare(false); Synchronized (looper.class) {if (sMainLooper! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

Gets the Looper of the main thread. We developers can call this method when we want to manipulate the main thread

public static Looper getMainLooper()
Copy the code

Different handlers for the same Thread

MainLooper, corresponding to the UI thread, can be associated with multiple handlers. Scheduled tasks between multiple handlers do not affect each other. Let’s say we have two handlers associated with the UI thread.

Handler mMainHandler1; Handler mMainHandler2; private void initUtils() { mMainHandler1 = new Handler(Looper.getMainLooper()); mMainHandler2 = new Handler(Looper.getMainLooper()); Log.d(TAG, "mMainHandler1 Post task "); MMainHandler1. PostDelayed (new Runnable () {@ Override public void the run () {the d (TAG, "mMainHandler1 presentation task executed rustfisher");  }}, 1500); mMainHandler2.removeCallbacksAndMessages(null); }Copy the code

MMainHandler2 cancelling its task does not affect mMainHandler1.

Handler related interview questions

1. Must child threads not update the UI?

A: Not necessarily.

  • Activity has an audit mechanism that works after the Activity is fully displayed, if the child thread is fully displayed

It was possible to update the UI before;

  • SurfaceView: Multimedia video playback, also can update UI in child threads
  • Progress: a progress-related control that can also update the UI in child threads

2. Tell me how Handler works

3. How do you solve the memory leakage caused by Handler?

4. How do I use Handler to make child threads communicate with child threads?

  • The child thread that sends the message
package com.cdc.handler; import android.os.Handler; import android.os.Message; import android.os.SystemClock; Public class Thread1 extends Thread {private Handler Handler; public Thread1(Handler handler){ super.setName("Thread1"); this.handler=handler; } @Override public void run() { Message msg = Message.obtain(); msg.what = 1; msg.obj = System.currentTimeMillis()+""; handler.sendMessage(msg); System.out.println((thread.currentThread ().getName()) + "---- sends cancellation!" + msg.obj)); SystemClock.sleep(1000); }}Copy the code
  • The child thread that receives the message
package com.cdc.handler; import android.os.Handler; import android.os.Looper; Public class Thread2 extends Thread{private Handler handler2; Public Handler getHandler(){// Note that null return handler2 is returned before run; } public Thread2(){ super.setName("Thread2"); } @override public void run() {// To create a Handler instance in a child thread, call looper.prepare (); Can't create handler inside thread that has not called looper.prepare () looper.prepare (); Handler2 = new Handler(){public void handleMessage(Android.os.message MSG){system.out.println ((" received Message: " + Thread.currentThread().getName() + "----" + msg.obj)); }; }; Looper.loop(); }}Copy the code
  • call
private Handler myHandler=null;
private Thread2 thread1;
private Thread1 thread2;
@OnClick(R.id.handler3)
public void handler3(){
    thread1=new Thread2();
    thread1.start();
    myHandler=thread1.getHandler();
    while(myHandler==null){
        SystemClock.sleep(100);
        myHandler=thread1.getHandler();
    }
    thread2=new Thread1(myHandler);
    thread2.start();
}
Copy the code

5. What is HandlerThread & principle & usage scenario?

6. What is IdleHandler?

7. Can a thread create more than one Handler?

8 Why does the Android System not recommend child threads to access the UI? First of all, UI controls are not thread-safe. If multiple threads access UI controls concurrently, unexpected state can occur. So why doesn’t the system lock UI controls? There are two disadvantages:

  • Adding locks complicates UI access logic;
  • Locking reduces the efficiency of UI access because it blocks the execution of certain threads

Given these two disadvantages, the simplest and most efficient way to handle UI operations is to use a single-threaded model, so the source code ViewRootImpl will have a thread judgment, code as follows: frameworks/base/core/java/android/view/ViewRootImpl.java

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can
touch its views.");
     }
 }
Copy the code

It’s not too much of a hassle for developers, just switching the thread of execution that the UI accesses via handler

9. Why does Looper loop death not cause apps to freeze?

10. What happens to message queues after using postDealy with Handler?

Android Zero Basics tutorial video reference