The Handler is the core class of the Android SDK for handling asynchronous messages. Child threads can use the Handler to notify the main thread of UI updates.

Note: This source screenshot is based on Android SDK 28

The main process of sending messages using the Handler mechanism is shown in the figure

First, Handler mechanism

1. MessageQueue is created

When the application starts, Zygote forks an application process, which executes the main function in ActivityThread as normal Java programs do. In the main function, the program first creates the Looper object and binds it to the main thread, then starts the loop. (PS: main thread loop cannot exit)

public static void main(String[] args) { ...... Looper.prepareMainLooper(); . Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

In the prepareMainLooper method, Looper, MessageQueue objects, and native layer MessageQueue objects are eventually created.

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

2. Message sending

After sending a message using handler. sendMessageXXX or this postDedayXXX, the SendMessageAtTime method is eventually called.

public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; .return enqueueMessage(queue, msg, uptimeMillis);
}
Copy the code

Then calls the MessageQueue. EnqueueMessage to save the message as a message queue.

Boolean enqueueMessage(Message MSG, long when) {synchronized (this) { // A message queue is actually a single linked list, with the smallest when indicating that the first message triggered is placed in the head of the list......if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

After storing the message, the native method is called to wake up the main thread for message processing.

3. Message loop

When the application is started and some necessary work has been done, the Loop starts and does not stop unless there is a system exception. A loop loop does two things. First, it fetches messages from a message queue. Second, message distribution processing.

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

        for(;;) {... Message msg = queue.next(); // might block ...... msg.target.dispatchMessage(msg); . }Copy the code
Message next() {
        for(;;) {... nativePollOnce(ptr, nextPollTimeoutMillis); . // Retrieves the current message from the queue and returns it. // If the current message queue is not empty, nextPollTimeoutMillis is assigned to the time when the next message will fire. // If the current message queue is empty, nextPollTimeoutMillis is assigned to -1, which blocks indefinitely, and wakes up until the next message enters the message queue.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) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        returnmsg; }}else{ nextPollTimeoutMillis = -1; } nextPollTimeoutMillis = 0; . }}Copy the code

Messagequeue.next () calls the native method nativePollOnce(PTR, nextPollTimeoutMillis) to implement the blocking function when there is no message processing. When nextPollTimeoutMillis is 0, the method returns immediately; When nextPollTimeoutMillis is -1, the method blocks indefinitely until it is awakened; When the nextPollTimeoutMillis value is greater than 0, the method sets the value to a timeout period, and returns when the block reaches a certain period.

4. message distribution

In the loop cycle, by calling the MSG. Target. DispatchMessage (MSG) for message distributed processing

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

        for(;;) { Message msg = queue.next(); // might block ...... msg.target.dispatchMessage(msg); . }Copy the code
    /**
     * 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

What is IdelHandler?

1, introduction,

Using the current thread MessageQueue. AddIdleHandler method can add a IdelHandler in the message queue.

        MessageQueue messageQueue = Looper.myQueue();
        messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false; }});Copy the code

When MessageQueue blocks, that is, when the current thread is idle, methods in IdleHandler are called back.

Note: when IdelHandler is added, the message queue is not empty. When IdelHandler is added, the message queue is empty, so the callback is not triggered at that time

When the IdelHandler interface returns false, the IdelHandler executes only once.

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for(;;) { nativePollOnce(ptr, nextPollTimeoutMillis); . synchronized (this) { ......for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if(! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } } }Copy the code

2. Common scenarios

A, delay execution

For example, when starting an Activity, some actions need to be delayed so as not to start too slowly. We often use the following methods to delay the execution of tasks, but the delay time is difficult to control.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        },1000);
    }
Copy the code

In fact, it would be more elegant to use IdelHandler

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

        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
               //do something
                return false; }}); }Copy the code

B. Batch tasks, task intensive, and focus only on the end result

For example, when developing an IM interface, the interface is refreshed once each time an IM message is received. However, when multiple IM messages are received within a short period of time, the interface is refreshed several times, which may cause lag and affect the performance. In this case, you can use a worker thread to listen for IM messages. You can add IdelHandler to notify the interface to refresh, avoiding multiple interface refreshes in a short period of time.

Three, what is the message barrier?

In Android messaging, there are actually three types of messages: normal messages, asynchronous messages, and message barriers.

A message barrier is also a message, but its target is NULL. A message barrier can be sent through the postSyncBarrier method in MessageQueue (which is private and requires reflection calls).

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; // Insert messages into the message queue in chronological order...... return token; }}Copy the code

In a message loop, if the first message is a barrier message, it loops back to see if there is an asynchronous message: if not, it sleeps indefinitely, waiting to be awakened. If there is, it sees how long it is before the message is triggered, sets a timeout, and continues sleeping

An asynchronous message is the same as a normal message, except that it has setAsynchronous set to true. With this flag bit, the messaging mechanism has some special treatment for it, as we’ll talk about later.

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    private void createAsyncMessage(){
        Message msg = Message.obtain();
        msg.setAsynchronous(true);
    }
Copy the code

So the role of message barriers and asynchronous messages is obvious, after the message barriers are set, the asynchronous messages have the priority of processing rights.

When we look back at adding messages to the message queue at this point, we can see that the thread is not actually woken up every time a message is added. When the message is inserted into the queue header, the thread is woken up; If the message is not inserted into the queue header, but the queue header is a barrier, and the message is an asynchronous message first in the queue, the thread will wake up and execute the message.

Call MessageQueue. RemoveSyncBarrier method can remove the specified message

    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while(p ! = null && (p.target ! = null || p.arg1 ! = token)) { prev = p; p = p.next; }...if(needWake && ! mQuitting) { nativeWake(mPtr); }}}Copy the code

And four, what is ANR? What does it matter?

ANR is Application Not Response, which is a monitoring of Application behavior by the system process. If the Application does Not complete the task within the specified time, ANR will be caused.

ANR type

Service Timeout: 20 seconds for foreground Service and 200 seconds for background Service

BroadcastQueue Timeout: BroadcastQueue Timeout: 10s in the foreground and 60s in the background

ContentPrivider Timeout: 10s

InputDispatching Timeout: 5s

For example, when a Service is started, the AMS side creates a Service through the Binder object of the application process. The onCreate() lifecycle function of the current Service is called in the scheduleCreateService() method.

private final void realStartServiceLocked(...){
    ...
    bumpServiceExecutingLocked(r, execInFg, "create"); . app.thread.scheduleCreateService(...) ; . serviceDoneExecutingLocked(...) }Copy the code

BumpServiceExecutingLocked () method inside will actually call to scheduleServiceTimeoutLocked () method, Send a ActivityManagerService. SERVICE_TIMEOUT_MSG type message to AMS work thread.

  void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg,
                  proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }
Copy the code

The delay time of the message is 20s for the foreground service and 200s for the background service.

    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
Copy the code

If the creation of the Service is completed within the latency of the appeal message, the message is removed,

private void serviceDoneExecutingLocked(...) {... mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); . }Copy the code

Otherwise, after the Handler receives the message, the service times out, that is, the ANR dialog box is displayed.

    public void handleMessage(Message msg) {
            case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            }
Copy the code

Fifth, performance optimization

In complex cases, sendMessage may be called frequently to add messages to the message queue, resulting in messages being backlogged and stalling.

1. Duplicate message filtering

When a message of the same type is frequently sent, another message of the same type may be sent to update the previous data. In this case, you can remove the previous message to optimize the message queue.

    private void sendTypeMsg(){
        Message msg = Message.obtain();
        msg.what = MSG_TYPE;
        handler.removeMessages(MSG_TYPE);
        handler.sendMessage(msg);
    }
Copy the code

2. Mutually exclusive messages are cancelled

When sending messages, priority is given to removing messages whose information is outdated from the message queue to optimize the message queue

3. Queue optimization – message reuse

When creating a message, the recycled message is preferred to avoid repeated object creation and GC

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in 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; }}return new Message();
    }
Copy the code

If mistakes or deficiencies, please point out, we make progress together.