I’m a code porter. I can’t just carry it. I have to clean it up.

1. Handler components:

  1. Message: the Message
  2. Handler: Indicates the initiator of the message
  3. Looper: News traverser
  4. MessageQueue: MessageQueue

2. Process of using Handler:

There are three steps to prepare for using Handler:

  1. Call looper.prepare (). (This is not needed for the main thread because it was created in the main method when the APP was created.)

  2. Create a Handler object that overrides the handleMessage method (you don’t have to) to handleMessage callbacks

  3. Call stars. The loop ()

Among them:

Looper.prepare() plays the following roles:

  1. Create a Looper object

  2. Create a MessageQueue object and let the Looper object hold it

  3. Let the Looper object hold the current thread


    public static void prepare() {
        prepare(true); } private static void prepare(Boolean quitAllowed) {private static void prepare(Boolean quitAllowed) {private static void prepare(Boolean quitAllowed) {if(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } // If the current thread does not have a Looper, create one and store it in sthreadLocal. set(new Looper(quitAllowed)); }Copy the code

As you can see from the code above, a thread has at most one Looper object. When no Looper object is available, create a Looper and store it in sThreadLocal

Private Looper(Boolean quitAllowed) {// Create MessageQueue and allow Looper to hold mQueue = new MessageQueue(quitAllowed); // let Looper hold the currentThread object mThread = thread.currentthread (); }Copy the code

The MessageQueue MessageQueue is created and held by Looper. Since a thread can only have at most one Looper object, a thread can only have at most one MessageQueue. The current thread is then assigned to the mThread.

Handler uses the process:

  1. Handler.post(or sendMessage) : The Handler sends the message MSG

  2. MessageQueue. EnqueueMessage (MSG, uptimeMillis) : MSG to join the message queue

  3. The loop() method retrieves the MSG from MessageQue, then calls back to the handler’s dispatchMessage, and then executes the callback(if any) or handleMessage. (Note that the loop method is always looped, having been running since the previous handler preparation.)

As shown in the figure:

3. Handler specific source code:

3.1. The Message acquisition

There are two ways to get a Message:

1. Message msg = new Message(); 2. Message msg = Message.obtain(); Returns a new message instance from the global pool. Allows us to avoid assigning new objects in many cases. /** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objectsin many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if(sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clearin-use flag
                sPoolSize--;
                returnm; }}returnnew Message(); /** * Recycles a Message that may be used /** * Recycles a Message that may be usedin-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if(sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; After each message is used, it will call recycleUnchecked recycling message for easy use next timeCopy the code

The Message core contains these messages:

    public int what;

    public int arg1;

    public int arg2;

    public Object obj;

    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

Copy the code
3.2 Sending Messages by the Handler

The handler provides many methods for sending messages, which fall into two general categories:

  1. Handler.post(xxx);
  2. Handler.sendMessage(xxx);

There are many ways to do this, but they all come back to this method:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

The method is to add the message to the queue.

3.3 Adding Messages to MessageQueue

EnqueueMessage (add message)

Source code analysis 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.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false; } // mark that the Message is already in use msg.markinuse (); msg.when = when; // This is the first Message in the queue to be processed (the header of the current Message queue, possibly empty). boolean needWake; Use this logic if there are no messages in the queue or if the MSG needs to be executed immediately, or if the MSG delay is longer than the first message in the queueif(p = = null | | the when = = 0 | | the when < p.w hen) {/ / the message is inserted into the message queue of the head / / the latest news, if have been blocked, need to wake up the MSG. The next = p; mMessages = msg; needWake = mBlocked; }else{ needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // The following oneforThe purpose of the loop is to figure out where MSG should be placed // through the following oneforLoop, and eventually find out that the previous message of MSG is prev and the next message is Pfor (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false; }} // AboveforMSG should be after prev and before p MSG. Next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! = 0 because McOntract is false. // Wake up if the queue is blockedif(needWake) { nativeWake(mPtr); }}return true;
    }

Copy the code

The handler sends the message and adds it to the message queue.

Let’s start with what happens after the message is added to the queue: fetch the message and execute it.

3.4 Fetch message of Loop

As mentioned earlier, there are three steps to prepare before using Handler:

  1. Call looper.prepare (). (This is not needed for the main thread because it was created in the main method when the APP was created.)

  2. Create a Handler object that overrides the handleMessage method (you don’t have to) to handleMessage callbacks

  3. Call stars. The loop ()

Looper.loop(), the third step, is used to fetch messages from the MessageQueue queue, that is, the loop is started before the handler sends the message.

Loop () is a long source, here is the core:


	/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.Run the message queue in this thread. Make sure to call {@link#quit()} to close the loop.
     *
     */
    public static void loop() {......for(;;) Message MSG = queue.next(); // might blockif (msg == null) {
                // No message indicates that the message queue is quitting.
                return; } · · · · · · · · · · · · · · · try {/ / MSG. The target is the Message when creating the incoming Handler, Also is the sender sends the message handler / / so will eventually to callback handler dispatchMessage method MSG. Target. DispatchMessage (MSG); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally {if(traceTag ! = 0) { Trace.traceEnd(traceTag); }}...... / / recycle MSG, repeated use of MSG. RecycleUnchecked (); }}Copy the code

Loop () just keeps getting messages from MessageQueue and calling them back to dispatchMessage to see what’s going on in dispatchMessage


	/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if(msg.callback ! = null) { handleCallback(msg); }else {
            if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code

So eventually callback or handleMessage is executed.

4. Common interview questions

  1. In Android, what communication is based on Handler?

A: App running, UPDATING UI, AsyncTask, Glide, RxJava, etc

  1. Which thread processes Handler messages? Must it be the thread that creates the Handler?

A: The thread on which the Looper is created

  1. How are messages inserted into MessageQueue?

A: It is sorted according to when in ascending order in MessageQueue, when= number of milliseconds since startup + number of delay milliseconds

  1. When MessageQueue has no messages, its next method blocks, does that cause App ANR?

A: The ANR of the App does not result. It is guaranteed by The Pipe mechanism of Linux. When blocked, the thread is suspended. Wake up the thread as needed

  1. Can toasts be used in child threads?

A: Yes, but the Toast display is implemented based on Handler, so you need to create Looper first and then call looper.loop.

  1. Looper.loop() is an infinite loop. Can I stop it?

A: You can stop; Looper provides the quit and quitSafely methods

  1. How to solve the problem of Handler memory leakage?

A: + weak references, a static inner class Handler MessageQueue removeCallbacksAndMessages methods to remove the message

Here’s a look at each one:

4.1 Common Android Handler Usage
  1. Keep the App running

The entry to the App is actually the ActivityThread main method:

Public static void main(String[] args) {// Create the main thread Looper Looper. PrepareMainLooper (); ActivityThread thread = new ActivityThread(); thread.attach(false);

        if(sMainThreadHandler == null) {// Create a Handler for communication sMainThreadHandler = thread.gethandler (); } // execute the loop method looper.loop (); }Copy the code

As you can see, the main method has the following steps:

  1. A Looper in the main thread is created. When a Looper is created, a MessageQueue is created to hold messages.
  2. The ActivityThread object is created and its attach method is called, which creates the Application, calls the Application’s onCreate method, and tells ActivityManagerService that the App is now started.
  3. A Handler is created for communication, which is an H object.
  4. Call the looper. loop method to start the loop pulling messages from MessageQueue in the main thread for processing.

To review the schematic of the Handler mechanism:

As can be seen, after the App starts, the main method has not been executed completely because looper. loop is an infinite loop, that is to say, all the subsequent operations in the App take place in Looper.loop

Now, how does the Handler mechanism keep your App running? Let’s look at the definition of the Handler used for communication in ActivityThread:

private class H extends Handler { public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; public static final int STOP_ACTIVITY_SHOW = 103; public static final int STOP_ACTIVITY_HIDE = 104; String codeToString(int code) {if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; // omit some code}}return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) ! = 0, msg.arg2, (msg.arg1&2) ! = 0); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;
                case PAUSE_ACTIVITY_FINISHING:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) ! = 0, msg.arg2, (msg.arg1&1) ! = 0); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;
                case STOP_ACTIVITY_SHOW:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_HIDE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break; // omit some code}if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }


Copy the code

As you can see from the code, H inherits from Handler and defines many message types, such as start Activity, stop Activity, display Window, low battery, and it overrides the handleMessage method to handle these messages.

And where was the message sent from? Let’s take the onStop life cycle of an Activity that is called from a lock screen, in ApplicationThread, which is an inner class of ActivityThread. Let’s take the start Activity message as an example. ActivityManagerService first calls the scheduleStopActivity method of ApplicationThread with binder. This method is executed in the Bindler thread pool of our App. Let’s see how it switches to the main thread to start the Activity.

    public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

Copy the code

The sendMessage method is called. The first argument is the message type defined in H above. Then look at the sendMessage method, which ends up calling the following multi-parameter constructor:

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + "" + mH.codeToString(what)
            + ":" + arg1 + "/" + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

Copy the code

MH is a member variable of type H defined in ActivityThread as follows:

 final H mH = new H();
 
Copy the code

So, to call the lock screen, call the Activity’s onStop method, as follows:

  1. ActivityManagerService invokes ApplicationThread’s scheduleStopActivity method using binder
  2. The scheduleStopActivity method of ApplicationThread adds messages to the MessageQueue of the main thread via H
  3. The main thread Looper iterates through the MessageQueue and calls H’s handleMessage method when it gets the message
  4. In H’s handleMessage, call the Activity’s onStop method

The flow chart is as follows:

So, this is how our App works, the App works by the Handler mechanism, and when the main thread when the MessageQueue gets a message, The looper. loop of the main thread takes messages out of the MessageQueue of the main thread to process (for example, the onCreate of the Activity is actually a message taken out of the MessageQueue), thus ensuring that the App runs

When MessageQueue does not have a message, MessageQueue’s next method blocks the current thread, causing it to hang until it has a message. ApplicationThread then uses the H object in the ActivityThread to send a message to the main thread’s MessageQueue and wake up the main thread if it is found to be suspended.

So, when there’s no message, the main thread of our App is suspended. The main thread wakes up when a message arrives (lock screen, click, etc.), so the Handler mechanism keeps the App running.

4.2 Handler Updates the UI

We know that updating the UI directly from a child thread raises an exception like this:

We can use handlers to update the UI in child threads in one of the following ways:

  1. Handler sendMessage method
  2. Handler Post mode
  3. The Activity’s runOnUiThread method
  4. Post mode of View

2.1 sendMessage method of Handler

	final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("Handler. SendMessage way"); }}; new Thread(newRunnable() {
            @Override
            public void run() {// Use the sendMessage method Message MSG = message.obtain (); msg.what = 100; handler.sendMessage(msg); } }).start();Copy the code

In this way, in the child thread, the hander created in the main thread calls sendMessage to send a Message, and the Message is added to the MessageQueue of the main thread. When the Looper of the main thread retrieves the Message from the MessageQueue, It calls the handleMessage method of the target of this Message, which is actually the handler we’re sending the Message to, which calls the handleMessage method we overwrote.

Message: Handler. SendMessage (Message) to handle the Message: the Message. The target. The handleMessage (including target is the Message Handler)

2.2 Post Methods of the Handler

	final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("Handler. SendMessage way"); }}; new Thread(newRunnable() {
            @Override
            public void run() {// Use post handler.post(new)Runnable() {
                    @Override
                    public void run() {
                        btn.setText("Handler. Post way"); }}); } }).start();Copy the code

The UI can also be updated in child threads using the Handler’s POST method, which requires passing in a Runnalbe object. Let’s take a look at the source of the POST method

    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
Copy the code

As you can see, getPostMessage is called to build a Message object, and sendMessageDelayed is called as well, so let’s just focus on how to build the Message object, Look at the getPostMessage method.

private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); // The post method passes the Runnable object as the Message callback m.callback = r;return m;
    }
Copy the code

It’s as simple as creating a Message as a callback to a Message from the parameters passed in to the POST method.

Let’s revisit taking messages out of MessageQueue and processing them by Handler to the dispatchMessage method

Public void dispatchMessage(Message MSG) {// Callback is the post method passed to the Runnable objectif(msg.callback ! = null) { handleCallback(msg); }else{// will not be executed to this pointif(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }} private static void handleCallback(message.callback.run();}} private static void handleCallback(message.callback.run); }Copy the code

If MessageQueue is not null, MessageQueue is not null. If MessageQueue is null, MessageQueue is null. If MessageQueue is null, MessageQueue is null. Call its run method, which is the run method of the Runnable object passed in by the POST method, without calling Hander’s handleMessage method.

Send messages: handler. post(Runnable) Handle messages: message.callback. run(callback passes Runnable for calling the POST method)

2.3 The Activity’s runOnUiThread method

	new Thread(new Runnable() {
            @Override
            public void run() {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("Activity's runOnUiThread method"); }}); } }).start();Copy the code

If we can get the Activity object in the child thread, we can call its runOnUiThread method to update the UI. Let’s look at the Activity’s runOnUiThread source code.

Public final void runOnUiThread(Runnable Action) {// If it is not a UI thread, the Handler post is calledif(Thread.currentThread() ! = mUiThread) { mHandler.post(action); }else{// If it is a UI thread, call the Runnable's run method action.run() directly; }}Copy the code

If it is a UI thread, the run method of the passed Runnable object is called directly. Let’s focus on non-UI thread logic.

The Activity’s runOnUiThread method is based on the post method of the Handler. The logic behind this is to wrap the incoming Runnable as a Message and send it. I won’t repeat it.

Let’s look at the definition of the mHandler, which is essentially the Activity’s member property

	final Handler mHandler = new Handler();
Copy the code

The Activity is created in the main thread, so this Handler is also created in the main thread and holds a Looper for the main thread. The message sent by this Handler calling the POST method is added to the main thread’s MessageQueue, thus completing the communication between the child thread and the main thread.

Send messages: activity.runonuithRead (Runnable) Process messages: message.callback. run(callback is the Runnable passed in by the runOnUiThread method)

2.4 View post method

	new Thread(new Runnable() {
            @Override
            public void run() {// call the View's post method btn.post(new)Runnable() {
                    @Override
                    public void run() {
                        btn.setText("The View. Post way"); }}); } }).start();Copy the code

So let’s go straight to the View’s post method

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if(attachInfo ! = null) {return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }
Copy the code

You can see that this is also the post method of the Handler called, similar to activity.runonuithRead.

Send messages: view. post(Runnable) Process messages: message.callback. run(callback is the Runnable object passed in by the POST method)

To sum up:

  1. Handler. SendMessage: add a Message to the main thread of the MessageQueue, which in the main thread from the MessageQueue, invoke Message. Target. HandleMessage method
  2. Handler.post: Adds the Message to the main thread’s MessageQueue based on handler. sendMessage. Looper in the main thread retrieves the Message from the MessageQueue and calls message.callback. run
  3. Activity. The runOnUiThread: based on the Handler. Post
  4. The post: based on the Handler. Post

So, all of the ways that the child threads update the main thread’S UI are dependent on the Handler mechanism.

  1. AsyncTask

We use AsyncTask when we want to do a time-consuming task in a child thread. For example, we create a custom MyAsyncTask in a child thread and execute it, and simulate time-consuming operations in a doInBackground thread:

public class AsyncTaskActivity extends AppCompatActivity {
    private static final String TAG = "AsyncTaskActivity";
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask); btn = (Button) findViewById(R.id.btn); // Start a child Thread to execute the asynchronous task new Thread(new)Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run, thread name:"+ Thread.currentThread().getName()); MyAsyncTask asyncTask = new MyAsyncTask(); asyncTask.execute(); } }).start(); } class MyAsyncTask extends AsyncTask<Void, Integer, Void>{@override protected VoiddoInBackground(Void... voids) {
            Log.e(TAG, "doInBackground, thread name:"+ Thread.currentThread().getName()); // Simulate time-consuming tasksfor (int i = 0; i < 5; i ++) {
                SystemClock.sleep(1000);
                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            Log.e(TAG, "onPreExecute, thread name:"  + Thread.currentThread().getName());
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            Log.e(TAG, "onPostExecute, thread name:"  + Thread.currentThread().getName());
            btn.setText("Done!);
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.e(TAG, "onProgressUpdate, thread name:"+ Thread.currentThread().getName()); }}}Copy the code

  1. OnPreExecute: thread-4 is the Thread that calls the execute method of AsyncTask
  2. DoInBackground: AsyncTask #1, which is actually a thread in the AsyncTask thread pool
  3. OnProgressUpdate: main, the main thread
  4. OnPostExecute: main, the main thread

OnPreExecute, this is pretty easy to implement, you don’t have to switch threads, you just call back; The doInBackground method can be executed directly from the thread pool maintained by AsyncTask. We’ll focus on how the onProgressUpdate and onPostExecute methods switch from child threads to main threads.

We can see such an inner class in the AsyncTask source code

private static class InternalHandler extends Handler {
        public InternalHandler() {// Create Handler super(looper.getMainLooper ()) with the main thread Looper; } @SuppressWarnings({"unchecked"."RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<? > result = (AsyncTaskResult<? >) msg.obj; switch (msg.what) {caseOnPostExecute result.mtask. finish(result.mdata [0]);break;
                caseMESSAGE_POST_PROGRESS: / / call onProgressUpdate result. MTask. OnProgressUpdate (result. MData);break; }}}Copy the code

We can see from the handleMessage method that this switch to the main thread is also implemented using the Handler mechanism, but why can we create asyncTasks in any thread and then call back both methods in the main thread? So when we create InternalHandler, we use the main thread Looper, so that when we use InternalHandler to send a message, the message will be added to the main thread Looper’s MessageQueue, so when the main thread Looper takes out the message processing, InternalHandler’s handleMessage method is called back in the main thread.

Therefore, AsyncTask is implemented based on the thread pool +Handler mechanism.

  1. Other places where handlers are used

  2. RxJava: The child thread switches to the main thread to perform the observer’s callback method (RxJava is unfamiliar to me)

  3. Glide: Echo after the image is ready

  4. LocalBroadcastManager: traditional broadcastmanager is implemented with binder, while LocalBroadcastManager is implemented with Handler

There are a lot of other ways to use the handler mechanism, so I’m not going to give you an example, but remember that the handler mechanism is important.

4.3 Which thread processes Handler messages? Must it be the thread that creates the Handler?

We used to think that the thread that processes the message is the thread that creates the Handler, but from our analysis in the last article, we know that this is not accurate (because of the default Looper that we typically use to create handlers).

The thread that processes the message is actually the thread that sends the Looper held by the handler.

It’s easy to understand how this works. We know how this Handler works

As in the AsyncTask example above, even if we create an AsyncTask on a child thread, as long as we pass the Looper of the main thread through looper.getMainLooper () when we create the Handler, The message is added to the main thread corresponding to the MessageQueue, where the message is processed.

Below, we create a handler in the child thread and send the message in the main thread. Since the handler is created using a Looper in the child thread, the message is processed in the main thread. The code is as follows:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh); btn = (Button) findViewById(R.id.btn); // Create Handler new Thread(new)Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: "+ Thread.currentThread().getName()); }}; Looper.loop(); } }).start(); btn.setOnClickListener(new View.OnClickListener() {@ Override public void onClick (View v) {/ / send a message mHandler sendEmptyMessage (100); }}); }}Copy the code

Log output, you can see that the message is processed in the child thread:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh); btn = (Button) findViewById(R.id.btn); // Create Handler new Thread(new)Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: "+ Thread.currentThread().getName()); Looper.prepare(); MHandler = new Handler(looper.getMainLooper ()){@override public void handleMessage(Message) msg) { super.handleMessage(msg); Log.e(TAG,"handleMessage, thread name: "+ Thread.currentThread().getName()); }}; Looper.loop(); } }).start(); btn.setOnClickListener(new View.OnClickListener() {@ Override public void onClick (View v) {/ / send a message mHandler sendEmptyMessage (100); }}); }}Copy the code

As you can see, after creating a Handler that uses the main thread’s Looper, the message is actually processed in the main thread:

4.4 How to Insert it into MessageQueue?

We said earlier that all handler.post and handler.sendMessage call handler’s sendMessageDelayed method as follows:

    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
Copy the code

SendMessageAtTime (Message), systemclock. uptimeMillis() + delayMillis, where delayMillis is the delay time, The unit is millisecond. Systemclock. uptimeMillis() indicates the time (excluding sleep time) between startup and now, in milliseconds. The second parameter mainly determines the order of the Message in MessageQueue. For example, if the boot time is 100s and a Message is sent with a delay of 20s, the sum of the two is 120s. After 5 seconds, sent a delay 5s message, then the two drink 105+5 = 110s.

SendMessageAtTime finally calls MessageQueue’s enqueueMessage method. Let’s look at this method:

Boolean enqueueMessage(Message MSG, long when) {synchronized (this) {// when = start time + delay time MSG. Message p = mMessages; boolean needWake; // If the current message queue is empty or the current when is less than the queue header when // the message is inserted into the queue headerif (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else{ needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // In an infinite loop, insert the Message into MessageQueue at the appropriate location according to whenfor(;;) { prev = p; p = p.next; // find the first message.when Message that is larger than the current message.when Messageif (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false; }} // Insert the current Message into the appropriate location of MessageQueue msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! = 0 because mQuitting is false.if(needWake) {// If you need to wake up, call nativeWake to wake up the processing thread nativeWake(mPtr); }}return true;
    }
Copy the code

From the above code, it is easy to see that messages are queued in MessageQueue according to when, which is the time from startup to present + delay time.

For example, suppose that the boot time is 100s, and there is no message in MessageQueue, a message delayed 20s is sent at this time, that is, when is 120000, then the message situation in MessageQueue is shown as follows:

4.5 Will MessageQueue’s next cause ANR of App

We know that the Activity ANR if 5s events do not correspond to the user’s request. Let’s review the looper. loop method:

public static void loop() { final Looper me = myLooper(); MessageQueue final MessageQueue queue = me.mqueue; / / death cyclefor(;;) Message MSG = queue.next(); // might blockif (msg == null) {
                // No message indicates that the message queue is quitting.
                return; } / / the message to distribute MSG. Target. DispatchMessage (MSG); // omit irrelevant code}}Copy the code

Ahead about stars. The loop method to maintain the App running, it is used in an infinite loop, we App normal operation (the Activity’s lifecycle, click event, etc.) are invoked the MSG. Target. DispatchMessage (MSG) of the message processing. But if MessageQueue’s next method blocks when there is no message in the MessageQueue, will it cause the App to apply ANR?

Let’s look at MessageQueue’s Next method

Message next() { int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; / / death cyclefor(;;) {// Block MessageQueue nativePollOnce(PTR, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Returnif found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {if(now < msg.when) {// If the message is delayed, NextPollTimeoutMillis nextPollTimeoutMillis = (int) math.min (MSG. When - now, integer.max_value); nextPollTimeoutMillis = (int) math.min (MSG. }else{// If the display is not delayed, the message is returned directly for processing mBlocked =false;
                        if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        returnmsg; }}else{// If there is no message, set nextPollTimeoutMillis to -1 and block MessageQueue nextPollTimeoutMillis = -1; }if(pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // This is generally trueifCondition, and mBlocked is then set totrueAnd continue tocontinue
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue; } pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; }}Copy the code

In the next method, first set up an infinite loop, and then call the nativePollOnce(PTR, nextPollTimeoutMillis) method, which is a native method that blocks MessageQueue, Focus on its second argument, nextPollTimeoutMillis, which has three possibilities:

  1. If nextPollTimeoutMillis=-1, the block does not time out.
  2. If nextPollTimeoutMillis=0, it does not block and returns immediately.
  3. If nextPollTimeoutMillis>0, block nextPollTimeoutMillis for up to milliseconds (timeout), and return immediately if any program wakes up in between.

So let’s go ahead and start with nextPollTimeoutMillis zero, which means it’s not blocking, and then go down, and there are three scenarios

  1. NextPollTimeoutMillis, the message is not returned, but is continued down, set mBlocked to true to indicate that the message queue is blocked, and continue to execute the for body. Executing the nativePollOnce method again with nextPollTimeoutMillis>0 will cause MessageQueue to sleep nextPollTimeoutMillis for milliseconds and should then go to case 2.
  2. If the message is not delayed, mBlocked is set to false, indicating that the message queue is not blocked and the message is directly returned and removed from the queue.
  3. If the message is empty, nextPollTimeoutMillis is called at -1. Further down, mBlocked is set to true to indicate that the message queue is blocked, and continue continues the for loop. In this case, the call to nativePollOnce will always block without timeout.

When the message queue is empty, the local method nativePollOnce is called, and the second parameter is -1. This will cause the current thread to block and will not time out, so no ANR will occur. The consumption of CPU and other resources is very low. The specific principle can be consulted by yourself.

When does a thread wake up after it’s blocked? Remember earlier when we said that messages join the logic of MessageQueue? Let’s review the enqueueMessage flow again:

boolean enqueueMessage(Message msg, long when) {

        synchronized (this) {

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue ifblocked. msg.next = p; mMessages = msg; // If MessageQueue is empty needWake = mBlocked; }else{// If there is a message in MessageQueue, needWake = mBlocked && p.target == null && msg.isasynchronous (); Message prev;for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // If you need to wake up the current thread, call the nativeWake methodif(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

When the message is added to MessageQueue, it will determine whether the current MessageQueue is blocked. If it is blocked, the nativeWake method needs to be called to wake up the thread. This block is set in the next method of MessageQueue mentioned earlier when MessageQueue has no message or the message is delayed. So MessageQueue’s next method might block the thread, but not cause ANR.

  1. When the MessageQueue has no message, calling nativePollOnce in the next method causes the thread to block until nativeWake is called when a new message is added to the MesssageQueue
  2. When MessageQueue has a message and the queue header message is delayed, the next method calls nativePollOnce causing the thread to block nextPollTimeoutMillis. NativeWake can wake up the thread when a new message is added to MessageQueue, or it can wait for nextPollTimeoutMillis to wake up the thread automatically
4.6 Child Threads Use Toast

Sometimes, we need to pop up Toast directly in the child thread to prompt some information, like this:

public class ToastActivity extends AppCompatActivity {
    private static final String TAG = "ToastActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toast);
        new Thread(new Runnable() {
            @Override
            public void runMakeText (toastActivity.this,"Hint!, Toast.LENGTH_LONG).show(); } }).start(); }}Copy the code

When you run the program, it crashes. Is it because child threads can’t update the UI? It’s not that it doesn’t.

  1. Toast belongs to the system Window and is not restricted by child threads updating the UI.
  2. In onCreate, the child thread might be able to update the UI, because the check that the child thread can’t update the UI is done by the checkThread in the ViewRootImpl, whereas in onCreate, the ViewRootImpl hasn’t been created yet, so it won’t be checked.

And since that’s not the case, let’s look at the error log

new Thread(new Runnable() {
            @Override
            public void run() { Looper.prepare(); MakeText (toastactivity. this,"Hint!, Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();

Copy the code

TN is an IBinder implementation class. TN is an IBinder implementation class. TN is an IBinder implementation class.

private static class TN extends ITransientNotification.Stub {
       final Runnable mShow = new Runnable() {
            @Override
            public void run() {// call handleShow, handle display logic handleShow(); } } final Handler mHandler = new Handler(); // omit irrelevant code WindowManager mWM;TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: "+ this); // Binder thread pool with handler switch to Looper thread mhandler. post(mShow); } // Toast real display public voidhandleShow() {
            if(mView ! = mNextView) { mView = mNextView; // addView to Window mwm. addView(mView, mParams); }} // omit irrelevant code}Copy the code

WindowManagerService takes the TN object and calls its show method, but this is executed in the Binder thread pool. Handler is used to switch to the thread where the toast. show method is called. Handler is used in this case. The final call is the handleShow method, which loads the View onto the Window.

To summarize the Handler:

  1. Toast is implemented by the system Window
  2. The Toast display uses IPC
  3. The Toast display uses the Handler mechanism
  4. Child threads can use Toast, but need to use the Handler routine
4.7 Can looper. loop be stopped?

The loop method is an infinite loop, and since the code is executed sequentially, the code after it cannot be executed, as follows:

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper); btn = (Button) findViewById(R.id.btn); // Start a child Thread to execute the asynchronous task new Thread(new)Runnable() {
            @Override
            public void run() {
               
                Looper.prepare();
                mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); }}; Log.e(TAG,"Stars. Before the loop"); // the looper.loop method is an infinite loop looper.loop (); // Log. E (TAG,"Stars. After the loop"); } }).start(); }}Copy the code

Log will print only the code before the loop method. The code after the loop will not be executed:

Quit (quit); quit (quitSafely); quit (quit); quit (quit)

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper); btn = (Button) findViewById(R.id.btn); // Start a child Thread to execute the asynchronous task new Thread(new)Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); }}; Log.e(TAG,"Stars. Before the loop"); // the looper.loop method is an infinite loop looper.loop (); // Log. E (TAG,"Stars. After the loop" );
            }
        }).start();
        btn.setOnClickListener(new View.OnClickListener() {@override public void onClick(View v) {// Call the quit method to stop Looper mhandler.getLooper ().quit(); }}); }}Copy the code

As before, the method after looper. loop will not be executed. When we click the button, Looper will stop and the code after looper. loop will be executed as follows:

    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
Copy the code

Both methods call MessageQueue’s quit method, but the parameters passed in are different. Let’s look at MessageQueue’s quit method:

    void quit(boolean safe) {
        synchronized (this) {
            if (mQuitting) {
                return; } // MessageQueue is stopping, which is used by the next method to exit the loop mstandard =true;

            if(safe) {/ / delete the MessageQueue delay message removeAllFutureMessagesLocked (); }else{// Remove all messages from MessageQueue removeAllMessagesLocked(); } // We can assume mPtr ! = 0 because mQuitting was previously false. nativeWake(mPtr); }}Copy the code

Set mquit to true, which is used to quit the loop in MessageQueue’s next method, and then use safe to check the logic. This is the difference between Looper’s quit and quitSafely

  1. Quit: Delete all messages from MesageQueue
  2. QuitSafely: Delete the delayed message in MessageQueue

Moving on to the effects of McOntract on MessageQueue’s Next method, let’s go back to the next method and just look at the key code:

    Message next() {
        for (;;) {
            if(nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {if(msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }if(msg ! = null) {if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        returnmsg; }}else{ // No more messages. nextPollTimeoutMillis = -1; } // Determine mspapersif (mQuitting) {
                    dispose();
                    returnnull; }}}}Copy the code

To get to the last part, check the McOntract that must be signed before, we set this property to true in the quit method of MessageQueue, which affects this. When the conditions are met, the Dispose method is called and NULL is returned.

Let’s look at the Dispose method first

    private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }
Copy the code

This is done by calling the nativeDestroy method, which is a native method that stops MessageQueue at the bottom.

Here we just stopped the loop in MessageQueue’s next, and the loop in looper. loop is still there. Let’s continue with the looper. loop method.

    public static void loop() {
        final Looper me = myLooper();
       
        final MessageQueue queue = me.mQueue;

        for(;;) {// When mspapers istrueQueue.next () returns null Message MSG = queue.next(); // might blockif(MSG == null) {// DirectreturnTo exit the loopreturn; } // omit irrelevant code}}Copy the code

The quit/quitSafely call to the quit/quitSafely command sets the mwemspapers on the current thread to true, causing MessageQueue’s next to return null, and then returns the user’s last call to quit/quitSafely. Exiting the loop loop completes the logic for stopping Looper.

4.8 Handler memory Leaks

We usually use handler to communicate in the following way

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler); // Anonymous inner class mHandler = newHandler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // Process messages}}; btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() {@ Override public void onClick (View v) {/ / mHandler. The delay of the 100 s messages sent sendEmptyMessageDelayed (100, 100 * 1000); }}); } @Override protected voidonDestroy() { super.onDestroy(); / / memory leak detection using leakcanary do RefWatcher RefWatcher = MyApplication. GetRefWatcher (this);if(refWatcher ! = null) { refWatcher.watch(this); }}}Copy the code

There is a problem, however, when we enter the page and click the button, send a message with a delay of 100s, and then exit the Activity, which may cause a memory leak.

The root cause is that the anonymous inner class Handler object we created holds the object of the external class Activity. We know that when we use Handler to send messages, the Handler will be saved as the Message target to MessageQueue. Because of the delay of 100s, Therefore, the Message is not processed for the time being, and their reference relationship is that MessageQueue holds Message, Message holds Handler, and Handler holds Activity, as shown in the figure below

So how do you fix Handler leaks? There are two main ways:

  1. Static inner class + weak reference
  2. Remove messages from MessageQueue
4.8.1 Static inner Class + Weak Reference

We know that a static inner class does not reference an object from an outer class. But how can we call an Activity method from an outer class since a static inner class does not hold an object from an outer class? The answer is to use weak references. The code is as follows:

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler); MHandler = new MyHandler(handlerActivity.this); btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() {@ Override public void onClick (View v) {/ / mHandler. The delay of the 100 s messages sent sendEmptyMessageDelayed (100, 100 * 1000); }}); } static class MyHandler extends Handler {private WeakReference<Activity> activityWeakReference; public MyHandler(Activity activity) { activityWeakReference = new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); // Process the messageif(activityWeakReference ! = null) { Activity activity = activityWeakReference.get(); // After you get the activity object, call the activity methodif(activity ! = null) { } } } } }Copy the code

First, we define a static inner class MyHandler. Then when we create MyHandler, we pass in the object of the current Activity, which is held by the Hander as a weak application. At this point, the Activity is referenced by both strong and weak references. We continue to send a message with a delay of 100s and exit the current Activity. At this point, there are no strong references to the Activity, only weak references, and the GC runs to reclaim the Activity with weak references so that there is no memory leak.

However, the delayed Message still exists in the MessageQueue. When the Message is retrieved, it will still be distributed, but at this time, the Activity is recycled, the Activity is null, and the Activity method can no longer be called. So, the Activity can be recycled, but neither Handler nor Message can be recycled.

As for why weak references are used and not soft references, it is actually very simple, comparing the two reclamation conditions is clear

  1. WeakReference: when gc runs, only weakly referenced objects are reclaimed, regardless of whether there is sufficient memory
  2. SoftReference: when gc runs, only SoftReference objects are reclaimed if memory is low

Obviously, when our Activity exits, we want the Activity object to be reclaimed regardless of whether there is enough memory, so using weak references is appropriate.

4.8.2 Removing messages from MessageQueue

MessageQueue (MessageQueue, MessageQueue, MessageQueue, MessageQueue, MessageQueue, MessageQueue, MessageQueue)

Handler provides us with removeCallbacksAndMessages method is used to remove the message, for example, in the Activity onDestroy call Handler removeCallbacksAndMessages, code is as follows:

    @Override
    protected void onDestroy() { super.onDestroy(); / / remove the MessageQueue target for the Message of the mHandler mHandler. RemoveCallbacksAndMessages (null); }Copy the code

Is actually the Activity onDestroy method invokes the mHandler. RemoveCallbacksAndMessages (null), thus removing the MessageQueue target for the mHandler Message, Since MessageQueue no longer refers to the Message sent by the Handler, the Message, Handler, and Activity are all recoverable when the Activity exits, thus fixing the memory leak.

5. At the end

Handler knowledge is not much, but the details are very much, once a long time not to see will forget. So, whether it’s written by someone else or by yourself, jot it down and come back the next time you forget.

6. Refer to the article

  1. www.jianshu.com/p/592fb6bb6…
  2. www.jianshu.com/p/9631eebad…
  3. www.jianshu.com/p/67eb02c8b…