preface

Handler mechanism is almost the Android interview must ask questions, although I have seen many times the handler source, but some interviewers ask questions can not be answered, take the opportunity to summarize the interview covered by the handler knowledge.

What is the basic implementation of Handler?

The following diagram shows the entire handler mechanism.

The simplest ones are the following four:

1. Each thread has a maximum of one Looper, which is stored by a ThreadLocal. A Looper has a Message queue, which holds the handler and executes the Message sent by the handler.

Create Looper with looper.prepare () and store Looper with ThreadLocal. Looper.prepare() can only be called once per thread. This ensures the uniqueness of Looper in the thread.

SendMessage or POST operations are performed on the same thread as the Looper of the handler, regardless of where the handler is created.

A thread can only have one Looper, but a Looper can correspond to multiple handlers. Messages in the same Looper are executed in the same thread.

Handler mechanism, sendMessage and POST (Runnable) difference?

To see the difference between sendMessage and POST, you need to look at the source code. Here are a few ways to use handler, and then look at the differences from the source code. Example 1: Use handler for the main thread

/ / main thread
        Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                if (msg.what == 1) {
                    //doing something
                }
                return false; }}); Message msg = Message.obtain(); msg.what =1;
        mHandler.sendMessage(msg);
Copy the code

Handler is used in the main thread, because Android already generates Looper in the main thread, so you don’t need to generate Looper yourself. This is reported if the above code is executed in a child thread

Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()
Copy the code

If you want to handle handler operations in child threads, you have to generate Looper yourself.

Example 2: Use handler in child thread

        Thread thread=new Thread(new Runnable() {
            @Override
            public void run(a) {
                Looper.prepare();
                Handler handler=new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run(a) {}}); Looper.loop(); }});Copy the code

Execute Looper. Prepare to generate a Looper object in the current Thread and store it in the current Thread’s ThreadLocal. Looper.prepare()

//prepare
    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
//Looper
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Copy the code

A new Looper is generated and stored in the thread’s ThreadLocal. This ensures that only one Looper can be created for each thread.

Also: Since Looper has a reference to the current thread, you can sometimes use this feature of Looper to determine if the current thread is the main thread.

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    boolean isMainThread(a) {
        return Objects.requireNonNull(Looper.myLooper()).getThread() == Looper.getMainLooper().getThread();
    }
Copy the code

sendMessage vs post

Let’s take a look at the sendMessage call chain:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

The code handling for enqueueMessage is simple, msg.target = this; The current handler object is passed to message.target. And then message goes into the queue.

Post code call chain:

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

As you can see, getPostMessage becomes a Messgae and assigns runnable to the Message callback. Once the messages are in MessageQueue, see how Looper handles them.

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            return;
        }
        msg.target.dispatchMessage(msg);
    }
Copy the code

Stars will traverse the message in the list, when the message is not null call MSG. Target. DispatchMessage (MSG) method. Take a look at the message structure:

    public void dispatchMessage(@NonNull Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}//
 private static void handleCallback(Message message) {
        message.callback.run();
    }
Copy the code

Because message.callback=runnable is generated when the POST method is called, message.callback.run() is called directly in the dispatchMessage method; That is, execute the runnable method in POST directly. In sendMessage, McAllback.handlemessage (MSG) is called if mCallback is not null, otherwise handleMessage is called directly.

The difference between post and handleMessage is that post runnable calls run directly in callback, while sendMessage requires the user to override mCallback or handleMessage.

3. Does Looper constantly consume system resources?

The first conclusion is that Looper does not consume system resources all the time. When there is no message in Looper’s MessageQueue, or when the timed message is not executed, the thread currently holding Looper will block.

News out of the team

   Message next(a) {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
           	if(hasNoMessage)
           	{
           	nextPollTimeoutMillis =-1; }}}Copy the code

NextPollTimeoutMillis =-1; nextPollTimeoutMillis=-1;

 	if(hasNoMessage)
           	{
           	nextPollTimeoutMillis =-1; }Copy the code

See what this field does in the for loop:

 if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
  nativePollOnce(ptr, nextPollTimeoutMillis);
Copy the code

Binder.flushPendingCommands(); The function of this method can be explained in the source code:

    /** * Flush any Binder commands pending in the current thread to the kernel * driver. This can be * useful to call before performing an operation that may block for a long * time, to ensure that any pending object references have been released * in order to prevent the process from holding on to objects longer than * it needs to. */
Copy the code

This means that a message is sent to the kernel thread before the user thread enters the block, preventing the user thread from holding an object for a long time. Consider the following method: nativePollOnce(PTR, nextPollTimeoutMillis); When nextPollingTimeOutMillis=-1, this native method will block the current thread, and the thread will return to the runnable state only after the next message is queued. Therefore, Looper does not consume running memory in an endless loop. The current thread will also block if the color message in the queue has not run out of time, but there will be a blocking time when nextPollingTimeOutMillis>0.

Looper must wake up when there is no message in the message queue.

News team

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;
            }

            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 if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue. Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr ! = 0 because mQuitting is false.
            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

You can see up there that there will be one after you join the team

  if (needWake) {
                nativeWake(mPtr);
            }
Copy the code

Method to wake up the thread. In addition, messages are sorted in the linked list according to the delay time of messages when joining the queue. The messages with long delay time are ranked behind, while those with short delay time are ranked in front. If the insertion time is the same, the insertion time is arranged in order of insertion time, with the early insertion time in front and the late insertion time in the back.

How does the Handler of the main thread determine which Handler sent the received message?

How does Looper determine which handler the Message came from? In sendMessage, the handler builds a Message object and places itself in the target of the Message. This way, Looper can determine which handler sent the current Message based on the target in the Message.

5, Handler mechanism process, Looper delay messages who wake up Looper?

We know from 3 that the following method is called in the for loop queue when the message is queued.

nativePollOnce(ptr, nextPollTimeoutMillis);
Copy the code

If the message is delayed, it will be woken up after blocking nextPollTimeoutMillis, which is the difference between the time the message is to be executed and the current time.

How does Handler cause a memory leak? How to solve it?

In a child thread, if you manually create a Looper for it, you should call quit to terminate the message loop after everything is done. Otherwise, the child thread will remain in a waiting state. If you exit the Looper, the thread will terminate immediately.

Looper.myLooper().quit()
Copy the code

If a message is processed in the Handler’s handleMessage (or run) method, it will remain in the message queue of the main thread and will affect the system’s ability to reclaim the Activity, causing a memory leak.

For details, see Handler Memory Leak analysis and troubleshooting

To summarize, there are two main points to address Handler memory leaks

1. There are delayed Messages that need to be removed when the Activity is destroyed

2. Change leaks from anonymous inner classes to anonymous static inner classes and use weak references to the context or Activity.

7. How to ensure uniqueness of Looper in handler mechanism?

Looper is stored in the thread’s ThreadLocal. Handler calls looper.prepare () to create a Looper and place it in the current thread’s ThreadLocal.

    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
Copy the code

If prepare is called several times, Only one Looper may be created per thread. This ensures that Only one Looper is created per thread.

8. How does Handler switch threads to send messages?

Handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler Or sendMessage, the final Handle Message is executed in the main thread.

        Thread thread=new Thread(new Runnable() {
            @Override
            public void run(a) {
                Looper.prepare();
                Handler handler=new Handler(getMainLooper());
                handler.post(new Runnable() {
                    @Override
                    public void run(a) {
                        Toast.makeText(MainActivity.this."hello,world",Toast.LENGTH_LONG).show(); }}); Looper.loop(); }}); thread.start();Copy the code

reference

1, www.jianshu.com/p/ea7beaeee…