Handler Messaging Mechanism Analysis

Keywords: Mobile Android Java

Use and usage of Handler

Most people who have written Android programs will encounter ANR (Application Not Responding).If the program does not respond for a period of time, a dialog box will pop up to let the user choose whether to continue waiting or force the application to close.To avoid ANR, we need to put time-consuming logic into a background thread to execute.But background threads cannot update the interface.So how do I update the interface based on the results when the task is completed?Handler can do this.The following example shows the use of Handler:

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
                handler.sendMessage(message);
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                textView.setText(text);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        backgroundThread.start();
    }
}

Why can handleMessage() update the interface?Because handlerMessage() is called in the main thread.There is an infinite loop in the main thread that constantly assigns messages to Handler processing.The object that executes this infinite loop is Looper.

Looper and MesageQueue

Looper is the main loop for processing messages, and all messages for Activity are dispatched by Looper.The code below is intercepted from ActivityThread.main(), from which you can see that the main loop for Android applications is Looper.loop().

public static void main(String[] args) {

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

The code below is intercepted from Looper.java, with some functions deleted.You can see that each thread has a Looper object whose method looper() is the main loop in the thread that processes messages.Looper() keeps getting messages from MessageQueue and delivering them to Message.target.dispatchMessage().Message.target is a Handler, and handleMessage() is called in the dispatchMessage () method.

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
        sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

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

        for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                        return;
                }

                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                msg.recycleUnchecked();
        }
}

Looper gets the message through the MessageQueue.next() method, which is a blocking method with the following general flow:

Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
                nativePollOnce(ptr, nextPollTimeoutMillis);

                long now = SystemClock.uptimeMillis();
                Message msg = get_next_message();

                if (now < msg.when) {
                        nextPollTimeoutMillis = msg.when - now;
                } else {
                        return msg;
                }

                nextPollTimeoutMillis = 0;
        }
}

nativePollOnce() is a local method that acts roughly like sleep().Unlike sleep(), nativePollOnce() returns immediately if a new message is received before nextPollTimeMills.This is achieved through an internal call to the epoll system call.

Some readers may wonder how the Message is sent through Handler.sendMessage(), and how does the Message object get passed to Looper.mQueue?In Handler's constructor, if no Looper object is passed in, Handler saves the current thread's Looper object and Looper's mQueue members.In the Handler.sendMessage() method, the Message is passed to the MessageQueue.

void sendMessage(Message message) {
        enqueueMessage(queue, message);
}

void enqueueMessage(MessageQueue queue, Message message) {
        queue.enqueueMessage(message);
}

Handler Memory Leak

As mentioned earlier, Message.target is a Handler object.If this handler is defined as an internal class of an Activity (as is the case in the first example in this article), when the Activity exits, if there is also a Message object in the Looper's message object, then Message.target will hold a reference to the Activity (through the internal class) and the Activity will not be recycled, which is the so-called Handler memory leak problem.There are two things you need to do to solve this problem. One is to define Handler as either a static internal class or a non-internal class.The second is to empty the message queue when exiting the Activity.The following examples illustrate these two points.

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activity;

        public MyHandler(MainActivity aActivity) {
            activity = new WeakReference<>(aActivity);
        }

        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                MainActivity mainActivity = activity.get();
                if (mainActivity != null) {
                    mainActivity.textView.setText(text);
                }
            }
        }
    };

    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new MyHandler(this);
        backgroundThread.start();
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
    }
}

Posted by big_c147 on Thu, 14 Nov 2019 12:38:52 -0800