preface

What do you think about handler?

An interview test? Project common? Huge system?

Since it’s so important, how well do you know it? Find out where the bottom of Handler’s casserole is.

27 ask, read Handler again from the point of view of the problem.

The outline

1. Why was Handler designed? What’s the use?

A thing is designed for a purpose, and the purpose of a Handler is to switch threads.

As a major member of the Android messaging mechanism, it manages all message events related to the interface. Common usage scenarios are as follows:

  • Interface message processing after cross-process.

For example, when AMS communicates between processes, the Binder threads send messages to ApplicationThread’s message Handler, which is then distributed to the main thread for execution.

  • Switch to the main thread for UI update after network interaction

After the child thread network operation, you need to switch to the main thread for UI update.

In short, Hanlder exists to solve the problem of not being able to access the UI in child threads.

2. Why is it recommended that child threads not access (update) the UI?

Because UI controls in Android are not thread-safe, it’s not a mess if multiple threads access UI controls.

Then why not lock it?

  • It reduces the efficiency of UI access. UI control itself is a component close to the user, lock will naturally block, then the efficiency of UI access will be reduced, the final reaction to the user is the mobile phone has a point card.
  • It's too complicated. UI access itself a relatively simple operation logic, directly create UI, modify UI. If you lock it then you make the logic of the UI access very complicated, it doesn’t have to be.

So Android’s single-threaded model for handling UI operations, coupled with handlers, is a more appropriate solution.

3, child thread access UI crash causes and solutions?

The crash occurred in the checkThread method of the ViewRootImpl class:

    void checkThread(a) {
        if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

Check if the current thread is the same thread that created the ViewRootImpl. If not, it will crash.

The ViewRootImpl is created when the interface is drawn, after the onResume, so if you update the UI in the child thread, you will find that the current thread (the child thread) is not the same thread as the View thread (the main thread) and crash.

There are three solutions:

  • The main thread creates the View, and the main thread updates the View.
  • inViewRootImplThe child thread updates the UI before creation, as in the onCreate method.
  • The child thread switches to the main thread for UI updates, for exampleThe Handler, the postMethods.

4. What is MessageQueue for? What data structure is used to store data?

The name should be a queue structure. What are the characteristics of queues? First in, first out, usually at the end of the team to add data, at the front of the team to get data or delete data.

The message in the Hanlder also seems to satisfy this characteristic, and the first message must be processed first. However, there are special cases in handlers, such as delayed messages.

The existence of delayed messages makes this queue some special, and it cannot guarantee the first in, first out completely, but needs to be judged according to the time. Therefore, Android adopts the form of linked list to realize this queue, which also facilitates the insertion of data.

Let’s take a look at the process of sending a message. Whichever way you send a message, it goes to the sendMessageDelayed method

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

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

The sendMessageDelayed method mainly calculates the time the message needs to be processed. If delayMillis is 0, the message processing time is the current time.

Then there is the key method, enqueueMessage.

    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) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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; 
                prev.next = msg;
            }

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

Don’t look at what we don’t know, just see what we want to see:

  • First of all, I setMessageThe when field represents the processing time of the message
  • The MSG of the current message is inserted into the table header when the execution time is greater than the message time of the table header.
  • Otherwise, you have to go through the queue, which is thetaThe listFind when when is less than a node and insert it.

All right, so without saying anything else, inserting a message is just finding the right place to insert a linked list based on the execution time of the message, the WHEN field.

The specific method is to use the fast and slow Pointers P and prev through an infinite loop, and move backward one space at a time until we find a node p whose WHEN is greater than the when field we want to insert the message, and insert it between P and prev. Or iterate to the end of the list and insert to the end of the list.

So a MessageQueue is a special queue structure implemented in linked lists for storing messages.

5. How are delayed messages implemented?

To sum up, the implementation of delayed messages is mainly related to the unified storage method of messages, the enqueueMessage method mentioned above.

In both instant and delayed messages, a specific time is calculated and then assigned by the process as the when field of the message.

The appropriate location is then found in the MessageQueue (arrange when from small to large) and the message is inserted into the MessageQueue.

In this way, a MessageQueue is a linked list structure arranged by message time.

6. How is the message of MessageQueue taken out?

Now that we’ve talked about message storage, let’s look at message retrieval, the queue.next method.

    Message next(a) {
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! =null && msg.target == null) {
                    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 {
                        // Got a message.
                        mBlocked = false;
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1; }}}}Copy the code

Strange, why the message is also used in an endless loop?

The purpose of the loop is to ensure that a message must be returned, and if no message is available, it is blocked until a new message arrives.

The nativePollOnce method is the blocking method, and the nextPollTimeoutMillis parameter is the blocking time.

So when does it block? There are two cases:

  • 1, There is a message, but the current time is less than the message execution time.
if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
Copy the code

In this case, the blocking time is the message time minus the current time, and then the next loop, blocking.

  • 2, When there is no message, this is the last line of the above code:
if(msg ! =null) {} 
    else {
    // No more messages.
    nextPollTimeoutMillis = -1;
    }
Copy the code

Minus one means always blocking.

7. What happens when there is no message from MessageQueue? How do you wake up after blocking? What about pipe/epoll?

Following the logic above, the next method is blocked when a message is unavailable or not available through the PIPE /epoll mechanism

Epoll mechanism is an I/O multiplexing mechanism. The specific logic is that a process can monitor multiple descriptors. When a descriptor is ready (usually read or write ready), it can notify the program to perform the corresponding read/write operation, which is blocked. In Android, a Linux Pipe is created to handle blocking and wake up.

  • When the message queue is empty, the reader of the pipe waits for something new to read in the pipe and passesepollThe mechanism is blocked.
  • When there is a message to be processed, it is written through the write end of the pipe, waking up the main thread.

So when and how does the message queue thread wake up?

Remember that the enqueueMessage method that inserted the message had a needWake field, which was obviously the wakeup field.

There is also a field called mBlocked, which means blocked. Check the code for it:

Message next(a) {
        for (;;) {
            synchronized (this) {
                if(msg ! =null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        returnmsg; }}if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue; }}}}Copy the code

In the method that gets the message, Next, mBlocked is assigned in two places:

  • When the message is retrieved,mBlockedThe assignment forfalseIs not blocked.
  • When there is no message to process, there is noidleHandlerWhen it comes time to deal with it,mBlockedThe assignment fortrueIs blocked.

Well, it’s true that this field means whether or not to block. Let’s look at the wake up mechanism in the enqueueMessage method:

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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; 
                prev.next = msg;
            }

            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code
  • When the list is empty or the time is less than the header message time, insert the header and set whether to wake up tomBlocked.

If the thread was previously blocked (mBlocked=true) when a new message is about to be inserted into the table header, the thread should be woken up.

  • Otherwise, you need to find a node in the list and insert the message, and you need to assign before you do thatneedWake = mBlocked && p.target == null && msg.isAsynchronous()

That is, before inserting a message, you need to determine whether it is blocked, whether the header is a barrier message, and whether the current message is an asynchronous message. If you are in synchronous barrier mode and the message to be inserted is an asynchronous message, you don’t need to insert the message and wake up the thread because the asynchronous message needs to be executed first.

  • Finally, in the loop, if an asynchronous message is found before, set whether to wake up tofalse.

This means that if there was an asynchronous message before, it must have been woken up before and there is no need to wake up again.

Finally, based on needWake’s value, decide whether to call the nativeWake method to wake up the next() method.

How are synchronous barriers and asynchronous messages implemented?

In the Handler mechanism, there are three types of messages:

  • A synchronous message. Just plain old messages.
  • Asynchronous messaging. Message set by setAsynchronous(true).
  • Synchronous barrier messages. Messages added through the postSyncBarrier method are characterized by an empty target that has no corresponding handler.

What is the relationship between these three?

  • Under normal circumstances, both synchronous and asynchronous messages are processed normally, that is, messages are fetched and processed according to the time when.
  • When a synchronous barrier message is encountered, it searches the message queue for the asynchronous message and blocks or returns the message depending on the time.
Message msg = mMessages;
if(msg ! =null && msg.target == null) {
      do {
      prevMsg = msg;
      msg = msg.next;
      } while(msg ! =null && !msg.isAsynchronous());
}
Copy the code

This means that the synchronous barrier message is not returned, it is just a flag, a tool, which means that the asynchronous message will be processed first.

So the point of synchronous barriers and asynchronous messages is that some messages need to be “rushed”.

9. Are there specific usage scenarios for synchronous barriers and asynchronous messages?

There are many scenarios, such as the drawing method scheduleTraversals.

    void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Synchronization barrier blocks all synchronization messages
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Send draw tasks through Choreographer
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }


    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
    msg.arg1 = callbackType;
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, dueTime);

Copy the code

Joined the synchronization barrier in this method, the follow-up to join an asynchronous message MSG_DO_SCHEDULE_CALLBACK, finally will perform to the FrameDisplayEventReceiver, VSYNC signal used in application.

More Choreographer related content can take a look at this article – www.jianshu.com/p/86d00bbda…

What happens to the Message after it is distributed? How are messages reused?

Look at the Loop method. After the dispatchMessage method has been executed, the recycleUnchecked operation is also added.

    public static void loop(a) {
        for (;;) {
            Message msg = queue.next(); // might block

            try{ msg.target.dispatchMessage(msg); } msg.recycleUnchecked(); }}//Message.java
    private static Message sPool;
    private static final int MAX_POOL_SIZE = 50;

    void recycleUnchecked(a) {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this; sPoolSize++; }}}Copy the code

In the recycleUnchecked method, all resources are freed, and the current empty message is inserted into the sPool header.

The sPool here is a message object pool, which is also a linked list structure of messages with a maximum length of 50.

So how is Message reusable? In the instantiation method of Message obtain:

    public static Message obtain(a) {
        synchronized (sPoolSync) {
            if(sPool ! =null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                returnm; }}return new Message();
    }
Copy the code

The first message in the message pool sPool is directly multiplexed, and sPool points to the next node. The number of message pools is reduced by one.

11. What is a Looper? How do I get the current thread’s Looper? Why not just store threads and objects with maps?

After the Handler sends the message, the message is stored in MessageQueue, and Looper is the role that manages the MessageQueue. Looper constantly looks for messages from MessageQueue, known as the loop method, and passes the messages back to Handler for processing.

Looper is obtained via ThreadLocal:

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

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

    public static @Nullable Looper myLooper(a) {
        return sThreadLocal.get();
    }
Copy the code

Create a Looper using the prepare method and add it to the sThreadLocal. Obtain a Looper from the sThreadLocal using the myLooper method.

12. How does ThreadLocal work? What are the benefits of this mechanic design?

Here’s how ThreadLocal works.

//ThreadLocal.java
    public T get(a) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
    }
Copy the code

As you can roughly see from the get and set methods in the ThreadLocal class, there is a ThreadLocalMap variable that stores data in the form of key-value pairs.

  • keyIs this, the current ThreadLocal variable.
  • valueT, which is the value we’re going to store.

Then continue to see where ThreadLocalMap comes from, which is the getMap method:

    //ThreadLocal.java
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //Thread.java
    ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

The ThreadLocalMap variable is stored in the Thread class Thread.

So the basic mechanics of ThreadLocal are clear:

Each thread has a threadLocals variable that stores the ThreadLocal and corresponding objects to be saved.

This has the advantage of accessing the same ThreadLocal object on different threads, but retrieving different values.

ThreadLocal (ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal)

What’s the good of that? Why not just store threads and objects with maps?

For example:

  • ThreadLocalThe teacher.
  • ThreadIt’s a classmate.
  • LooperThat’s the pencil.

Now the teacher has bought a batch of pencils and wants to distribute them to the students. How to distribute them? There are two ways:

  • 1, The teacher wrote each student’s name on each pencil and put it in a big box. Let the students look for it by themselves.

This method is to store classmates and pencils in the Map, and then use classmates to find pencils in the Map.

This is a bit like using a Map to store all threads and objects. The downside is that it can be messy, each thread is connected to each other, and it can cause memory leaks.

  • 2. The teacher will give each pencil to each student and put it in their pocket (MAP). When using the pencil, each student can take it out of his pocket.

In this way, the Map stores the teacher and the pencil, and the teacher says when you use it, the students just need to take it out of the pocket.

Obviously, this approach is more scientific, and this is ThreadLocal’s approach. Since the pencils are used by students themselves, it is best to give the pencils to students for their own care at the beginning, and separate them from each other.

13. Where else are ThreadLocal mechanisms used?

For example: Choreographer.

public final class Choreographer {

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue(a) {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            returnchoreographer; }};private static volatile Choreographer mMainInstance;

Copy the code

Choreographer is primarily for the main thread, used to coordinate with VSYNC interrupt signals.

So the point of using ThreadLocal here is more to do with thread singletons.

Can I create Looper multiple times?

Prepare creates a Looper using the prepare method, which determines whether the current thread has a Looper object and throws an exception if it does:

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

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

Therefore, only one Looper can be created for the same thread.

What is the quitAllowed field in Looper? What’s the use?

Let’s look at some of the ways he uses it:

    void quit(boolean safe) {
        if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else{ removeAllMessagesLocked(); }}}Copy the code

If this field is false, it will not allow you to quit, and an error will be reported.

But what does this quit method do? It’s never been used. And what about safe?

The quit method is to quit the message queue and terminate the message loop.

  • First of all, I setmQuittingThe field is true.
  • Then determine whether it is safe to exit. If it is, execute itremoveAllFutureMessagesLockedMethod, which empties all delayed messages, and sets the next node of the non-delayed message to null (p.ext =null).
  • If it does not exit safely, executeremoveAllMessagesLockedMethod to empty all messages and set the message queue to null (mMessages = NULL)

Then look at how messages are sent and processed after the quit method is called:

// Message sent
    boolean enqueueMessage(Message msg, long when) {
        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; }}Copy the code

When the quit method is called and McOntract is true, the message cannot be sent and an error is reported.

Looking at message handling, the loop and next methods:

    Message next(a) {
        for (;;) {
            synchronized (this) {
                if (mQuitting) {
                    dispose();
                    return null; }}}}public static void loop(a) {
        for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return; }}}Copy the code

Obviously, the loop exits the loop when the next method returns NULL when McOntract is true.

So when is the quit method usually used?

  • In the main thread, you can’t normally exit because the main thread stops. So when the APP needs to quit, the quit method is called, and the message involved is EXIT_APPLICATION, you can search for it.
  • In the child thread, if all the messages are processed, you need to call the quit method to stop the message loop.

16, Looper. Loop method is an infinite loop, why doesn’t it get stuck (ANR)?

About this problem, it is strongly recommended that see Gityuan answer: www.zhihu.com/question/34…

Let me summarize:

  • 1, the main thread itself is required to run, because to deal with various views, interface changes. So we need this infinite loop to keep the main thread running and not exiting.
  • 2. The only operation that really freezes is one that takes too long to process a message, resulting in frame drops and ANR drops, not the loop method itself.
  • 3. Outside of the main thread, other threads handle events that accept other processes, such asBinder Threads (ApplicationThreads), will accept events sent by AMS
  • 4, After receiving the cross-process message, it will be handed to the main threadHanlderThen the message is distributed. So the life cycle of an Activity is dependent on the main threadLooper.loopWhen different messages are received, corresponding actions are taken, for example, receivedmsg=H.LAUNCH_ACTIVITYThe callActivityThread.handleLaunchActivity()Method, and finally executes to the onCreate method.
  • 5. Block in loop when there is no messagequeue.next()In thenativePollOnce()Method, the main thread releases CPU resources and goes to sleep until the next message arrives or a transaction occurs. So an infinite loop is not a huge drain on CPU resources.

17. How does a Message find the Handler to which it belongs and distribute it?

In the loop method, we find the Message to process and call this code to process the Message:

msg.target.dispatchMessage(msg);
Copy the code

So the message is handed to msg.target. What is the target?

Find out where it came from:

//Handler
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;
       
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

MSG. Target = this is set when a Hanlder is used to send a message, so target is the Handler that added the message to the message queue.

What is the difference between Handler post(Runnable) and sendMessage

The main sent messages in Hanlder can be divided into two types:

  • post(Runnable)
  • sendMessage
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

The difference between POST and sendMessage is as follows:

The POST method sets a callback to Message.

So what does this callback do? Let’s switch to the message handling method dispatchMessage:

    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

This code can be viewed in three parts:

  • 1, ifmsg.callbackNon-null, which means that when a message is sent via post, it is passed to msg.callback for processing, and there is no further action.
  • 2, ifmsg.callbackIs empty, that is, through the sendMessage when sending a message, will determine whether the current mCallback Handler is empty, if not null to the Handler. Callback. HandleMessage processing.
  • 3, ifmCallback.handleMessageReturn true, there is no follow-up.
  • 4, ifmCallback.handleMessageReturn false, the handleMessage method overridden by the Handler class is called.

So the difference between POST (Runnable) and sendMessage is how subsequent messages are handled, whether to MSG.Callback or handler. callback or handler.handleMessage.

19, Handler. Callback. HandleMessage and Handler handleMessage what’s different? Why?

Then the code above that, the difference between the two processing methods Handler. The Callback. The handleMessage method will return true:

  • If it istrueHandler.handleMessage is no longer executed
  • If it isfalse, both methods are executed.

So when is there a Callback and when isn’t? There are two ways in which hanlders can be created:

    val handler1= object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    val handler2 = Handler(object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true}})Copy the code

The common approach is the first, to subclass Handler and rewrite the handleMessage method. In the second case, the system gives us a way to use the Callback without subclass derivation.

Handler, Looper, MessageQueue, thread is a one-to-one correspondence?

  • There can only be one threadLooperObject, so threads and Looper are one-to-one correspondences.
  • MessageQueueThe object is created when new Looper is created, so Looper and MessageQueue correspond one to one.
  • HandlerMessageQueue is used to add a message to a MessageQueue and then distribute the message to the original handler according to the target field of the message. That is, multiple Hanlder objects can use the same thread, the same Looper, the same MessageQueue.

Conclusion: Looper, MessageQueue, thread are one-to-one correspondence, while they can be one-to-many with Handler.

21. What is done about handlers in ActivityThread? (Why the main thread doesn’t need to create a separate Looper)

Two main things were done:

  • 1, in the main method, create the main threadLooperandMessageQueueAnd calls the loop method to open the message loop for the main thread.
public static void main(String[] args) {

        Looper.prepareMainLooper();

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

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code
  • 2, create a Handler for the four components of the start and stop event processing
final H mH = new H();

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int STOP_SERVICE            = 116;
        public static final int BIND_SERVICE            = 121;

Copy the code

22, What is IdleHandler? What are the usage scenarios?

As mentioned earlier, when MessageQueue has no messages, it blocks in the next method. Before blocking, MessageQueue also does one thing: check for IdleHandler, and if so, execute its queueIdle method.

    private IdleHandler[] mPendingIdleHandlers;

    Message next(a) {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                PendingIdleHandlerCount is set when the message completes execution
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                // Initialize mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //mIdleHandlers are converted to arrays
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Go through the number group and process each IdleHandler
            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 queueIdle returns false, the IdleHandler is removed
                if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0; }}Copy the code

When there is no message processing, each IdleHandler object in the mIdleHandlers collection is processed and its queueIdle method is called. Finally, the current IdleHandler is deleted based on the queueIdle value.

Then see how the IdleHandler is added:

Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle(a) {  
        / / do something
        return false; }});public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) { mIdleHandlers.add(handler); }}Copy the code

IdleHandler is used to do idle tasks when there are no current messages in the message queue and it needs to be blocked.

Common usage scenarios are: Startup optimization.

We typically place events (such as interface view drawing and assignment) in onCreate or onResume. However, both methods are actually called before the interface is drawn, which means that to some extent the time of these two methods will affect the startup time.

So we can reduce startup time by putting some operations into IdleHandler, which is called after the interface is drawn.

However, there may be some potholes to be aware of.

If used incorrectly, IdleHandler will never execute, such as calling the View’s invalidate method directly or indirectly in the View’s onDraw method.

Its reason lies in the ontouch method that executes invalidate, will add a synchronization barrier, before until the asynchronous message blocks in the next method, and after FrameDisplayEventReceiver asynchronous task executes ontouch method, thus an infinite loop.

Check out this article: mp.weixin.qq.com/s/dh_71i8J5…

23, What is HandlerThread? What are the usage scenarios?

Direct look at the source code:

public class HandlerThread extends Thread {
    @Override
    public void run(a) {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }
Copy the code

Oh, I see. HandlerThread is a Thread class that encapsulates Looper.

Just to make it easier for us to use Handler in child threads.

NotifyAll is a notifyAll method used to wake up other threads. NotifyAll is a notifyAll method used to wake up other threads.

    public Looper getLooper(a) {
        if(! isAlive()) {return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
Copy the code

GetLooper method, so to wait means to wait for the Looper to be created and then tell the side to return the Looper correctly.

24, What is IntentService? What are the usage scenarios?

Old rules, directly look at the source code:

public abstract class IntentService extends Service {


    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); }}@Override
    public void onCreate(a) {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
Copy the code

Check the source code:

  • First of all, this is aService
  • And it maintains one internallyHandlerThreadThat is, there is a full Looper running.
  • A child thread is also maintainedServiceHandler.
  • After a Service is started, it is executed by a HandleronHandleIntentMethods.
  • After the task is complete, it is automatically executedstopSelfStop the current Service.

So, this is a Service that can perform a time-consuming task on a child thread and stop automatically after the task is executed.

25. Have BlockCanary been used? Tell me about the principle of

BlockCanary is a tripartite library for detecting application lag time.

The View is also drawn by a Handler, so if you know how long it takes the Handler to process a message, you know how long it takes to draw a View. How do I get the processing time of the Handler message?

Go back to the loop method for details:

public static void loop(a) {
    for (;;) {
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if(logging ! =null) {
            logging.println(">>>>> Dispatching to " + msg.target + "" +
                    msg.callback + ":" + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if(logging ! =null) {
            logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); }}}Copy the code

It can be found that there is a Printer class in the loop method, and two logs are printed before and after dispatchMessage processing message.

Then we replace this log class Printer with our own Printer, and then count the time of printing log twice is equivalent to the time of processing message.

    Looper.getMainLooper().setMessageLogging(mainLooperPrinter);

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
Copy the code

That’s how BlockCanary works.

Specific can see the author’s note: blog. Zhaiyifan. Cn / 2016/01/16 /…

26, Talk about Hanlder memory leak.

This is also a commonly asked question, what is the cause of the Handler memory leak?

“The inner class holds a reference to the outer class, so the Hanlder holds a reference to the Activity and therefore cannot be recycled.”

That’s the wrong answer, or the wrong answer.

We must find the final reference, the reference that will not be reclaimed, which is the main thread. The complete reference chain should look like this:

Main thread – > ThreadLocal – > Looper – > MessageQueue – > Message – > Handler – > Activity

Check out this article I wrote earlier: juejin.cn/post/690936…

27, Use Handler mechanism to design a non-crash App?

The main thread crashes, in fact, occur in the processing of the message, including the life cycle, interface drawing.

So if we can control this process and re-open the message loop after a crash, the main thread will continue to run.

Handler(Looper.getMainLooper()).post {
        while (true) {
            // The main thread is blocked
            try {
                Looper.loop()
            } catch (e: Throwable) {
            }
        }
    }
Copy the code

There are also special cases to handle, such as a crash within onCreate, as described in the article

“Can APP Never Crash” juejin.cn/post/690428…

conclusion

As you can see, there is a question that is often asked, but not mentioned in the whole article, and that is:

Operation principle of Hanlder mechanism.

The reason for not mentioning this question is that a good answer to this question requires a large amount of knowledge reserve. I hope that you in front of the screen can form your own “perfect answer” by combining your knowledge base after reading this article.

Hanlder, I Got it!

reference

“Android development art exploration” www.zhihu.com/question/34… Segmentfault.com/a/119000000… Juejin. Cn/post / 684490… Juejin. Cn/post / 689379…

Bye bye

Thank you for your reading. If you study with me, you can pay attention to my public account — building blocks on the code ❤️❤️, a knowledge point every day to establish a complete knowledge system architecture. Here is a group of Android friends, welcome to join us