The interviewer also asked the Handler? Then I'll tell you a story

Come on, little brother, what's the matter with Handler

There are too many blogs related to Handler, and there are a large number of random searches, but they basically post the source code and pose when they come up. It's not easy to understand the overall relationship and process in a short time

Interviewer, sit down and listen to my story?

This paper analyzes the example of ordering meals in life combined with the principle of source code. I hope it will help you a little. Come on, let's get into the role.

The whole process cooperation relationship of Handler, Looper, MessageQueue and Message is like the overall operation relationship of a restaurant

  • A Handler is like a orderer
  • Looper is like a back chef.
  • MessageQueue is like an order printer.
  • Message is like a table by table order.

Next, let's review the scene of ordering in our restaurant. Ordering in the restaurant is divided into standard ordering and special ordering. Let's break it down.

Standard process

    1. First, enter a store and submit the data to the back kitchen for stand-alone by ordering.
    1. Then the chef picked up the orders one by one and told the chef in the back kitchen to start making according to the order of ordering.
    1. Serve after making and mark the order.

Special process

    1. The order is a delayed order. For example, the customer requests that the order be made after 30 minutes. At this time, the order will be sorted according to time and placed in the appropriate position in the order queue through systemclock uptimeMillis() sets the alarm. The reason why uptimeMillis is used is that this time is the millisecond time calculated from the start of system startup and is not affected by manual regulation time.
    1. If there are all delayed orders in the single machine, order the kitchen chef to rest, stick a needWake sign at the door and wait for the alarm. If a new immediate order comes in and a needWake sign is found, wake up the chef through nativeWake() and start serving.
    1. However, in order to improve the coverage of store dishes, many adjacent stores choose cooperative operation, that is, you can mix and match the meals of the next store to our store. At this time, you only need the ordering clerk to submit the order of the next store, so that the chef of the next store can take out the order and make and serve dishes by playing a single machine.

summary

A shop can have more than one orderer, but the chef can only have one. There can only be one single player.

Map to the above scenario

  • A store is like a Thread
  • There can be multiple handlers in a Thread
  • However, a store can only have one Looper, one MessageQueue, and multiple messages.

Interviewer, I'm almost done. Do you believe it?

According to the above examples, let's look at the source code by analogy, and fully study the process and implementation principle of the whole mechanism.

Looper workflow

ActivityThread.main();//Initialization entry
    1. Looper.prepareMainLooper(); //initialization
          Looper.prepare(false); //Setting cannot be closed
              Looper.sThreadLocal.set(new Looper(quitAllowed)); //Bind to thread
                    1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper and MessageQueue binding
                    1.2.Looper.mThread = Thread.currentThread();
    2. Looper.loop();
        2.1.myLooper().mQueue.next(); //Loop to get messages in MessageQueue
              nativePollOnce(); //Blocking queue
                  native -> pollInner() //Underlying blocking implementation
                        native -> epoll_wait();
        2.2.Handler.dispatchMessage(msg);//Message distribution

myLooper(). mQueue. Implementation principle of next()

    1. Through myloop() mQueue. The next () loop gets the messages in the MessageQueue. If the synchronization barrier is encountered, the asynchronous messages will be processed first
    1. The synchronization barrier is message The message sent by postsyncbarrier() has no Handler bound to its target. In Hnandler, asynchronous messages take precedence over synchronous messages.
    1. You can send asynchronous messages by creating a new Handler (true). The ViewRootImpl.scheduleTraversals method uses a synchronization barrier to ensure that UI drawing takes precedence.

Handler. Implementation principle of dispatchmessage (MSG)

    1. Priority callback MSG callback.
    1. Second, callback in the handler constructor.
    1. Finally, callback handler handleMessage().

Process of sending message by handler

1.Handler handler = new Handler();//Initialize Handler
        1.Handler.mLooper = Looper.myLooper();//Gets the current thread Looper.
        2.Handler.mQueue = mLooper.mQueue;//Gets the MessageQueue object of the Looper binding.

2.handler.post(Runnable);//send message
        sendMessageDelayed(Message msg, long delayMillis);
            sendMessageAtTime(Message msg, long uptimeMillis);
                Handler.enqueueMessage();//Message.target is assigned this.
                    Handler.mQueue.enqueueMessage();//Add a message to the MessageQueue.

MessageQueue. Implementation principle of enqueuemessage() method

    1. If the message queue is discarded, an exception is thrown.
    1. If the current inserted message is an instant message, take the message as a new header element, point the next of the message to the old header element, and wake up the Looper thread through needWake.
    1. If the message is asynchronous, use message When is inserted into the corresponding position of the queue and does not wake up the Looper thread.

    Then it's time for the interviewer to ask

It is often asked why Looper blocking of the main thread does not lead to ANR?

    1. First, we need to know that the ANR is the main thread that does not respond within 5 seconds.
    1. What is 5 seconds without response? All operations in the Android system add events to the event queue through the Handler, and the Looper loops to the queue to get events for execution. If the main thread event feedback exceeds 5 seconds, ANR will be prompted.
    1. If no event comes in, the queue in the loop method will be blocked based on the Linux pipe/epoll mechanism nativePollOnce() in next() will not report ANR.
    1. For the above example, Anr can be understood as that the user fails to serve on time after instant ordering (of course, there are many reasons for not serving on time, which may be slow (time-consuming operation, IO, etc.), occupied kitchen utensils (deadlock), and there may not be enough chefs (poor CPU performance, etc...), the customer has initiated a complaint or bad comment. However, if the agreed time has not arrived or no one orders at present, there will be no bad comment or complaint, so there will be no ANR.

All the above contents focus on the principle and source code. Next, let's give some examples of special scenarios

  • 1. Why can't a child thread directly new Handler()?
       new Thread(new Runnable() {
           @Override
           public void run() {
              Handler handler = new Handler();
           }
       }).start();


       The above code will report the following error

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
       at android.os.Handler.<init>(Handler.java:207)
       at android.os.Handler.<init>(Handler.java:119)
       at com.example.test.MainActivity$2.run(MainActivity.java:21)
       at java.lang.Thread.run(Thread.java:919)
  • Through the error message "not called Looper.prepare()", you can see that the prompt does not call looper Prepare (), as for why we have to look at the source code
 public Handler(Callback callback, boolean async) {
        ...Omit several codes

       //Through looper Myloop() gets the mloop object. If mloop = = null, throw an exception
        mLooper = Looper.myLooper();
        if (mLooper == null) {
             //It can be seen that the abnormality is reported from here
            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() {
        //myLooper() is through sThreadLocal Get(), what the hell is sThreadLocal?
        return sThreadLocal.get();
    }

 //You can see that sThreadLocal is a ThreadLocal < looper > object. Where is the ThreadLocal value assigned?
 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//The value of sThreadLocal is assigned in this method
 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //Specific assignment point
        sThreadLocal.set(new Looper(quitAllowed));
    }
  • Through the above source code comments, we fully understand the meaning of the error log. The error log indicates that we did not call looper Prepare() method, and loop The prepare () method is the location of the sThreadLocal assignment.

How does a child thread create a handler? Just call the next Looper. before new Handler(). Prepare().

  • 2. Why can the main thread directly new Handler?

  • If the child thread directly new Handler will report an error, why won't the main thread report an error? I didn't call looper on the main thread Prepare? Then we have to look at the source code.

    //Let's look at the entry main method of ActivityMain and call looper prepareMainLooper();
    public static void main(String[] args) {
       ...
        Looper.prepareMainLooper();
        ...
    }

  //You can see from this point that the original thread called prepareMainLooper() when it was started, and prepare() was called in this method.  
 //sThreadLocal is assigned in advance.
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  • 3. Why does the handler leak memory?

  • First, popularize what is called memory leakage. When an object is no longer used and should be recycled, but another object in use holds its reference, so that it cannot be recycled, which makes the object that should be recycled cannot be recycled and stay in heap memory. In this case, memory leakage occurs.

  • Let's give an example of a Handler memory leak.

public class HandlerActivity extends AppCompatActivity {
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
}
  • When the above code is written, the compiler will immediately report yellow and prompt “this handler should be static or leaks might occur...Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main threa d, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”

Roughly speaking "Since this handler is declared as an internal class, it can prevent external classes from being garbage collected. If the handler is using Looper or MessageQueue for threads other than the main thread, there is no problem. If the handler is using Looper or MessageQueue of the main thread, you need to fix the handler declaration as follows: declare the handler static Class; And reference the "external class" through the WeakReference.

  • By saying so many things, it simply means that the above writing method will reference HandlerActivity by default. When the HandlerActivity is finish ed, the Handler may still be executing and cannot be recycled. At the same time, because the Handler implicitly references HandlerActivity, the HandlerActivity cannot be recycled, so the memory is leaked.

Let's write a correct way

public class HandlerActivity extends AppCompatActivity {
      MyHandler handler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
    private static class MyHandler extends Handler{
        private WeakReference<HandlerActivity> activityWeakReference;

        public MyHandler(HandlerActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}
  • The above method uses the method of static internal class + weak reference. In fact, if you do not need to access the members of HandlerActivity in the handleMessage() method, you do not need to use weak reference. You only need the static internal class. Weak reference is just convenient to call the internal members of HandlerActivity.
  • Non static internal classes and non static anonymous internal classes do hold references to external classes. Static internal classes do not hold references to external classes, which does not affect subsequent recycling. Therefore, there is no memory leakage.

4. Add a little knowledge. What is implicit reference?

  • In fact, the non-static internal classes and non-static anonymous internal classes we wrote implicitly help us pass in the parameter this during compiler compilation. That's why we can use the keyword this in methods at ordinary times. After understanding the implicit reference, why does it lead to memory leakage? Here we have to explain the garbage collection strategy of virtual machines.
  • Garbage collection mechanism: Java adopts the root search algorithm. When GC roots are unreachable and the object finalize does not save itself, it will be recycled. In other words, GC will collect objects that are not GC roots and are not referenced by GC roots, as shown in the figure below.

  • The connections between objects in the figure above are references between these objects. The judgment condition of garbage collection is on these connections. To prevent the leakage of non static internal classes, we must manage the reference relationship between objects.
  • Removing implicit references (removing implicit references through static internal classes) manually managing object references (modifying the construction method of static internal classes and manually introducing their external class references) when memory is unavailable, not executing uncontrollable code (Android can combine smart pointers and wrap external class instances with WeakReference) is a better way to solve memory leakage.

Note: static internal classes are not recommended for all internal classes. Static internal classes are recommended only when the life cycle of the internal class is uncontrollable. Non static inner classes can also be used in other cases.

Well, that's the end of the introduction to Handler. It's a little long. If it brings you some help, please praise it.

Keywords: Android Design Pattern

Added by jd6th on Fri, 24 Dec 2021 00:26:09 +0200