If you are interviewing for advanced Android development, Handler is definitely one of the must-ask questions. This article combines the interview experience of Byte, Ali, netease and other companies in April and May 2021, and summarizes the knowledge points related to Handler that were asked during the interview process. Will involve a lot of details of knowledge, we can as a reference to understand the interview.

Handler based

Can you talk about Handler?

About half of all companies will start with this question and ask a broad question. It is recommended to sort out the overall process of the Handler, in order:

  1. The Handler — Looper — Message — MessageQueue relationship can be discussed first. We’ll talk about Looper — MessageQueue uniqueness in a thread, and how ThreadLocal ensures uniqueness.

  2. The Handler process is as follows:

    • Handler. sendMessage sends messages to MessageQueue MessageQueue
    • Looper calls its loop() function to drive the MessageQueue. The enqueueMessage() Message of the MessageQueue is queued. The next() method polls each Message in the MessageQueue through a for loop. (enqueueMessage, next, quit, synchronized)
    • The message is processed by dispatchMessage() with priority: message callback > handler callback > default handleMessage(MSG).
  3. Talk about asynchronous messaging, synchronization barriers. (This will be followed by questions about the use of synchronization barriers, how to send an asynchronous message, etc., which will be covered later.)

  4. Finally, let’s talk about IdleHandler. IdleHandler (IdleHandler)

How to get an instance of Message? Why obtain?

Byte asked the question.

  1. Create Message MSG = new Message.
  2. Obtain a Message object either message.obtain () or handler.obtatinMessage (). The Message reuse pool mechanism is described here. Message uses a private static Message sPool to reuse messages globally. During the recycle process, data in a Message is emptied. Prevents message from holding other objects and causing memory leaks. MAX_POOL_SIZE = 50

Why does Looper loop death not cause apps to freeze?

Handler is native layer blocking, wake (ativePollonce, nativeWake) mechanism, application stuck that is ANR (here can be developed to say ANR), conditional can say Linux underlying message mechanism (this is definitely a plus).

How to ensure a one-to-one relationship between threads and Looper?

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

Set () for looper.prepare () and sthreadLocal.get () for looper.mylooper (). A ThreadLocal contains a Map of keys and values. The key is the thread and the value is the stored value, so the thread and Looper are one-to-one.

So you might ask, why is sThreadLocal static? I’m not sure about the answer, I said in the interview that you only need to load once to reduce object creation overhead; All operations in the thread are shared. Anyone who knows why can tell us in the comments section.

How does the handler send delayed messages?

When the MessageQueue -> next function sorts the message, it calculates a nextPollTimeoutMillis value based on msg.when and the current time, which controls the message delay.

NextPollTimeoutMillis determines whether or not to block, and when to block, in three cases:

  1. = 0, no blocking, return immediately, Looper first processing message, one message is finished;
  2. Greater than 0, the maximum blocking wait time, during which a new message may return immediately (execute immediately);
  3. If it’s -1, it’s always blocked when there’s no message;

Native pollonce and nativeWake are used to block the system. If you want to block the system, you can use the following methods: I would like to check your depth, or I would like to consult some information.

What does the new Handler have to do if I want to prepare it in a child thread?

Initialize Looper in the child thread: prepare the loop, and pass in the corresponding child thread Looper object when constructing the handler.

   Thread thread = new Thread(new Runnable() {
      Looper looper;
      @Override
      public void run(a) {
          // Log.d(TAG, "click2: " + Thread.currentThread().getName());
          Looper.prepare();
          looper =Looper.myLooper();
          Looper.loop();
}
      public Looper getLooper(a) {
          returnlooper; }}); thread.start(); Handler handler =new Handler(thread.getLooper());
Copy the code

Thread. GetLooper () may be called before the looper has been initialized.

Here we need to introduce the concept of HandlerThread, which is a simple encapsulation of the Android source layer’s child thread handler:

@Override
    public void run(a) {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
Copy the code

Handler Memory leakage

I don’t remember which factory asked this question, only once was asked, low-frequency survey topic (big factory estimate all disdain to ask this).

Anonymous inner classes hold references to external activities, causing memory leaks.

How do I monitor messages in the Handler?

This is a question bybyte asked, and I’m not sure if I answered what the interviewer was looking for.

Looper has an Api: setMessageLogging, which prints a Printer for each message.

If you have other ideas, please let me know in the comments section.

Asynchronous messaging, synchronization barriers

This is also a necessary problem.

Thread messages are put into the same MessageQueue, which is mutually exclusive when retrieving messages (there is a large range of synchronized code blocks), and can only fetch messages from the header, while adding messages is sorted according to the sequence of message execution. If there is a message that needs to be executed immediately and you want that message to jump the queue, you can enable a synchronization barrier that blocks synchronous messages and only allows asynchronous messages to pass through. This is the concept of a synchronization barrier.

MessageQueue postSyncBarrier()

Note that this method is a hide method and cannot be called externally. Netease asked about this detail. What if I want to send an asynchronous message externally? (No hurry, I’ll talk about it later.)

  1. The Message object inside postSyncBarrier() is initialized without assigning a value to target, so target == null. Thus, a message with target == NULL enters the message queue.
  2. When Message Next () sorts messages, if the Message queue has synchronization barrier enabled (marked as msg.target == NULL), asynchronous messages will be processed first.

The use of asynchronous messaging in source code

ViewRootImpl scheduleTraversals ()

void scheduleTraversals(a) {
     if(! mTraversalScheduled) { mTraversalScheduled =true;
        // Enable synchronization barrier
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
        // Send asynchronous messages
        mChoreographer.postCallback(
                  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); }}Copy the code

How do I send an asynchronous message externally

  1. public Handler(boolean async)This handler will automatically add a setAsynchronous(true) asynchronous message to each message.
  2. message.setAsynchronous(true), but this method requires API 22.

IdleHandler

There is a static interface IdleHanlder in the MessageQueue class. This interface is called back when the thread is about to block, waiting for more messages. Simply put: A callback when there are no messages to process in a MessageQueue.

Function: After the UI thread has finished processing all View transactions, it calls back some additional operations without blocking the main process.

 Message next(a) {
    synchronized (this) {......if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
continue; }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCo
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }
    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);
                }
} }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
}
Copy the code

There is only one queueIdle() function in the interface, where additional operations performed when the thread is blocked can be written. If the return value is true, the IdleHandler is kept after the method is executed (the callback will be executed multiple times), otherwise the callback will be removed (it will be executed only once).

public static interface IdleHandler {
    boolean queueIdle(a);
}
Copy the code