Psychological analysis: This question is difficult to ask, but if asked, it will be 100% impossible to answer. It’s hard for a developer to notice that a main thread has four loops that don’t block the main thread candidate: the main thread’s message loop mechanism and Linux’s looping wait asynchronism are a good example. Finally, the memory leak caused by Handle must be a plus

Start with a prepared interview catalogue


preface

Android message mechanism is mainly refers to the Handler mechanism, for everyone is already familiar with the Handler, but really master the Handler? This paper mainly through several questions around the Handler in-depth and expanded understanding.

Standing on the shoulders of giants will see further. If you are interested, you can also go to Gityuan’s blog to learn more about it. It is all dry goods. What’s more, he writes with authority. After all, he is also a key member of Xiaomi’s system engineers.

Questions

  1. Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?

  2. What is the message loop mechanism for the main thread (how does an infinite loop process other transactions)?

  3. What motivates ActivityThread? (ActivityThread what thread executes Looper)

  4. How does the Handler switch threads to send messages? (Thread to thread communication)

  5. What methods do child threads have to update the UI?

  6. Toast showDialog method in the child thread. (Does it have to do with child threads not being able to update the UI?)

  7. How do I handle memory leaks caused by improper use of Handlers?

Answer 1: Why does Looper loop death not cause applications 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 we often refer to, also known as the UI thread, is an ActivityThread. An ActivityThread initializes a Looper when it is created, which is why you can use a Handler 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, 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. Gityuan – Handler (Native)

Answer 2: What is the message loop mechanism for 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 via Handler

System_server process

The system_server process is the system process, the core carrier of the Java Framework framework, which runs a large number of system services, such as ApplicationThreadProxy (ABBREVIATED as ATP), ActivityManagerService (AMS for short). Both services run in different threads of the System_server process. Since ATP and AMS are based on IBinder interfaces, they are both binder threads. The creation and destruction of binder threads are determined by binder drivers.

App process

App processes are commonly referred to as applications. The main thread is mainly responsible for the life cycle of activities/Services and other components and UI-related operations run in this thread. Binder Threads ApplicationThread(AT) and ActivityManagerProxy (AMP) are AT least two binder threads in each App process

Binder

A Binder is used to communicate between processes. A Binder client of one process sends a transaction to a server of another process. For example, thread 2 sends a transaction to thread 4. Handlers are used to communicate between different threads in the same process, such as thread 4 in the figure sending messages to the main thread.


The lifecycle of an Activity, such as suspending an Activity, is shown as follows:

1. AMS of thread 1 calls ATP of thread 2; (Because the threads of the same process share resources, they can call each other directly, but need to pay attention to the problem of multithreading concurrency) 2. Thread 2 is transmitted to thread 4 of App process through binder; 3. Thread 4 sends a message to the main thread to suspend the Activity through the handler message mechanism. 4. The main thread in the stars. The loop () to iterate over the message, when I received the news of suspension of the Activity, then the message is distributed to ActivityThread. H.h andleMessage () method, through method calls again, 5. Finally, activity.onpause () is called, and when onPause() is finished, the loop continues.

Supplement:

The main method of ActivityThread is basically a message loop, and once you exit the message loop, your application 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.

Finally, through a paragraph of “Android Development art Exploration” summary:

ActivityThread communicates with AMS through the ApplicationThread. The Binder method is called back by AMS when the ApplicationThread requests the ActivityThread. The ApplicationThread then sends a message to H, which then switches the logic in the ApplicationThread to the ActivityThread for execution. Message loop model for the main thread

In addition, ActivityThreads are not actually threads, and unlike the HandlerThread class, activityThreads do not really inherit from the Thread class

If an ActivityThread is not a Thread, which Thread does Looper bind to?

Answer 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.

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? 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, why 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.

So again, the child thread must not update the UI, right?

This leaves us with two more things to look at in the next part: the drawing mechanism of a View and the internal mechanism of Android Window.

The main thread defines the Handler. The child thread sends messages through the mHandler. 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…

Handler is really……

A Toast, showDialog method in a child thread

Some people might look at this and think, “Child threads are not supposed to update the UI and it says how to update the UI

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 to the bottom of Toast’s internal implementation.

//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?

Answer 7: How do I 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 is to get the reflection of Looper’s MessageQueue method in the HandlerThread.

because

Looper.myqueue () will be used if it is called on the main thread Looper 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? More interview content, interview topics, the full set of Flutter videos, audio and video from zero to expert development. Follow GitHub: github.com/xiangjiana/… Get the interview PDF collection for free