preface

Before writing this article, I wish you all a happy Mid-Autumn Festival, in this three-day holiday, I wish you all have fun! Well, then dog brother will analyze the Handler mechanism for everyone, which is also a compensation for my idle three days off. In fact, the Handler mechanism principle has been written by many big men, here we why also say? Because for this kind of vegetable dog like me, also just can use Handler, for its mechanism, also just about know, today we will combine source code in-depth analysis of its principle.

Handler is introduced

Most Android web requests are made in the child thread, while UI updates are made in the main thread. Since Android 4.0, network requests in the main thread and UI updates in child threads have been prohibited. Therefore, the Handler mechanism has been created as a communication mechanism between threads, but it is often used to update the UI.

Principle of mechanism

The handler is responsible for sending messages and processing messages (sender and handler). The handler is responsible for storing messages in a MessageQueue Message queue. The handler is responsible for storing messages in a Looper pump. A message is removed from a message queue and sent to the Handler that holds the message. The Handler sends a Message to the MessageQueue, in which case the Handler acts as the sender object and then, through the Message pump Looper, keeps pulling messages out of the Message queue, and then processing the messages through the Handler, At this point handelr acts as a handler figure.Copy the code

Source code analysis

Let’s first look at the initialization of the Handler class

public class Handler { final MessageQueue mQueue; // MessageQueue final Looper mLooper; // Associated looper final Callback mCallback; publicHandler() {// Default looper associated with the current thread mLooper = looper.mylooper (); // Looper cannot be null, meaning that the default constructor can only be used in the looper threadif(mLooper == null) {throw new RuntimeException(// Cannot create a handler in a thread that did not call loop.prepare ()."Can't create handler inside thread that has not called Looper.prepare()"); MQueue = mlooper.mqueue; mQueue = mlooper.mqueue; mCallback = null; }}Copy the code

We found that the handler associated messageQueue with its Looper object during initialization.

We normally create handlers like this. When the handler is created, a Looper will be associated. The default constructor will associate the looper of the current thread. So these are the two ways that we often create handler objects.

    Handler handler = new Handler(Looper.getMainLooper());
    Handler handler = new Handler();
Copy the code

Handler associates looper object with Messagequeue object to complete the scheduling work, and in the whole scheduling work, Looper object is the real core, which is equivalent to the leader of the whole team. Below we carefully and seriously analyze looper source code.

Looper.getmainlooper (), we see that the handler entry to the current thread Looper is actually done in the main() method of the ActivityThread. And in the main() method it does one other important thing.

Public static void main(String[] args) {// The main thread creates a Looper object. Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq);
        if(sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // Execute the message loop looper.loop (); }Copy the code

That’s right: execute the message loop looper.loop (); . To summarize two things that main() does:

1, stars. PrepareMainLooper (); // The main thread creates a Looper object. 2, stars. Loop (); // Execute message loopCopy the code

Let’s break it down one by one:

1, stars. PrepareMainLooper ();

public static void prepareMainLooper() {// The main thread initializes a Looper object by default, so we don't need to prepare() ourselves. prepare(false); Synchronized (looper.class) {prepareMainLooper() {prepareMainLooper(); synchronized (looper.class) {prepareMainLooper()if(sMainLooper ! = null) {// Look at the exception thrown, initialization looper is ready throw new IllegalStateException("The main Looper has already been prepared."); } // set the global variable, the main thread looper sMainLooper = myLooper(); }}Copy the code

Create a looper on the main thread, the main looper of the main thread, which is created when the app is initialized, so this function is not for you to call, but for the system itself to call when the program is created.

Moving on, there’s a prepare(Boolean) function, so let’s see what it does.

Private static void prepare(Boolean quitAllowed) {// Check whether the current thread already has a Looper. If so, no new Looper object is allowed. Only one Looper can exist on a threadif(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // In the current thread, create a new Looper object and bind the current thread sthreadLocal.set (new Looper(quitAllowed)); }Copy the code

Let’s see what sThreadLocal does in Looper.

//sThreadLocal is used as a global variable in Looper Public static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();Copy the code

In Looper, sThreadLocal is used as a global variable. SThreadLocal is actually a container that stores Looper. We continue to analyze the get and set of ThreadLocal.

public T getThread t = thread.currentThread (); 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();
}
 
public void setThread T = thread.currentThread (); Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t);if(map ! = null) map.set(this, value);else
        createMap(t, value);
}
Copy the code

Key code:

    Thread t=Thread.currentThread();
Copy the code

That is, our Looper objects are stored separately in the corresponding thread. Let’s go back to the prepare(Boolean) function:

Private static void prepare(Boolean quitAllowed) {// Check whether the current thread already has a Looper. If so, no new Looper object is allowed. Only one Looper can exist on a threadif(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // In the current thread, create a new Looper object and bind the current thread sthreadLocal.set (new Looper(quitAllowed)); }Copy the code

Looper.prepare()

Looper.prepare(Boolean) creates a Looper object and binds it to the current thread. In the code, we first determine if the current thread already has a Looper, and if it doesn't, we create a new Looper and bind it to the current thread.Copy the code

Then we’ll look at the prepareMainLooper() code again:

public static void prepareMainLooper() {// The main thread initializes a Looper object by default, so we don't need to prepare() ourselves. prepare(false); Synchronized (looper.class) {prepareMainLooper() {prepareMainLooper(); synchronized (looper.class) {prepareMainLooper()if(sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } // set the global variable, the main thread looper sMainLooper = myLooper(); }}Copy the code

So what do we see sMainLooper is, and what is myLooper()?

// Save a main thread looper private static looper sMainLooper; // guarded by Looper.class public static LoopermyLooper() {// Use the current thread's looperreturn sThreadLocal.get();
}
Copy the code

Conclusion:

SMainLooper is used as a global variable to hold the main thread binding Looper, and myLooper() is used to retrieve the current thread binding Looper. In prepareMainLooper(), create a new Looper in the main thread, bind the main thread, and assign the main thread Looper to the global variable sMainLooer.Copy the code

The handler does two things in the main() method of the AcitvityThread. The first thing we analyzed is looper.prepareMainLooper ();

Let’s move on to the second thing he does: looper.loop ();

MessageQueue (MessageQueue); MessageQueue (MessageQueue); Looper.loop(); Get the entire message queue moving. Let’s examine the source code in the loop() method:

/** * This function is used to start the message queue loop, which is equivalent to a command issued by the leader of a team, * only the leader says that you have to work overtime today 😂 * Run the message queuein this thread. Be sure to call
 */
public static void loopLooper 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 that the thread's identity is that of the local process, and keep track of what the identity is actually. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); // Loop through message queue to get messagesfor(; ;) { Message msg = queue.next(); // might blockif(MSG == null) {// No message exits the message queue.return; } // This must be ina local variable in case the UI event sets final Printer Logging = me.mlogging;if(logging ! = null) { logging.println(">>>>> Dispatching to " + msg.target + "" +
                    msg.callback + ":" + msg.what);
        }
 
        final long traceTag = me.mTraceTag;
        if(traceTag ! = 0) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try {// the MSG. Target is the hanlder. Look back to the code hanlder. EnqueueMessage () MSG. Target. DispatchMessage (MSG); } finally {if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
 
        if(logging ! = null) { logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); // Discard message msg.recycleunchecked (); }}Copy the code

If there is a messagequeue stored in loOPER messagequeue, and then loop the messagequeue indefinitely to get the message, the following code is important:

// The key point is that msg.target is the hanlder. Look back to the code hanlder. EnqueueMessage () MSG. Target. DispatchMessage (MSG);Copy the code

Analysis:

MSG. Target is actually our handler, and either the handler via POST or sendEmptyMessage will eventually call enqueueMessage(), where the handler is assigned to MSG. Target.Copy the code

Ok, so let’s go back to the enqueueMessage() function

Private Boolean enqueueMessage(MessageQueue queue, Message MSG, long uptimeMillis) {MSG. Target = this;if (mAsynchronous) {
        msg.setAsynchronous(true); } // Put the message in the queue herereturn queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Now that the stars in the loop () call the MSG. Target. DispatchMessage, we’ll look at how the Handler dispatchMessage processed this MSG.

Note that the dispatchMessage() function is handled by the Handler as a Handler, which is naturally in the Handler class.

Let’s continue to analyze the source code one by one:

Public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG)if(msg.callback ! = null) { handleCallback(msg); }else{// If hanlder's input parameter callback is not empty, it is processed firstif(mCallback ! = null) {// If the callback returns true. The handler. HandleMessage method is interceptedif (mCallback.handleMessage(msg)) {
                return; }} // This is why we need to override the handleMessage method handleMessage(MSG) when using hanlder; }}Copy the code

Analysis:

If msg.callback is null, what is msG. callback? The runnable object in handler.post is a runnable object that handles post operations if there are any.Copy the code

We are looking at the handlerCallback function.

private static void handleCallback(Message message) {
    message.callback.run();
}
Copy the code

Very simple, one line of code, we see the familiar run method, this is the Runnbale object we passed in when we used post. Let’s simulate a piece of code:

/** * start the simulation */ private voiddoSometh() {// start a Thread to process complex transactions new Thread(new)Runnable() {
        @Override
        public void run() {// Simulate a very complex service, which needs 1000ms to operate...... handler.post(newRunnable() {
                @Override
                public void run() {// Here you can update UI mtv.settext ("At this point I updated :"+ System.currentTimeMillis()); }}); } }).start(); }Copy the code

What is the relationship between the thread created by Handler’s POST method and the UI thread? Let’s continue to analyze the source code:

public final boolean post(Runnable r)  {  
      return sendMessageDelayed(getPostMessage(r), 0);  
}  
Copy the code
private static Message getPostMessage(Runnable r) {  
      Message m = Message.obtain();  
      m.callback = r;  
      return m;  
}  
Copy the code

As you can see, in getPostMessage, we get a Message object, and then assign the Runable object we created as a callback attribute to the current Message. Return to handler.dispatchMessage(Message), callback is null if not via post, we see a mCallback variable, we look at the definition of this callback:

public interface Callback {
    public boolean handleMessage(Message msg);
}
public Handler(Callback callback) {
    this(callback, false);
}
Copy the code

We can process the message by implementing the interface and passing it to the Handler as a parameter. Let’s go back to the dispatchMessage() function to see what’s going on:

Public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG) {public void dispatchMessage(Message MSG)if(msg.callback ! = null) { handleCallback(msg); }else{// If hanlder's input parameter callback is not empty, it is processed firstif(mCallback ! = null) {// If the callback returns true. The handler. HandleMessage method is interceptedif (mCallback.handleMessage(msg)) {
                return; }} // This is why we need to override the handleMessage method handleMessage(MSG) when using hanlder; }}Copy the code

Analysis: In the last line of code, we see the familiar handleMessage. This is how we always use handler.handlerMessage.

But notice what we saw earlier, if we return true for McAllback.handlermessage (MSG), then handler.handleMessage is not handled. Here we look again at the handleMessage() function:

  public void handleMessage(Message msg) {  
  
  } 
Copy the code

Here we find that this method is null. Why? Here’s a sidebar. Because we control the final callback of the message, we create handlers by overwriting the handleMessage method and then processing the message according to msG.what. Generate a Message object, either new or using the message.obtain () method; Both are acceptable, but the obtain method is more recommended because a Message pool is maintained internally for Message reuse, avoiding the need to reallocate memory using New.

Let’s go back to our looper.loop () :

public static void loop() {... . // Loop through message queue to get messagesfor(; ;) {... // Discard message msg.recycleunchecked (); }}Copy the code

Beyond the handler. DispatchMessage call, the msg.recycleunchecked () call is used to recycle each message indefinitely.

Summary of mechanism principle

1. Create Handler in main thread without calling looper.prepare (). PrepareMain Looper() is created by calling looper.prepareMain Looper() in the entry of the program and starts the Looper for its main thread. If we create a handler in a child thread, we need to manually create the Looper and start it. 2. Each thread can only have one Looper. Looper has a global variable sThreadLocal to store the Looper of each thread. 3.Handler can send messages via POST or sendMessage, because sendMessageDelayed is eventually called. We can process messages through runnable mode or override handleMessage. Of course, if we use handler.sendMessage(MSG), we can implement the Callback interface to handle messages. 4. Why can't THE UI be updated on child threads? It's more accurate to say that the UI can only be updated in the thread that created the UI, the main thread, and if a child thread created the UI, it can be updated in the child thread.Copy the code

Handler Handler:

From: www.cnblogs.com/devinzhang/…

Method 1: Thread

new Thread( new Runnable() {     
    public void run() {     
         myView.invalidate();    
     }            
}).start();
Copy the code

Analysis: can realize functions, refresh the UI interface. But this is unreasonable because it violates the single-threaded model: Android UI operations are not thread-safe, and they must be performed in the UI thread.

Method 2: Thread + Handler

Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case 0:   
                         myView.invalidate();  
                         break; } super.handleMessage(msg); }};Copy the code
class MyThread implements Runnable {   
          public void run() {  
               while(! Thread.currentThread().isInterrupted()) { Message message = new Message(); message.what = 0; myHandler.sendMessage(message); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }}}}Copy the code

Handler to process UI updates based on incoming messages. Thread The Thread sends a Handler message to update the UI.

Method three: TimerTask + Handler to implement the Timer function

public class TestTimer extends Activity {  
  
    Timer timer = new Timer();  
    Handler handler = new Handler(){   
        public void handleMessage(Message msg) {  
            switch (msg.what) {      
            case 1:      
                setTitle("Dog is the handsomest.");  
                break; } super.handleMessage(msg); }}; TimerTask task = newTimerTask(){    
        public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); }}; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.main); timer.schedule(task, 10000); }}Copy the code

Runnable + handler. postDelayed(Runnable,time)

private Handler handler = new Handler();  
  
    private Runnable myRunnable= new Runnable() {    
        public void run() {  
             
            if (run) {  
                handler.postDelayed(this, 1000);  
                count++;  
            }  
            tvCounter.setText("Count: "+ count); }};Copy the code

The statement

Here the contents of my last year when the beginning of the CDSD to write some, here I have to make fun of, because CSDN community is really a variety of advertising too much, see an article to pay, can not stand, so turned to nuggets community, my CSDN original address: blog.csdn.net/MrZhao_Perf… , this article is also a summary and supplement. OK, the Handler mechanism principle through the source code to give you a general, if you have any questions or can leave a message to me.