It only takes 5 minutes for the handles to show you the wrong use of the Handler

Keywords: Mobile Java Android REST Linux

The whole collaborative relationship of Hander, Looper, MessageQueue, Message is like the overall operational relationship of a restaurant:

Handler - Dinner Looper - Rear Chef. MessageQueue - Order machine. Message - A table-by-table order.

Next we review the scene of our restaurant meals, which are divided into standard and special meals. Let's break them down.

Standard process

  1. First enter a store and submit the data to the back-kitchen single machine by ordering a meal.
  2. Then the chef picks up the orders one by one and gives the chef to start making them in the order in which they order their meals.
  3. Make the ready-to-serve dish and mark the ready order.

Special Processes

  1. An order is a delayed order, such as when a customer asks for 30 minutes to have it ready for production. The order is placed in the right place in the order queue by time, and the alarm is set through SystemClock.uptimeMillis().The reason why uptimeMillis is used is that it is the milliseconds from the start of the system boot calculation and is not affected by the manual timing.
  2. If the order machine is full of delayed orders, order the rear cook to rest, place a non-intrusive sign (needWake) on the door and wait for the alarm to remind him. If a new instant order comes in and a non-intrusive sign is found, wake the cook up via nativeWake() and start cooking again.
  3. However, in order to improve the coverage of the store's dishes, many adjacent stores choose to operate cooperatively, that is, you can mix and match the meals of the next stores to eat at this store. At this time, just order the order submitted by the next restaurant, so that the chef of the next store can take out the order, make it and serve it through the order machine.

summary

A store can have more than one orderer, but a chef can only have one.There can only be one single machine.

Mapping to the above scenario, a store is like a Thread, and a Thread can have multiple Handler s (orderers), but only one Looper, one MessageQueue, and multiple Messages (orders).

Look at the whole process

Based on the above examples, we look at the source code by analogy, and fully study the process and implementation principle of the whole mechanism.

Workflow for Looper

ActivityThread.main();//Initialize Entry
    1\. Looper.prepareMainLooper(); //Initialization
          Looper.prepare(false); //Settings cannot be turned off
              Looper.sThreadLocal.set(new Looper(quitAllowed)); //Bind to Thread
                    1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper and MesageQueue bindings
                    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() //Bottom Blocking Implementation
                        native -> epoll_wait();
        2.2.Handler.dispatchMessage(msg);//Message Distribution

myLooper().mQueue.next() Implementation Principles

  1. Get messages from MessageQueue through myLooper().mQueue.next() loop, and process asynchronous messages first if synchronization barrier is encountered.
  2. The synchronization barrier is a message sent with Message.postSyncBarrier(), whose target is not bound to a Handler.Asynchronous messages take precedence over synchronous messages in the Hnandler.
  3. 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.dispatchMessage(msg) implementation principles

  1. Priority callback to msg.callback.
  2. The callback in the handler constructor is then called back.
  3. Finally callback handler handleMessage ().

Hander's process for sending messages

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 for 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 to this.
                    Handler.mQueue.enqueueMessage();//Add message to MessageQueue.

Implementation principle of MessageQueue.enqueueMessage() method

  1. If the message queue is abandoned, an exception is thrown.
  2. If the currently inserted message is an instant message, use the message as a new header element, point the next of the message to the old header element, and wake the Looper thread through needWake.
  3. If the message is asynchronous, the Looper thread is not waked up by inserting the Message.when length into the queue location.

It is often asked why Looper blocking on the main thread does not cause ANR?

  1. First we know that ANR is the main thread and has not responded in 5 seconds.
  2. What is 5 seconds no response?All operations in the Android system are performed by adding events to the event queue through Handler, and Looper loops through the queue to fetch the events.The ANR is prompted if the main thread event feedback exceeds 5 seconds.
  3. If no events come in, nativePollOnce() in queue.next() in the loop method will be blocked based on the Linux pipe/epoll mechanism and will not report ANR.
  4. For the example above, ANR can be interpreted as the user failing to serve on time after an instant meal (of course, there are many reasons for not serving on time, it may be slow (time consuming IO, etc.), it may be that the kitchen utensils are occupied (deadlock), it may not have enough cooks (poor CPU performance), etc.)., complaints or bad reviews from customers.However, if the agreed time has not yet arrived, or if no one is ordering at the moment, there will be no bad reviews or complaints, and therefore no ANR.

All of the above is about principles, source code, and let's take a few examples of special scenarios

1. Why can't a child thread directly use 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)

The error prompt "not called Looper.prepare()" tells us that Looper.prepare() has not been called, so why do we have to look at the source code?

 public Handler(Callback callback, boolean async) {
        ...Omit some code

       //The mLooper object is obtained from Looper.myLooper(), and an exception is thrown if mLooper ==null
        mLooper = Looper.myLooper();
        if (mLooper == null) {
             //You can see that the exception was 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() {
        //And myLooper() is acquired through sThreadLocal.get(), what is sThreadLocal?
        return sThreadLocal.get();
    }

 //You can see that sThreadLocal is a ThreadLocal <Looper>object, where does the ThreadLocal value get assigned from?
 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 points
        sThreadLocal.set(new Looper(quitAllowed));
    }
  • With the source comments above, you can see exactly what the error log means. The error log indicates that we did not call the Looper.prepare() method, which is where the sThreadLocal assignment is located. How does a child thread create a Handler?Simply call Looper.loop() before the new Handler().

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

  • Subthreads directly new Handler will fail. Why won't the main thread fail?Didn't I call Looper.prepare() on the main thread, either?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();
        ...
    }

  //As you can see, the main thread called prepareMainLooper() by default at startup, whereas prepare() was called in this method.
 //The sThreadLocal was assigned earlier.
  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 Handler leak memory?

  • First, what is known as a memory leak? When an object is no longer using what should have been recycled, but another object in use holds a reference to it so that it cannot be recycled, which causes the object that should have been recycled to remain in heap memory instead of being recycled, a memory leak occurs.

  • Let's take a look at a Handler memory leak scenario.

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 immediately yellows and prompts:

"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 thread, 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."

In general, "Since this handler is declared as an internal class, it prevents external classes from being garbage collected.If the handler is using Looper or MessageQueue on a thread other than the main thread, there is no problem.If the handler is using the main thread's Looper or MessageQueue, you need to fix the handler declaration as follows: declare the handler as a static class; and reference the external class through WeakReference.

  • This means simply that the Handler activity is referenced by default. When the Handler activity is finish ed, the Handler may still be executing and cannot be recycled, and because the Handler implicitly references the Handler activity, the Handler activity cannot be recycled, so the memory is leaked.

Let's write a correct way of writing

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 static internal class + weak reference, in fact, if you do not need to access members of HandlerActivity in handleMessage() method, you do not need to use weak reference, only static internal class, weak reference is just convenient to call inner members of HandlerActivity.
  • References to external classes do exist in both non-static and non-static anonymous internal classes. References to external classes are not held in static internal classes, which does not affect subsequent recycling and therefore there is no memory leak.

4. Add a little bit of knowledge, what is an implicit reference?

  • In fact, we write non-static internal classes and non-static anonymous internal classes, which implicitly help us pass in this parameter during compiler compilation. This is why we usually use this keyword in our methods and understand the implicit reference, then why does it cause memory leak?Here's another example of the virtual machine's garbage collection strategy.
  • Garbage Collection Mechanism: Java uses a root search algorithm to recycle when GC Roots are not reachable and finalize the object is not self-saving.That is, GC collects objects that are not GC roots and are not referenced by GC roots, just like the figure below.

  • The links between the objects in the diagram above are references between these objects, which are the criteria for garbage collection. To prevent leakage of non-static internal classes, you have to manage the reference relationships between objects.
  • Removing implicit references (removing implicit references through static internal classes) Manually managing object references (modifying the construction of static internal classes and introducing their external class references manually) When memory is unavailable, not executing uncontrollable code (Android can combine smart pointers, WeakReference wraps external class instances) is a better way to resolve memory leaks.

Be careful:

Static internal classes are not recommended for all internal classes. Static internal classes are recommended only if the life cycle in the internal class is not controlled.Otherwise, you can use non-static internal classes.

Last

This year, I spent a month to collect and organize a set of knowledge system. If you have the idea of in-depth and systematic learning, you can click Portal I will send all the information I have collected and sorted to you to help you progress faster.

Posted by timbo6585 on Wed, 11 Dec 2019 18:56:45 -0800