Handler is a message processing mechanism in Android, it is a solution for communication between threads, and you can also understand that it is natural for us to create a queue in the main thread, the order of messages in the queue is the time we set the delay. If you want to implement a queue in Android, Think about it first. This paper is divided into three parts:

  • Handler source code and FAQ answers
    1. What is the maximum number of handlers in a thread, Looper, MessageQueue?
    2. Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
    3. How does the child thread update the UI, such as Dialog, Toast, etc.? Why does the system not recommend updating the UI in child threads?
    4. How does the main thread access the network?
    5. How do I handle memory leaks caused by improper use of handlers?
    6. Handler message priority, what are the application scenarios?
    7. When does the main thread Looper exit? Can I log out manually?
    8. How to determine whether the current thread is the main thread of Android?
    9. The correct way to create a Message instance?
  • Handler In-depth problem solving
    1. ThreadLocal
    2. Epoll mechanism
    3. Handle Synchronization barrier mechanism
    4. Handler lockrelated problems
    5. A synchronization method in Handler
  • Some applications of Handler in systems and third-party frameworks
    1. HandlerThread
    2. IntentService
    3. How to build an APP that doesn’t crash
    4. Glide application

Handler source code and FAQ answers

Here’s the official definition:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is  bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.Copy the code

Handler allows you to send a Message/Runnable to a thread’s MessageQueue. Each Handler instance is associated with a thread and the Message queue for that thread. When you create a Handler, it should be bound to a Looper (the main thread already creates a Looper by default, and the child thread needs to create its own Looper). It sends a Message/Runnable to the corresponding Message queue of Looper and processes the corresponding Message/Runnable in that thread of Looper. The following diagram shows how this Handler works

Workflow flow chart of Handler

It can be seen that in Thread, the conveyor belt of Looper is actually a dead loop, which continuously takes messages from MessageQueue MessageQueue and finally delivers them to handler. dispatchMessage for message distribution. The Handler sendXXX, Handler. PostXXX these methods the MessageQueue messages sent to the queue, the entire model is actually a producers, consumers, a steady stream of news production, processing messages, no news for dormancy. MessageQueue is a priority queue consisting of a single linked list (the headers are taken, so it is a queue).

As mentioned earlier, when you create a Handler you should bind it to a Looper (binding can also be interpreted as creating a Looper, the main thread already creates a Looper by default, and the child thread needs to create a Looper by itself), so let’s first look at how this is handled in the main thread:

//ActivityThread.java
public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
		···
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code

As you can see in the main method of ActivityThread, we call looper.prepareMainLooper (), then get the Handler of the current thread, and finally call looper.loop (). Let’s take a look at the looper.prepareMainLooper () method

//Looper.java  
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself.  See also: {@link #prepare()}
*/
public static void prepareMainLooper(a) {
     prepare(false);
     synchronized (Looper.class) {
         if(sMainLooper ! =null) {
             throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}//prepare
private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code

PrepareMainLooper () creates a Looper for the current thread and stores the Looper instance in the thread-local variable sThreadLocal(ThreadLocal), i.e. each thread has its own Looper. PrepareMainLooper creates a MessageQueue for that thread when Looper is created. PrepareMainLooper determines if sMainLooper has a value. If it is called multiple times, it will throw an exception, so the main thread will have only one Looper and MessageQueue. The prepare(true) method will be called when looper.prepare () is called in a child thread. The prepare(true) method will be called when looper.prepare () is called in a child thread.

//Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code

SMainThreadHandler = thread.gethandler (). The getHandler actually gets the mH Handler.

//ActivityThread.java
final H mH = new H(); 
@UnsupportedAppUsage
	final Handler getHandler(a) {
  	return mH;
}
Copy the code

MH this Handler is an inner class of ActivityThread. If you look at the handMessage method, you can see that this Handler handles messages for the four components, applications, etc., such as creating a Service and binding messages to the Service.

//ActivityThread.java
class H extends Handler {...public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if(mInitialApplication ! =null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case RECEIVER:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                handleReceiver((ReceiverData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case CREATE_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                handleCreateService((CreateServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                handleBindService((BindServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case UNBIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                handleUnbindService((BindServiceData)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case SERVICE_ARGS:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
                handleServiceArgs((ServiceArgsData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case STOP_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
                handleStopService((IBinder)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break; ...case APPLICATION_INFO_CHANGED:
                mUpdatingSystemConfig = true;
                try {
                    handleApplicationInfoChanged((ApplicationInfo) msg.obj);
                } finally {
                    mUpdatingSystemConfig = false;
                }
                break;
            case RUN_ISOLATED_ENTRY_POINT:
                handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
                        (String[]) ((SomeArgs) msg.obj).arg2);
                break;
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                if (isSystem()) {
                    // Client transactions inside system process are recycled on the client side
                    // instead of ClientLifecycleManager to avoid being cleared before this
                    // message is handled.
                    transaction.recycle();
                }
                // TODO(lifecycler): Recycle locally scheduled transactions.
                break;
            case RELAUNCH_ACTIVITY:
                handleRelaunchActivityLocally((IBinder) msg.obj);
                break;
            case PURGE_RESOURCES:
                schedulePurgeIdler();
                break;
        }
        Object obj = msg.obj;
        if (obj instanceof SomeArgs) {
            ((SomeArgs) obj).recycle();
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: "+ codeToString(msg.what)); }}Copy the code

Finally, we look at the looper.loop () method

//Looper.java
public static void loop(a) {
  	// Get Looper from ThreadLocal
    finalLooper me = myLooper(); ...finalMessageQueue queue = me.mQueue; ...for (;;) { / / death cycle
      	// Get the message
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }, MSG. Target. DispatchMessage (MSG); ...// recyclemsg.recycleUnchecked(); }}Copy the code

The loop method is an infinite loop that fetches queue.next() from the message queue and dispatches the message to the Handler(msg.target) without any binding. Since the Handler has only one Looper and one MessageQueue per thread, it is the Handler that calls the looper.loop () method. Looper.loop() continues to fetch messages in an endless loop that is eventually recycled.

The target(Handler) parameter in Message is highlighted here. It is this variable that allows each Message to find a corresponding Handler for Message distribution, allowing multiple handlers to work simultaneously.

What do we do in the child thread? First create a Handler in the child thread and send a Runnable

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run(a) {
                        Toast.makeText(HandlerActivity.this."toast",Toast.LENGTH_LONG).show(); }}); } }).start(); }Copy the code

The error log tells you that you need to call looper.prepare () in the child thread, essentially creating a Looper to “associate” with your Handler.

    --------- beginning of crash
2020-11- 0915:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.jackie.testdialog, PID: 21122
    java.lang.RuntimeException: CanT create handler inside thread Thread thread [thread-2,5,main] that has not called Looper. Prepare () at android.os.Handler.
      
       (Handler.java:207) at android.os.Handler.
       
        (Handler.java:119) at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31) at java.lang.Thread.run(Thread.java:919)
       
      Copy the code

Add looper.prepare () to create Looper while calling looper.loop () to begin processing the message.

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                // create Looper, MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run(a) {
                        Toast.makeText(HandlerActivity.this."toast",Toast.LENGTH_LONG).show(); }});// Start processing messages
                Looper.loop();
            }
        }).start();
    }
Copy the code

It is important to note that the quit method should be called to terminate the message loop after everything is done, otherwise the child thread will remain in the loop waiting state, so terminate Looper when it is not needed, calling looper.mylooper ().quit().

Android does not allow you to update the UI on child threads. This is not the case. In ViewRootImpl, the checkThread method checks the mThread. CurrentThread (),mThread is initialized in the ViewRootImpl constructor, which means that a ViewRootImpl Thread must be created on the same Thread that called the checkThread. UI updates are not limited to the main thread.

void checkThread(a) {
    if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

Here we need to introduce some concepts. Window is the Window in Android. Every Activity and Dialog and Toast corresponds to a specific Window respectively. A Window and a View are linked by the View View pl, so it exists as a View. If we look at the creation of the ViewRootImpl in Toast, the show method calling Toast will eventually call its handleShow method

//Toast.java
public void handleShow(IBinder windowToken) {...if(mView ! = mNextView) {// Since the notification manager service cancels the token right
        // after it notifies us to cancel the toast there is an inherent
        // race and we may attempt to add a window after the token has been
        // invalidated. Let us hedge against that.
        try {
            mWM.addView(mView, mParams); // Create the ViewRootImpl
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
            /* ignore */}}}Copy the code

The final implementer of this mWM (WindowManager) is Windows ManagerGlobal, whose addView method creates the View wrootimpl, Then go to root.setView(View, wparams, panelParentView), update the interface through ViewRootImpl and complete the adding process of Window.

//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); / / create ViewRootImpl

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        //ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throwe; }}Copy the code

The setView uses requestLayout to complete the asynchronous refresh request, and also calls the checkThread method to check the validity of the thread.

@Override
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code

Therefore, our ViewRootImpl is created in a child thread, so the value of mThread is also a child thread. At the same time, our update is also in a child thread, so there is no exception. At the same time, we can refer to this article for analysis, write very detailed. Similarly, the following code verifies this case

// called in the child thread
public void showDialog(a){
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                // create Looper, MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run(a) {
                        builder = new AlertDialog.Builder(HandlerActivity.this);
                        builder.setTitle("jackie"); alertDialog = builder.create(); alertDialog.show(); alertDialog.hide(); }});// Start processing messages
                Looper.loop();
            }
        }).start();
    }
Copy the code

Call the showDialog method in the child thread, calling alertdialog.show () and alertdialog.hide (). The hide method hides the Dialog and does nothing else (it does not remove the Window). Then call alertdialog.show () on the main thread; Only the original thread that created a view Hierarchy can touch its views.

2020-11- 0918:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.jackie.testdialog, PID: 24819
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
        at android.view.View.requestLayout(View.java:24454)
        at android.view.View.setFlags(View.java:15187)
        at android.view.View.setVisibility(View.java:10836)
        at android.app.Dialog.show(Dialog.java:307)
        at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
        at android.view.View.performClick(View.java:7125)
        at android.view.View.performClickInternal(View.java:7102)
Copy the code

So the key to updating the UI in a thread is whether the ViewRootImpl that created it is the same thread as the checkThread.

How do I access the network from the main thread

Add the following code before the network request

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);
Copy the code

StrictMode has been introduced with Android2.3 to check for two major problems: ThreadPolicy and VmPolicy. StrictMode can be used to perform network operations on the main thread, which is not recommended. You can check out the rigor mode here.

Why does the system not recommend accessing 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, then why does the system not lock access to the UI controls? There are two disadvantages:

  1. Adding locks in the first place complicates the logic of UI access

  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. (Android development art exploration)

How a child thread notifies the main thread to update the UI (Handle sends a message to the main thread to manipulate the UI)
  1. 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.
  2. Use the Activity object’s runOnUiThread method.
  3. Create a Handler and pass in getMainLooper.
  4. The post (Runnable r).
Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?

As can be seen from the previous analysis of the main thread and child thread, the Looper will continuously retrieve messages in the thread. If it is a child thread’s Looper loop, once the task is completed, the user should manually exit, rather than let it sleep and wait. (Quote from Gityuan) A thread is simply a piece of executable code, and when the executable code completes, the thread’s 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. Android is based on message processing, and the user’s actions are all in this Looper loop, where we click on the screen during sleep to wake up the main thread and continue working.

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.

When the main thread Looper exits

When the App exits, the mH (Handler) in the ActivityThread receives the message and exits.

//ActivityThread.java
case EXIT_APPLICATION:
    if(mInitialApplication ! =null) {
        mInitialApplication.onTerminate();
    }
    Looper.myLooper().quit();
    break;
Copy the code

If you try to exit the main thread Looper manually, the following exception is thrown

 Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
    at android.os.MessageQueue.quit(MessageQueue.java:428)
    at android.os.Looper.quit(Looper.java:354)
    at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
    at android.app.Activity.performCreate(Activity.java:7802)
    at android.app.Activity.performCreate(Activity.java:7791)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
    at android.os.Handler.dispatchMessage(Handler.java:107) 
    at android.os.Looper.loop(Looper.java:214) 
    at android.app.ActivityThread.main(ActivityThread.java:7356) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Copy the code

Why is it not allowed to exit? Because the main thread is not allowed to exit, and exiting means the program is dead, and exiting shouldn’t be done that way.

Handler Specifies the order in which messages are processed

The following line of code is executed when Looper executes the message loop(), MSG. Targe is the Handler object

msg.target.dispatchMessage(msg);
Copy the code

Let’s look at the dispatchMessage source code

    public void dispatchMessage(@NonNull Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            // If callback processes the MSG and returns true, handleMessage is not called again
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code
  1. If the Message object has a CallBack, the CallBack is actually a Runnable, and the CallBack is executed.

    Message msgCallBack = Message.obtain(handler, new Runnable() {
        @Override
        public void run(a) {}});Copy the code

    The handleCallback method calls the Runnable run method.

    private static void handleCallback(Message message) {
        message.callback.run();
    }
    Copy the code
  2. If the Message object does not have a CallBack, the else branch checks whether the Handler’s CallBack is null, executes the handleMessage method of the CallBack, and returns the Handler’s CallBack as follows:

  3. Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
          	//retrun true does not perform the following logic, can be used to do priority processing
            return false; }};Copy the code
  4. Finally, we call Handler’s handleMessage() function, which we often override, to handle the message.

Usage scenarios

If a message is processed by Callback and then intercepted (returns true), then Handler’s handleMessage(MSG) method is not called. If a Callback processes a message but does not intercept it, it means that a message can be processed by both Callback and Handler. We can use the CallBack interception to intercept Handler messages.

Scenario: Hook ActivityThread.mh. In ActivityThread, there is a member variable, mH, which is a Handler and an extremely important class. Almost all plug-in frameworks use this method.

Handler.post(Runnable r) method execution logic

Post (Runnable r) {Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r

//Handler.java
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

The Runnable object is wrapped as a Message object, and the Runnable object is the Message’s CallBack object, which has precedence over execution.

How does Handler do thread switching

SendXXX, handler.postXXX and other methods to send messages, and then looper.loop () to retrieve messages in the message queue. Finally, the handle.dispatchMessage method is used for message distribution.

How do I handle memory leaks caused by improper use of handlers?
  1. Messages have delay in timely remove Message/Runnable interface was off, call handler. RemoveCallbacksAndMessages (null)

  2. Replace memory leaks caused by inner classes with static inner classes and use weak references to context or Activity/Fragment.

Specific analysis and resolution of memory leaks can be found in this article. There is also a key point, if there is a delay message, when the interface closed, the message in the Handler has not finished processing, then what is the final processing of the message? After testing, for example, I delayed sending a message 10 seconds after opening the interface, closed the interface, and eventually received the message in the handMessage method of Handler (created by the anonymous inner class) (printing the log). Because there is a MessageQueue -> Message -> Handler -> Activity reference chain, the Handler is not destroyed and the Activity is not destroyed.

Create the Message instance correctly
  1. Static methods via MessageMessage.obtain()To obtain;
  2. Public method via Handlerhandler.obtainMessage()

All messages are recycled and put into sPool using the meta-design pattern.

Handler In-depth problem solving

ThreadLocal

ThreadLocal provides each thread with a copy of a variable so that each thread is not accessing the same object at any one time, thus isolating multiple threads from sharing data.

If you’re designing a ThreadLocal, and the goal of a ThreadLocal is to have different variables V for different threads, the most straightforward way to do this is to create a Map where the Key is the thread and the Value is the variable V owned by each thread. A ThreadLocal holds such a Map internally. You might design it like this

The Java implementation also has a Map called ThreadLocalMap, but instead of ThreadLocal, ThreadLocalMap is held by Thread. The class Thread has a private property called threadLocals, which is of type ThreadLocalMap and whose Key is ThreadLocal.

The simplified code looks like this

class Thread {
  // Internal holds ThreadLocalMap
  ThreadLocal.ThreadLocalMap 
    threadLocals;
}
class ThreadLocal<T>{
  public T get(a) {
    // Get the thread held first
    //ThreadLocalMap
    ThreadLocalMap map =
      Thread.currentThread()
        .threadLocals;
    / / in ThreadLocalMap
    // Find variables
    Entry e = 
      map.getEntry(this);
    return e.value;  
  }
  static class ThreadLocalMap{
    // Inside is an array instead of a Map
    Entry[] table;
    // Find Entry based on ThreadLocal
    Entry getEntry(ThreadLocal key){
      // omit the lookup logic
    }
    / / Entry definition
    static class Entry extends
    WeakReference<ThreadLocal>{ Object value; }}}Copy the code

In the Java implementation, ThreadLocal is only a proxy tool class that does not hold any thread-related data. All thread-related data is stored in the Thread. In this design, ThreadLocalMap belongs to the Thread from the perspective of data kinship. So the get method for ThreadLocal is to get a unique ThreadLocalMap for each thread.

Another reason is that memory leaks are less likely. In our design, the Map held by ThreadLocal holds references to Thread objects, which means that Thread objects in the Map are never reclaimed as long as ThreadLocal objects exist. ThreadLocal lives tend to be longer than threads, so this design can easily lead to memory leaks.

In Java, threads hold ThreadLocalMap, and ThreadLocalMap references are weak references to ThreadLocal, so ThreadLocalMap can be reclaimed as long as Thread objects can be reclaimed. The Java implementation, while more complex, is more secure.

ThreadLocal and memory leaks

However, it’s not always perfect. Using ThreadLocal in a Thread pool can lead to memory leaks. Threads in a Thread pool live too long and often live with the program, which means that threadLocalMaps held by threads are never recycled. In addition, entries in a ThreadLocalMap are weak references to ThreadLocal, so ThreadLocal can be reclaimed as soon as it ends its life cycle. However, the Value in an Entry is strongly referenced by the Entry. Therefore, the Value cannot be reclaimed even after its life cycle ends, resulting in memory leakage.

So we can manually release resources through the try{}finally{} scheme

ExecutorService es;
ThreadLocal tl;
es.execute(()->{
  //ThreadLocal adds variables
  tl.set(obj);
  try {
    // Omit the business logic code
  }finally {
    // Manually clean ThreadLocaltl.remove(); }});Copy the code

The ThreadLocal content above is mainly referenced here.

Epoll mechanism

When the main thread MessageQueue has no message, it blocks in the nativePollOnce() method of the loop queue.next(), and finally calls epoll_wait() to block and wait. 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.

Select,poll, and epoll for IO multiplexing

  1. On the surface, epoll performs best, but select and poll may perform better than epoll in the case of a small number of connections and very active connections, since epoll’s notification mechanism requires many function callbacks.

  2. Select is inefficient because it requires polling every time. But inefficiencies are relative and can be improved by good design, depending on the situation.

The reason why WE chose epoll mechanism at the bottom of Handler is that Epoll is more efficient. In select/poll, the kernel scans all monitored file descriptors only after a certain method is called. Epoll registers a file descriptor through epoll_ctl(). Once a file descriptor is ready, the kernel uses a callback mechanism similar to callback. Activate this file descriptor quickly to be notified when the process calls epoll_wait(). The traversal file descriptor is removed here, and instead a mechanism that listens for callbacks. That’s the beauty of epoll.)

Handler synchronization barrier mechanism

What if there is an urgent Message that needs to be processed first? This actually involves the architectural aspects of the design, the design of general scenarios and special scenarios. You might think of sendMessageAtFrontOfQueue () this method, the actual well not only so, add a Handler synchronization barrier this mechanism, to implement the asynchronous message priority executive function.

PostSyncBarrier () sends synchronization barriers, and removeSyncBarrier() removes synchronization barriers

The synchronization barrier can be understood as intercepting synchronous Message execution. The main thread Looper will loop through MessageQueue’s next() to fetch the Message execution of the queue head, and then fetch the next Message when the Message execution is finished. When the next() method fetches a Message whose header is a synchronization barrier, it traverses the queue, looking only for messages with the asynchronous flag set. If it finds an asynchronous Message, it fetches the asynchronous Message and executes it, otherwise it blocks the next() method. If the next() method is blocked, the main thread is idle, that is, not doing anything. So, if the queue header is a synchronization barrier message, all synchronization messages behind it are blocked until the synchronization barrier message is removed from the queue, otherwise the main thread will not process the synchronization messages behind the synchronization screen.

All messages are synchronous by default, and are asynchronous only if the asynchronous flag is manually set. In addition, synchronization barrier messages can only be sent internally, and this interface is not exposed to us.

All the message-related code in Choreographer, as you will see, is manually set up to flag asynchronous messages, so these operations are not affected by synchronization barriers. The reason for doing this may be to ensure that the upper app can perform the work of traversing and drawing the View tree in the first time when it receives the screen refresh signal.

Actions during Choreographer are also asynchronous messages to ensure that Choreographer runs smoothly, It also ensures that doTraversal → performTraversals traversals will be executed at the first time. If there are other synchronization messages in this process, they will not be processed. After doTraversal.

For thread if there are too many messages to be performed, and the message is according to the time stamp for sorting, if do not add a synchronization barrier, then traverse map View tree might be forced to delay the work, because it also need to line up, then it is likely when the end of a frame is to screen data calculation, Even if the calculation is less than 16.6ms, it will also cause frame loss.

So, the synchronization barrier message control will ensure that every time the screen refresh signal is received the first time to traverse the View tree work?

Let’s just say that synchronization barriers are done as much as possible, but there is no guarantee that they will be handled first. Because the synchronization barrier is only sent to the message queue when scheduleTraversals() is called, that is, only when a View initiates a refresh request will any synchronization messages after that time be blocked. If the work is sent to the message queue before scheduleTraversals(), it will still be taken out and executed in sequence.

Here’s a partial breakdown:

WindowManager maintains the DecorView and ViewRootImpl for all activities. As we explained earlier, Windows ManagerGlobal initializes ViewRootImpl in its addView method and then calls its setView method, passing in the DecorView as an argument. So let’s see what viewrotimpl does, right

//ViewRootImpl.java
/ / the view is DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) { mView = view; ...// Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            requestLayout(); // Initiate a layout request... the assignParent (this); // Call DecorView assignParent with the current ViewRootImpl object this as an argument...}}}Copy the code

DecorView assignParent is called in the setView() method

//View.java
/* * Caller is responsible for calling requestLayout if necessary. * (This allows addViewInLayout to not request a new layout.) */
@UnsupportedAppUsage
void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent"); }}Copy the code

The argument is ViewParent, and ViewRootImpl implements the ViewParent interface, so here we bind the DecorView to ViewRootImpl. The root layout of each Activity is a DecorView, and the parent of a DecorView is a ViewRootImpl, so do something like invalidate() in the child View, loop through the parent and end up in the ViewRootImpl. So the View refresh is actually controlled by the View wrootimPL.

Even when a little View on the interface initiates a redraw request, you have to go all the way to ViewRootImpl, and it initiates the redraw request, and then it starts traversing the View tree until you get to the View that needs to be redrawn and then calls its onDraw() method to draw.

The invalidate () request to redraw operations is the last call to ViewRootImpl. ScheduleTraversals (), and ViewRootImpl. SetView () method call requestLayout methods

@Override
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code

Finally, the scheduleTraversals() method is called, which is the key to the screen refresh.

Start an Activity and bind its DecoView to a new ViewRootImpl object when its onCreate- onResume life cycle is complete. At the same time, start scheduling a traversal of the View (drawing the View tree) and wait to execute, then set the DecoView parent to the ViewRootImpl object. So we don’t get the View width and height in onCreate~onResume, and the interface is drawn after onResume. You can refer to my previous article analysis.

ViewRootImpl. ScheduleTraversals () a series of analysis and screen refresh mechanism can refer to this article, the content most refer to it, the analysis of the related content synchronization barrier also in it.

Choreographer is primarily about coordinating the timing of animation, input, and drawing. It receives timing pulses from the display subsystem (for example, vSYNC) and then schedules part of the rendering of the next frame.

By Choreographer. GetInstance (). PostFrameCallback () to listen on frame rate;

public class FPSFrameCallback implements Choreographer.FrameCallback {
    private static final String TAG = "FPS_TEST";
    private long mLastFrameTimeNanos;
    private long mFrameIntervalNanos;

    public FPSFrameCallback(long lastFrameTimeNanos) {
        mLastFrameTimeNanos = lastFrameTimeNanos;
        // Render time per frame is nanoseconds
        mFrameIntervalNanos = (long) (1000000000 / 60.0);
    }

    @Override
    public void doFrame(long frameTimeNanos) { //Vsync signal arrival time frameTimeNanos
        // Initialization time
        if (mLastFrameTimeNanos == 0) {
            // Render time of last frame
            mLastFrameTimeNanos = frameTimeNanos;
        }
        final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames > 5) {
                Log.d(TAG, "Skipped " + skippedFrames + " frames! "
                    + "The application may be doing too much work on its main thread.");
            }
        }
        mLastFrameTimeNanos = frameTimeNanos;
        // Register the next frame callback
        Choreographer.getInstance().postFrameCallback(this); }}Copy the code

The invocation method is registered with Application

Choreographer.getInstance().postFrameCallback(FPSFrameCallback(System.nanoTime()))
Copy the code

Causes of frame loss: There are generally two kinds of reasons for frame loss. One is that it takes more than 16.6ms to traverse the drawing View tree and calculate screen data. Second, the main thread has been processing other time-consuming messages, so that the work of traversing and drawing the View tree cannot start late, which exceeds the time of switching to the next frame at the bottom level of 16.6ms.

Handler lock related problems

Since there can be multiple handlers to add data to a MessageQueue (each Handler may be on a different thread when the message is sent), how is it internally thread-safe?

Handler.sendXXX, handler. postXXX will eventually be called to MessageQueue’s enqueueMessage method

The source code is as follows:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
		// Lock to ensure safety
    synchronized (this) {···}}Copy the code

Its internal synchronization keyword to ensure thread safety. At the same time, messagequeue.next() is also locked internally by synchronized to ensure thread safety at fetch time, and inserts are also locked. This problem is not difficult, just see if you know the source code.

A synchronization method in Handler

How do I get the handler.post message to execute after it has been executed and then proceed further, synchronizing the method runWithScissors

public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    if (r == null) {
        throw new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout must be non-negative");
    }

    if (Looper.myLooper() == mLooper) {
        r.run();
        return true;
    }

    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}
Copy the code

Some applications of Handler in systems and third-party frameworks

HandlerThread

HandlerThread inherits Thread. As the name implies, HandlerThread is actually a encapsulation of Handler and Thread, which has been encapsulated well and safely for us. Internal synchronization also ensures Thread safety, such as getLooper method

public Looper getLooper(a) {
        if(! isAlive()) {return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

Copy the code

In the thread’s run method, Looper can be created and assigned to mLooper only after the thread is started, and the block is waiting for the Looper to be created successfully. The Looper method was created successfully for external use. The Looper method was created successfully for external use.

IntentService

A quick look at the source code shows the Handler in action. The Handler’s handMessage eventually calls back to the onHandleIntent method.

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
Copy the code
How do you build a program that doesn’t crash

To create a program that doesn’t crash, refer to this article

Glide application

You can control the life cycle of a LiveData by adding an empty Fragment to an Activity or Fragment. The Fragment lifecycle is then managed through FragmentMannager to achieve lifecycle control. The following is an excerpt of the code to add Fragment Glide:

private RequestManagerFragment getRequestManagerFragment(
    @NonNull final android.app.FragmentManager fm,
    @Nullable android.app.Fragment parentHint,
    boolean isParentVisible) {
  Obtain a RequestManagerFragment from FragmentManager. If it has been added to FragmentManager, return the instance. Otherwise, empty
  RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    //2. If FM does not exist, fetch it from map cache
    current = pendingRequestManagerFragments.get(fm);
    if (current == null) {
      //3. If steps 1 and 2 are not displayed, the system is not created
      current = new RequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      if (isParentVisible) {
        current.getGlideLifecycle().onStart();
      }
      //4. Save the newly created fragment to the Map container
      pendingRequestManagerFragments.put(fm, current);
      //5. Send the event to add a Fragment transaction
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      //6. Send remove local cache eventhandler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
}

// The only difference is that this is the FragmentManager and this is the Acitivity FragmentManager
private SupportRequestManagerFragment getSupportRequestManagerFragment(
    @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
  SupportRequestManagerFragment current =
      (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    current = pendingSupportRequestManagerFragments.get(fm);
    if (current == null) {
      current = new SupportRequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      if(isParentVisible) { current.getGlideLifecycle().onStart(); } pendingSupportRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
}

@Override
public boolean handleMessage(Message message) {
  boolean handled = true;
  Object removed = null;
  Object key = null;
  switch (message.what) {
    case ID_REMOVE_FRAGMENT_MANAGER:
      // remove the cache
      android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
      key = fm;
      removed = pendingRequestManagerFragments.remove(fm);
      break;
  		// omit code...
  }
  // omit code...
  return handled;
}
Copy the code

If you look at the code above, you might be confused.

  • Why are fragments added to the FragmentManager stored in the Map container (step 4)?
  • Check whether the Fragment has been added from the map (step 2). The FragmentManager will find the Fragment.

The answer is simple: after step 5, you don’t add the Fragment to the FragmentManager (event queue). Instead, you send the event that adds the Fragment. Now let’s look at the code

//FragmentManagerImpl.java
void scheduleCommit(a) {
    synchronized (this) {
        booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
        booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
        if(postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); }}}Copy the code

Adding a Fragment ends up in the scheduleCommit method of FragmentManagerImpl, which we can see sends events through a Handler.

This explains why the Fragment is not added to the FragmentManager immediately after step 5, so the Map cache Fragment is needed to mark if any fragments are added. Step 6 then sends the message to remove the Map cache, because the Handler processes the messages in order.

conclusion

In fact, the analysis of the source code is not very careful, but from the whole system of various aspects of the use of different extensions, as well as some third-party framework use, I hope this article is helpful to you, like the point like it ~

Have you mastered the basic, intermediate and advanced methods of Activity?

Reference article:

Zhuanlan.51cto.com/art/202007/…

Blog.csdn.net/meegomeego/…

Juejin. Cn/post / 684490…

www.jianshu.com/p/dfd940e7f…

Time.geekbang.org/column/arti…

Xiaozhuanlan.com/topic/65243…

Juejin. Cn/post / 686972…