The relationship among Handler, MessageQueue and Looper in Android and then write a handler of your own

Keywords: Java

The division of labour among Handler, Looper and MessgaeQueue:

  • handler is responsible for sending messages
  • Looper is responsible for polling messages in MessageQueue and sending them back to handler
  • MessageQueue is responsible for storing messages (FIFO)

Reference Relations among Handler, Looper and MessgaeQueue

  • There are MessageQueue objects, Looper objects in Handler
  • MessageQueue objects in Looper (the same object as in Handler)
  • Message in MessageQueue (and Handler in Message)

Illustrate the relationship between the three:

Now we receive a message from creating Handler to handler Messge explaining the whole process:

First: Create a Handler, what do you do? Handler handler = new Handler();

public Handler(Callback callback, boolean async) {

        //Some source codes are omitted.

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
}
  • One important operation, Looper.myLooper(), is to get the Looper object bound by the current thread. Why? That's analyzed through Looper's source code.
  • mLooper.mQueue Gets Message Queue

2: Looper.myLooper(), which retrieves the current thread-bound Looper object

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

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
  • sThreadLocal: A ThreadLocal Map (similar to HashMap) is maintained internally and stored as a key value
  • key: The thread currently creating the Looper object
  • value: Looper object
  • The Looper object retrieved here may be an empty object, because we first call Looper.prepare() to initialize it.

Looper.prepare(): Create a Looper object for the current thread

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));
}
  • SThreadLocal. set (new Looper (quitAllowed): Here we create a Looper object and bind it to the current thread
  • Emphasis: Looper.prepare() must be invoked to initialize the first time it is used

Where is the initialization of MessageQueue? It's in Looper's constructor, of course.

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

So far, the creation of all three objects has been completed. It is said that the Looper.prepare() method must be invoked first when using Handler, but we usually do not call it when we use it. Why is that possible? The answer is, of course, that the system is called for you, and where is the system called?

By default, the system creates a thread, i.e. the main thread (UI thread), when the program starts. By looking at the main() function in the source code of ActivityThread.java (which is not a thread but a simple java class), you can see the following code:

public static void main(String[] args) {

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);

        //A number of codes are omitted...

        Process.setArgV0("<pre-initialized>");

         //Initialize our main thread
        Looper.prepareMainLooper();

         //A number of codes are omitted...

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

         //A number of codes are omitted...

        //Unlimited dead loops, of course, are done inside loops through the thread's waiting wake-up mechanism (saving resources)
        Looper.loop();

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

I'm sure you've understood the relationship between the three. The most important operation is in Looper. Go ahead and analyze the last step, handler.sendMessage(msg), to send a message:

handler.sendMessage(msg)
//Calling the above line of code, we continue to track the source code and find that the following code is finally called
//Add the message to MessageQueue and wake up Looper.loop() to wait for the message to be taken out.
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

The whole process of Handler's work here is clear at a glance:

Handler sends a message - > Puts it in MessageQueue - > Looper. loop () Dead Loop to retrieve the message back to handler

Now let's use Handler in creating a thread of our own, and you can clearly understand the Looper above.

new Thread(new Runnable() {
    @Override
    public void run() {
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
            }
        };
    }
}).start();

As you can see, the system throws a Runtime Exception prompt to call Looper.prepare().
Let's revamp the code:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
            }
        };
        Looper.loop();
    }
}).start();

When you create handler, you will definitely call Looper.loop(), otherwise the message will just be put in and not taken out.

Question 1: Why does the system call Looper.loop() in the main thread to loop the main thread, and how does the Activity life cycle or other UI updates work? Here we will continue to look at the main() function source code of ActivityThread.java class for explanation:

public static void main(String[] args) {

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);

        //A number of codes are omitted...

        Process.setArgV0("<pre-initialized>");

         //Initialize our main thread
        Looper.prepareMainLooper();

         //A number of codes are omitted...
         //Get handler s for updating ui operations or other operations
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

         //A number of codes are omitted...

        //Unlimited dead loops, of course, are done inside loops through the thread's waiting wake-up mechanism (saving resources)
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  1. From the source code above, you can see that the sMainThreadHandlerHandler object is initialized first. What's the use of this object? Here's what you can tell.
  2. After initializing sMainThreadHandler, Looper.loop() is executed; the main thread is dead-looped
  3. The last line of code is a bit annoying to me: if Looper.loop() exits the dead loop, then the main thread runs out and your program exits. So the main thread is a thread that runs from the start of the application to the exit of your program.
  4. We tried to call the following code:
 //Exit the main thread
 Looper.getMainLooper().quit();

It's exactly what we expected.

  1. We all know to create a thread manually, and when the code in the run function is executed, the thread will automatically exit. Our main thread must not exit (as demonstrated above), so the main thread finally performs Looper.loop(); let the thread enter a dead loop so that the thread is running all the time.

So the question arises: How is the life cycle function in Activity called?

  1. Of course, we use the sMainThreadHandler object created before the dead cycle, which is an internal class class H extends Handler {} in ActivityThread, through which some of Activity's life cycle callback functions are called back. Let's take a look at the handler Message (Message msg) function in this H class.
public void handleMessage(Message msg) {

            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, "LAUNCH_ACTIVITY");
                    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");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handlePauseActivity((IBinder) args.arg1, false,
                            (args.argi1 & USER_LEAVING) != 0, args.argi2,
                            (args.argi1 & DONT_REPORT) != 0, args.argi3);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY_FINISHING: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
                            args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

            // Some of the code has been intercepted. If you are interested, you can see the source code.

    }

Summary: The main thread performs UI operations through the message notification interface of sMainThreadHandler.

Question 2: Why send a message in the sub-thread and the message is in the main thread?

Reason: When you create a handler, you also create Looper and MeessageQueue, which are created in the main thread (Handler is usually initialized in the main thread), so that when you send a message in MeessageQueue in any other thread, the message is in MeessageQueue, and Looper loop takes the message out of MeessageQueue and calls it. Use msg.target.dispatchMessage(msg); this returns the message to the main thread where the handler was created. (If there is any mistake, we hope to point out)

Next, we implement a set of handlers according to the content we analyzed above.

Main classes to be created:

  1. MyHandler
  2. MyMessage
  3. MyLooper
  4. MyMessageQueue

MyHandler only gives the main implementation code here, and some other code in the source code has been omitted.

public class MyHandler {
    /**
     * Message interception
     */
    private MyCallback mCallback;
    /**
     * Poller
     */
    private MyLooper looper;
    /**
     * Message queue
     */
    private MyMessageQueue queue;

    public MyHandler() {
        this(null);
    }

    /**
     * handler Initialization
     * 1.Get the Looper object bound by the current thread
     */
    public MyHandler(MyCallback mCallback) {
        looper = MyLooper.myLooper();
        if (looper == null) {
            throw new RuntimeException("If the current thread is not initialized Looper Object, you need to call Looper.prepare();");
        }
        this.mCallback = mCallback;
        queue = looper.queue;
    }

    /**
     * Processing message
     */
    public void handleMessage(MyMessage msg) {

    }

    /**
     * Distribution of messages
     */
    public void dispatchMessage(MyMessage msg) {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }

    /**
     * Send a message
     */
    public final boolean sendMessage(MyMessage msg) {

        //... a series of function calls in the middle, and ultimately the following key code is executed
        msg.target = this;
        return queue.enqueueMessage(msg);
    }

    /**
     * Used to intercept messages sent by handler
     */
    public interface MyCallback {
        boolean handleMessage(MyMessage msg);
    }
}

MyMessage message entity class

public class MyMessage {

    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    public MyHandler target;

    public MyHandler getTarget() {
        return target;
    }

    public void setTarget(MyHandler target) {
        this.target = target;
    }

    public void sendToTarget() {
        target.sendMessage(this);
    }
}

MyLooper Loop Cancel Interest

public class MyLooper {
    /**
     * ThreadLocal A ThreadLocal Map (similar to HashMap) is maintained internally and stored as key value
     * key:Threads currently creating Looper objects
     * value: Looper object
     */
    static final ThreadLocal<MyLooper> sThreadLocal = new ThreadLocal<>();
    /**
     * Current thread
     */
    private final Thread mThread;
    /**
     * Message queue
     */
    public final MyMessageQueue queue;

    /*
     * Initialize the MyLooper object and bind its thread
     * The same (Handler Looper MessageQueue) is maintained within a thread
     */
    public static void prepare() {
        sThreadLocal.set(new MyLooper());
    }

    /**
     * Initialize message queue
     */
    private MyLooper() {
        queue = new MyMessageQueue();
        mThread = Thread.currentThread();
    }

    /**
     * Returns the Loper object associated with the current thread
     * Returning null does not initialize a Looper object in the thread
     */
    public static MyLooper myLooper() {
        return sThreadLocal.get();
    }

    public static void loop() {
        /**
         * In Looper source loop function
         * <p>Send messages back to handler using
         * <p>msg.target.dispatchMessage(msg);
         */
        MyLooper looper = myLooper();
        MyMessageQueue queue = looper.queue;
        for (; ; ) {
            MyMessage msg = queue.next();
            if (msg != null) {
                msg.target.dispatchMessage(msg);
            }
        }
    }
}

MyMessageQueu stores messages

public class MyMessageQueue {

    private int position = 0;
    /**
     * Store message
     */
    private List<MyMessage> list = new ArrayList<>();

    /**
     * Add a message
     *
     * @return Successful addition
     */
    public boolean enqueueMessage(MyMessage msg) {
        synchronized (this) {
            //Eliminate dried source code
            list.add(msg);
        }
        return true;
    }

    /**
     * Get the latest news
     */
    public MyMessage next() {
        synchronized (this) {
            if (list.size() > 0) {
                if (position <= list.size() - 1) {
                    MyMessage message = list.get(position);
                    position++;
                    return message;
                } else {
                    position = 0;
                    list.clear();
                }
            }
            return null;
        }
    }
}

Posted by jebster on Sun, 16 Dec 2018 19:51:03 -0800