preface

Android development is inseparable from dealing with Handler, which is usually used as a communication tool between the master thread and child threads. Handler, as an important part of the Messaging mechanism in Android, has indeed brought great convenience to our development.

You can say that whenever there’s an asynchronous thread talking to the main thread there’s going to be a Handler. Then;

  • What is the rationale behind the Handler communication mechanism?
  • What is the difference between Handler, Thread, and HandlerThread?
  • Message mechanism Handler function? What are the elements? What is the process?
  • Causes of memory leaks caused by handlers and the optimal solution
  • What happens to message queues when you use Handler’s postDealy?
  • Can I just new a Handler on a child thread? How to do?
  • This Handler does not block the main thread because it has an infinite Loop

This article will keep you informed

First, the Handler principle

Handler the whole process, there are mainly four objects, handler, Message, MessageQueue, stars. When the application is created, it creates a handler object in the main thread,

In Android, the main thread cannot perform time-consuming operations, and the child thread cannot update the UI. So there’s a handler, and it’s all about communicating between threads.

When the application is created, a handler object is created in the main thread. We store the Message we want to send to Message, and the handler sends the Message to MessageQueue by calling sendMessage. The Looper object constantly calls the loop() method

The Message is continually fetched from the MessageQueue and handed to handler for processing. Thus realize the communication between threads.

Second, differences between Handler, Thread and HandlerThread:

1) Handler a bridge for message communication, mainly used to send messages and process messages.

To create a message queue, call Looper. Prepare () to create a Looper instance and call loop() to loop the message.

3) The HandlerThread is a thread with a Looper. Looper is created by calling looper.prepare () in the run() method of the HandleThread. Looper.loop() is called to start the loop, which retrieves messages from the message queue and passes them to Handler. Use the thread’s Looper to create an instance of a Handler whose handleMessage() method runs in a child thread. That is, the Handler creates an instance of the thread’s Looper, and it binds to the corresponding thread to process messages on that thread. Its handleMessage() method is run in that thread, and the no-argument construction is the main thread by default.

The HandlerThread provides the quit()/quitSafely() methods to quit the HandlerThread’s message loop. They call Looper’s quit and quitSafely() methods, respectively. Quit removes all the messages from the message queue. The quitSafely removes all delayed messages from the message queue and sends the non-delayed messages out to the Handler to handle.

HandlerThread is suitable for handling local I/O operations (reading and writing to databases or files). Local I/O operations do not take a long time and do not cause large block for single-thread and asynchronous queues. Network operations are time-consuming and block subsequent requests easily. Therefore, handlerThreads are not suitable for network operations

Three, message mechanism Handler function? What are the elements? What is the process?

Is responsible for cross-thread communication, because the main thread cannot do time-consuming operations, and the child thread cannot update the UI, so when the child thread needs to update the UI after time-consuming operations, the Handler switches the UI-related operations to the main thread.

It is divided into four elements: (1) Message: the Message that needs to be delivered. Messages are divided into messages generated by hardware (such as buttons and touches) and messages generated by software.

②MessageQueue: stores and manages messages and messages sent by the Handler. Read will automatically delete messages, single linked list maintenance, insert and delete advantages. In its next() method, it loops indefinitely, constantly determining if there is a message, returning it and removing it

③Handler (Message processor) : Responsible for sending and processing messages. It mainly sends various message events to the message pool (handler.sendMessage ()) and processes the corresponding message events (handler.handleMessage ()) on a first-in-first-out basis, internally using a single linked list structure.

④Looper: In this thread, the Message is obtained from MessageQueue and distributed to Handler. When Looper is created, a MessageQueue is created and the Message loop starts when loop() is called. MessageQueue’s next() method is constantly called, processed when there is a message, otherwise blocked in messageQueue’s next() method. MessageQueue’s quit() is called when Looper’s quit() is called, next() returns null, and the loop() method exits.

Causes of memory leakage caused by Handler and the best solution

Handler allows us to send delayed messages that will leak if the user closes the Activity during the delay. This leak happens because Message holds Handler, and because of Java’s nature, inner classes hold external classes, so the Activity is held by Handler, which eventually leads to Activity leaks.

Solution: Defines a Handler into a static inner class in internal holds the Activity of weak references, and during the Acitivity onDestroy () call Handler. RemoveCallbacksAndMessages (null) remove all messages in a timely manner.

5, using Handler postDealy after the message queue will have what changes?

If the message is the only one in the queue, the message will not be sent. Instead, Looper will be blocked and woken up when the time is up. However, if a new message is to be added at this time and the head of the message queue is longer than the delay time, it is inserted to the head and sorted according to the trigger time, with the minimum time at the head and the maximum time at the end

Is it possible to create a Handler directly in the child thread? How to do?

No, because in the main thread, the Activity contains a Looper object that automatically manages the Looper and handles messages sent from the child thread. For child threads, there is no object to help us maintain the Looper object, so we need to do it manually. So to start Handler on a child thread, create Looper and start Looper loop

New Thread (new Runnable() {@override public voidrun() {
                 looper.prepare();
                 new Handler() {
                         @Override 
                         piblic void handlerMessage(Message msg) {
                             super,handleMessage(msg);
                         }
                 }
                 looper.loop();
            }
       }).start();
Copy the code

7, There is an infinite Loop in Handler, why not block the main thread, what is the principle

It’s a tough question to ask, but it’s 100% impossible to answer if asked. It’s hard to notice that a quad loop on the main thread doesn’t block the main thread.

Start with the main thread message loop mechanism and Linux loop asynchrony etc. Finally, the memory leak caused by Handle is definitely a plus

Here are a few simple questions to throw out:

1. Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources? 2. What is the message loop mechanism of the main thread (how does an infinite loop process other transactions)? 3. What motivates ActivityThread? 4. How does the Handler switch threads to send messages? (Communication between threads) 5. What methods does the child thread have to update the UI? 6. Toast, showDialog, method in child thread. 7. How to handle memory leaks caused by improper use of handlers?

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

Threads do not have Looper by default. If you want to use a Handler, you must create a Looper for the thread. The main thread, also known as the UI thread, is an ActivityThread. When an ActivityThread is created, it initializes a Looper, which is why you can use handlers in the main thread by default

Let’s start with a piece of code:

 new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("qdx"."step 0 ");
            Looper.prepare();

            Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();

            Log.e("qdx"."step 1 ");
            Looper.loop();

            Log.e("qdx"."step 2 ");

        }
    }).start();
Copy the code

We know looper.loop (); Looper.prepare(); looper.prepare (); With the stars. The loop (); In between.

In a child thread, if a Looper is manually created for it, the quit method should be called to terminate the message loop after everything is done. Otherwise, the child thread will remain in a waiting (blocking) state. If Looper is quit, the thread will terminate immediately. It is therefore recommended to terminate Looper when it is not needed

The result is exactly what we said. If we know about ActivityThread at this point, and in the main method we see that the main thread also maintains a message loop in Looper mode.

public static void main(String[] args) { Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); // Create Looper and MessageQueue objects for processing the main thread messages. thread.attach(false); // Create a new thread.if(sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); // The application crashes or exits if you execute the following method... throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

So back to our question, does this loop cause the application to freeze, and even if it doesn’t, does it slowly consume more and more resources?

A thread is a piece of executable code. When the executable code completes, the thread life cycle terminates and the thread exits. For the main thread, we do not want to be run for a period of time, and then exit, so how to ensure that it is always alive? Binder threads, for example, also use an infinite loop to write and write to binder drivers in a different way. Of course, they do not simply loop forever, falling asleep when there is no message. But that might raise another question: how do you do anything else if it’s an infinite loop? By creating a new thread. Really getting stuck the operation of the main thread is in the callback method onCreate/onStart/onResume operating time is too long, can lead to a frame, even ANR, stars. The loop itself does not result in application. Is the main thread running in an endless loop particularly CPU intensive? If the main thread MessageQueue has no message, it blocks the nativePollOnce() method in the loop queue.next(). The main thread then releases CPU resources and goes to sleep until the next message arrives or a transaction occurs, waking up the main thread by writing data to the pipe end. The epoll mechanism adopted here is an IO multiplexing mechanism, which can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), it immediately notifies the corresponding program to carry out read or write operations, which is essentially synchronous I/O, that is, read and write is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources.

What is the message loop mechanism of the main thread?

In fact, a new binder thread is created before it goes into an infinite loop, in code activityThread.main () :

Public static void main(String[] args) {// Create Looper and MessageQueue objects, used to process the main thread message looper.prepareMainLooper (); ActivityThread thread = new ActivityThread(); // Create a new thread with a new thread.false); Looper.loop(); // Message loop throws new RuntimeException("Main thread loop unexpectedly exited");
   }
Copy the code

The Activity’s life cycle depends on the main thread’s looper. loop, which takes action when different messages are received: Once you exit the Message loop, your program can exit. Fetching a message from a message queue may block and will be processed accordingly. If a message takes too long to process, it can affect the refresh rate of the UI thread and cause a lag.

The Attach (false) method creates a Binder thread (ApplicationThread, the server side of the Binder that receives events from AMS). The Binder thread sends messages to the main thread through a Handler. “Activity Start Process”

Such as received MSG = H.L AUNCH_ACTIVITY, call the ActivityThread. HandleLaunchActivity () method, which will eventually through reflection mechanism, create the Activity instance, and then execute the Activity. The onCreate () method;

Such as MSG = have received H.P AUSE_ACTIVITY, call the ActivityThread. HandlePauseActivity () method, which will eventually perform the Activity. The onPause () method.

Where does the main thread come from? Of course, other threads in the App process send messages to the main thread process via Handler

3. What motivates ActivityThread?

Process A process is created before each APP runs. This process is produced by Zygote fork and is used to host various activities/services running on the app. Processes are completely transparent to the top applications, which is a deliberate move by Google to make apps run in the Android Runtime. In most cases an App runs in a process, unless the Android: Process attribute is configured in androidmanifest.xml, or the process is forked through native code

Threads Threads are common in applications, such as creating a new Thread every time a new Thread().start is created. From the perspective of Linux, there is no essential difference between a process and a thread except whether they share resources. They are both a task_struct structure. In the view of CPU, a process or thread is nothing more than a piece of executable code. Ensure that each task has as fair a slice of CPU time as possible.

The main thread hosting ActivityThread is the process created by Zygote fork.

4. How does Handler enable thread switching

In fact, it is generally clear from the above that threads share resources. So Handler handles different threads just by looking at the asynchronous case.

Here are a few things about Handler. The Handler is created using the current thread’s Looper to construct the message loop. The Looper is bound to the thread in which it was created, and the Handler processes the message in the thread in which it is associated with the Looper. (Knocks on the blackboard)

So how does the Handler internally get the Looper of the current thread — ThreadLocal? ThreadLocal can store and provide data in different threads without interfering with each other. ThreadLocal can easily fetch Looper for each thread.

Note, of course:

Threads do not have Looper by default. If you want to use Handler, you must create Looper for the thread. The main thread, also known as the UI thread, is an ActivityThread. The Looper is initialized when an ActivityThread is created, which is why we can use handlers in the main thread by default.

Why does the system not allow access to the UI in child threads? (From Exploring the Art of Android Development)

This is because Android UI controls are not thread-safe. If concurrent access in multiple threads can cause the UI controls to be in an unexpected state, then why does the system not lock access to the UI controls? There are two disadvantages:

(1) Adding locks complicates UI access logic. (2) Locking reduces the efficiency of UI access because it blocks the execution of certain threads. So the simplest and most efficient way to handle UI operations is to adopt a single-threaded model

5. What methods does the child thread have to update the UI

The main thread defines a Handler, the child thread sends a message through the mHandler, and the main thread Handler’s handleMessage updates the UI. Use the Activity object’s runOnUiThread method. Create a Handler and pass in getMainLooper. The post (Runnabler).

RunOnUiThread We’re not going to analyze the first one, but we’re going to look at the second one that’s more commonly used.

Let’s go over what we said above

The Looper is bound to the thread in which it is created, and the Handler processes the message in the thread in which it is associated with the Looper. (Knocks on the blackboard)

  new Thread(new Runnable() {
        @Override
        public void run() {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //DO UI method

                }
            });

        }
  }).start();

  final Handler mHandler = new Handler();

  public final void runOnUiThread(Runnable action) {
     if(Thread.currentThread() ! = mUiThread) { mHandler.post(action); // child thread (non-UI thread)}else{ action.run(); }}Copy the code

If you enter the Activity class, you can see the update UI message sent by mHandler if it is in a child thread. This Handler is created in the Activity, that is, in the main thread, so it is no different from the main thread where we use the Handler to update the UI. Because this Looper is the Looper created in ActivityThread (looper.prepareMainLooper ()).

Create a Handler and pass in a getMainLooper and similarly, in a child thread, can we create a Handler and get a MainLooper and update the UI in that child thread? First we see that in the Looper class we have the static object sMainLooper, and this sMainLooper is the MainLooper created in ActivityThread

  private static Looper sMainLooper;  // guarded by Looper.class

  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

So needless to say, we can use this sMainLooper to update the UI

  new Thread(new Runnable() {
        @Override
        public void run() {

            Log.e("qdx"."step 1 "+Thread.currentThread().getName());

            Handler handler=new Handler(getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {

                    //Do Ui method
                    Log.e("qdx"."step 2 "+Thread.currentThread().getName()); }}); } }).start();Copy the code

View.post(Runnabler) as usual, we click on the source //View

/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 */
  public boolean post(Runnable action) {
     final AttachInfo attachInfo = mAttachInfo;
     if(attachInfo ! = null) {returnattachInfo.mHandler.post(action); As we can no longer wait for the delivery of our order, we can no longer wait until we know onwhich thread it needs to run.
     // Assume that the runnable will be successfully placed after attach.
     getRunQueue().post(action);
     return true;
  }


     /**
      * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This * handler can be used to pump events in the UI events queue. */ final Handler  mHandler;Copy the code

This Handler can handle UI events, so its Looper is also the main thread’s sMainLooper. This means that most UI updates are done through handlers.

In addition, UI updates can also be implemented via AsyncTask. Yes, also via Handler…

A child thread is not allowed to update the UI and the method for updating the UI is stated. Hold on, brother, and let me finish my story

    new Thread(new Runnable() {
        @Override
        public void run() {

            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show(); // Crash no doubt}}).start();Copy the code

If you look at this crash log, it’s a little confusing, because normally if the child thread can’t update the UI control, it will report the following error:

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler To meet you.

  new Thread(new Runnable() {
        @Override
        public void run() {

            Looper.prepare();
            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();
            Looper.loop();

        }
  }).start();
Copy the code

This gives the child thread Toast. ? As usual, let’s get down to the bottom and look at the internal execution of Toast

/**
 * Show the view for the specified duration.
 */
  public void show() { `````` INotificationManager service = getService(); / / gets called notification service for SMgr String PKG = mContext. GetOpPackageName (); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); //enqueue? Does it have to do with the queue of the Handler? } catch (RemoteException e) { // Empty } }Copy the code

In the show method, we see that Toast’s show method is different from normal UI controls and also performs Toast drawing using the Binder interprocess communication method. It would not be in the process of the much discussed, interested in NotificationManagerService class analysis.

Binder’s native TN class is a Binder’s native TN class. Binder’s native TN class is Binder’s native TN class. In the show method of Toast, will pass this TN object NotificationManagerService to communication! And we also found its show method in TN.

Private static class TN extends ITransientNotification. Stub {/ / Binder server-side implementation class / * * * schedule handleShow into the right thread */ @Override public void show(IBinder windowToken) { mHandler.obtainMessage(0, windowToken).sendToTarget(); } final Handler mHandler = newHandler() { @Override public void handleMessage(Message msg) { IBinder token = (IBinder) msg.obj; handleShow(token); }}; }Copy the code

Watching the code above, know the child thread Toast in the cause of the error, because in TN use Handler, so you need to create stars. Now that the Handler is used to send the message, you can find the method to update the Toast in the handleMessage. See handleMessage handled by handleShow. / / Toast TN classes

   public void handleShow(IBinder windowToken) {

            ``````
            mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

            mParams.x = mX;
            mParams.y = mY;
            mParams.verticalMargin = mVerticalMargin;
            mParams.horizontalMargin = mHorizontalMargin;
            mParams.packageName = packageName;
            mParams.hideTimeoutMilliseconds = mDuration ==
                Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            mParams.token = windowToken;
            if(mView.getParent() ! = null) { mWM.removeView(mView); } mWM.addView(mView, mParams); / / use the WindowManager addView method trySendAccessibilityEvent (); }}Copy the code

See here can be summed up:

Toast is essentially displayed and drawn by the Window, and the main thread cannot update the UI due to the behavior of ViewRootImpl’s checkThread method in the View tree maintained by the Activity. The TN class in Toast uses Handler to queue and time-control the Toast display, so looper.prepare () is used in the child thread to prevent an exception from being thrown when TN is created. And the stars. The loop (); (This is not recommended, however, because it prevents the thread from executing and causes a memory leak.)

So does Dialog. At the same time, we have another knowledge point to study: what is Windows in Android, what is its internal mechanism?

7. How to handle memory leaks caused by improper use of Handlers? First create a Looper in the child thread above for program effect using the following method

    Looper.prepare();
        ``````
    Looper.loop();
Copy the code

In fact, this is a very dangerous approach

In a child thread, if you manually create a Looper for it, you should call quit to terminate the message loop after everything is done. Otherwise, the child thread will remain in a waiting state. If you exit the Looper, the thread will terminate immediately. (【 stars. MyLooper (). The quit ();. )

If a message is processed in the Handler’s handleMessage (or run) method, it will remain in the message queue of the main thread and will affect the system’s ability to reclaim the Activity, causing a memory leak.

For details, see Handler Memory Leak analysis and troubleshooting

To summarize, there are two main points to address Handler memory leaks

1 There are delayed Messages to remove when the Activity is destroyed. 2 Leaks caused by anonymous inner classes are replaced with anonymous static inner classes and weak references to the context or Activity.

conclusion

Handler was able to free up so much spray, and at the same time thanks to the previous fumble.

In addition, there are many hidden secrets of Handler, waiting for you to explore, I will briefly introduce the following two minutes

HandlerThread

IdleHandler

HandlerThread

HandlerThread is a Thread that can use a Handler. The implementation of this Thread is also very simple. In the run method, looper.prepare () is used to create a message queue. And start the message loop with looper.loop () (basically the same way we created it manually), which in practice allows the creation of handlers in HandlerThread.

Since the HandlerThread’s run method is an infinite loop, use the quit or quitSafely method to terminate the thread when it is not needed.

Handlerthreads are threads by nature, so remember that the handleMessage that handles the message in the associated Handler is a child thread.

IdleHandler

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
  public static interface IdleHandler {
     /**
      * Called when the message queue has run out of messages and will now
      * wait for more.  Return true to keep your idle handler active, false
      * to have it removed.  This may be called if there are still messages
      * pending in the queue, but they are all scheduled to be dispatched
      * after the current time.
      */
     boolean queueIdle();
 }
Copy the code

As you can see from the comments, this interface method is called after the message queue has finished processing or while waiting for more messages while blocking. The return value is false for one callback and true for multiple callbacks.

The specific code is as follows

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {


            return false; }});Copy the code

Another tip: get the reflection of Looper’s MessageQueue method in the HandlerThread.

Because looper.myQueue () will use the main thread Looper if called on the main thread using handlerThread.getLooper().getQueue() The lowest version requires 23 // handlerThread to retrieve MessageQueue

  Field field = Looper.class.getDeclaredField("mQueue");
        field.setAccessible(true);
        MessageQueue queue = (MessageQueue) field.get(handlerThread.getLooper());
Copy the code

So Android message loop mechanism is through Handler, whether you can use IdleHandler to judge the Activity loading and drawing (measure,layout,draw, etc.)? And does the IdleHandler also hide special functionality that no one knows about?

The last

Knowledge summary

  • What is the rationale behind the Handler communication mechanism?
  • What is the difference between Handler, Thread, and HandlerThread?
  • Message mechanism Handler function? What are the elements? What is the process?
  • Causes of memory leaks caused by handlers and the optimal solution
  • What happens to message queues when you use Handler’s postDealy?
  • Can I just new a Handler on a child thread? How to do?
  • This Handler does not block the main thread because it has an infinite Loop
  • Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
  • What is the message loop mechanism for the main thread (how does an infinite loop process other transactions)?
  • What motivates ActivityThread? (ActivityThread what thread executes Looper)
  • How does the Handler switch threads to send messages? (Thread to thread communication)
  • What methods do child threads have to update the UI?
  • Toast showDialog method in the child thread. (Does it have to do with child threads not being able to update the UI?)
  • How do I handle memory leaks caused by improper use of Handlers?

There is a lot of wisdom behind the simplicity of the Handler, so try to learn from them.

After reading and understanding this article, you can say that you have a very deep and comprehensive understanding of Handler, which is certainly good enough for an interview.

More interview topics PDF, including BatJ. bytedance interview topics, Algorithms topics, Advanced Technology topics, Hybrid Development topics,Java Interview topics, Android,Java tips, to performance optimization. Thread. The OpenCV. The NDK.

Android Interview + Notes

Github.com/xiangjiana/…