A sequence.

Handler mechanism is basic Android, interview frequent. Instead of asking you to talk directly about Handler mechanics, Looper looping, MessageQueue managing messages, etc., most interviews will ask you questions based on the situation to see if you have a solid understanding of Handler mechanics.

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler What can it do? How to use it? What are the appropriate usage scenarios? What are not appropriate use scenarios? Where is it used in the Android Framework?

2. IdleHandler

2.1 Brief introduction to the Handler mechanism

Before we talk about IdleHandler, let’s take a quick look at the Handler mechanism.

Handler is a standard event-driven model. There is a MessageQueue MessageQueue, which is a priority queue based on the message trigger time, and an event loop Looper based on the MessageQueue. Messages to be processed are retrieved from MessageQueue and sent to Handler/callback for processing.

MessageQueue is managed by Looper, which creates MessageQueue synchronously during construction and binds it to the current thread using TLS such as ThreadLocal. The Looper object for the main thread of the App is already constructed and ready when the App starts, and the developer only needs to use it directly.

The Handler class encapsulates most of the “Handler mechanism” external operation interface, can use its send/ POST related methods to insert a Message queue MessageQueue. In the Looper loop, the next Message to be processed will be continuously extracted from the MessageQueue for processing.

The IdleHandler uses the associated logic in the next() method of MessageQueue to fetch the message.

2.2 What is IdleHandler? How does it work?

IdleHandler is a Handler mechanism that allows us to execute tasks when idle during the Looper event loop.

IdleHandler is defined in MessageQueue, which is an interface.

// MessageQueue.java
public static interface IdleHandler {
  boolean queueIdle(a);
}
Copy the code

As you can see, the definition needs to implement its queueIdle() method. A value of true indicates a persistent IdleHandler that will be reused, and a value of false indicates a one-time IdleHandler.

Since IdleHandler is defined in MessageQueue, using it requires MessageQueue as well. The corresponding Add and remove methods are defined in MessageQueue.

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
	// ...
  synchronized (this) { mIdleHandlers.add(handler); }}public void removeIdleHandler(@NonNull IdleHandler handler) {
  synchronized (this) { mIdleHandlers.remove(handler); }}Copy the code

You can see that add or Remove actually operate on mIdleHandlers, and its type is an ArrayList.

Since IdleHandler is executed primarily when the MessageQueue is idle, when is idle?

MessageQueue is a priority queue based on message trigger time, so there are two scenarios when the queue is idle.

  1. MessageQueue is empty with no message;
  2. The latest message to be processed in MessageQueue is a delayed message (when>currentTime), which needs to be executed late.

In both scenarios, an IdleHandler will be attempted.

The scenario for handling IdleHandler is message.next (), which fetches the next Message to be executed on the Message queue. Let’s follow the logic.

Message next(a) {
	// ...
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {
      // ...
      if(msg ! =null) {
        if (now < msg.when) {
          // Calculate the sleep time
          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
          // Other code
          // Return after message processing is found
          returnmsg; }}else {
        // No more information
        nextPollTimeoutMillis = -1;
      }
      
      if (pendingIdleHandlerCount < 0
          && (mMessages == null || now < mMessages.when)) {
        pendingIdleHandlerCount = mIdleHandlers.size();
      }
      if (pendingIdleHandlerCount <= 0) {
        mBlocked = true;
        continue;
      }

      if (mPendingIdleHandlers == null) {
        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
      }
      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }

    for (int i = 0; i < pendingIdleHandlerCount; i++) {
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; 

      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

Let’s start by explaining the main logic executed by the IdleHandler in next() :

  1. When IdleHandler is to be executed, the message to be executed is null or the execution time of this message is not expired.
  2. whenpendingIdleHandlerCount < 0When, according tomIdleHandlers.size()Assigned topendingIdleHandlerCountIt is the basis of the late cycle;
  3. willmIdleHandlersIdleHandler is copied to themPendingIdleHandlersArray, the array is temporary, and then it goes into the for loop;
  4. The loop takes the IdleHandler from the array and calls itqueueIdle()Record the return value saved tokeep;
  5. whenkeepIf is false, frommIdleHandlerTo remove the IdleHandler for the current loop, otherwise retain it;

If queueIdle() returns a value of false, the IdleHandler object in next() will loop through the IdleHandler object as the queue is idle. Remove it from the mIdleHander.

Synchronized is used to ensure thread-safety for all operations on the mIdleHandler List.

2.3 How does IdleHander ensure that it does not enter into an infinite loop?

When the queue is idle, the mIdleHandlers array is looped through and the idleHandler.queueidle () method is executed. If queueIdle() of some idlehanders returns true, it will remain in the mIdleHanders array and will be executed again next time.

Note that the code logic is still in the loop of messagequyue.next (). How does the IdleHandler mechanism ensure that it does not go into an infinite loop in this scenario?

Some articles say that IdleHandler does not loop because the next loop calls nativePollOnce() to sleep with the epoll mechanism and wakes up again the next time a new message is queued, but this is not true.

Notice the code in next() above, which resets pendingIdleHandlerCount and nextPollTimeoutMillis at the end of the method.

Message next(a) {
	// ...
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {
		nativePollOnce(ptr, nextPollTimeoutMillis);
    // ...
    // The loop executes the mIdleHandlers
    // ...
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0; }}Copy the code

NextPollTimeoutMillis determines how long the next nativePollOnce() timeout should be, and it is incorrect to say that natievPollOnce() does not go to sleep because it passes 0.

This makes sense, after all, idleHandler.queueidle () is running on the main thread, and its execution time is not controllable, so the message condition in MessageQueue may change, so it needs to be processed again.

The key to not actually doing this is pendingIdleHandlerCount, so let’s look at the code below.

Message next(a) {
	// ...
  // Step 1
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {
      // ...
      // Step 2
      if (pendingIdleHandlerCount < 0
          && (mMessages == null || now < mMessages.when)) {
        pendingIdleHandlerCount = mIdleHandlers.size();
      }
     	// Step 3
      if (pendingIdleHandlerCount <= 0) {
        mBlocked = true;
        continue;
      }
      // ...
    }
		// Step 4
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0; }}Copy the code

Let’s sort it out:

  • Step 1, before the cycle starts,pendingIdleHandlerCountThe initial value of is -1;
  • Step 2 inpendingIdleHandlerCount<0, will passmIdleHandlers.size()The assignment. That is, only the first time the loop changespendingIdleHandlerCountThe value of the;
  • Step 3, ifpendingIdleHandlerCount<=0, the loop continus;
  • Step 4, resetpendingIdleHandlerCount0;

In the second loop, pendingIdleHandlerCount is equal to 0 and does not change its value in Step 2. Then Step 3 will directly continus the next loop and there is no chance to modify nextPollTimeoutMillis.

NextPollTimeoutMillis has two possibilities: -1 or the wait interval for the next wake up, which will go to sleep when nativePollOnce() is executed and wait to be woken up again.

Next messagequeue.next () returns to looper.loop (), distributes the Message, and then loops through a new next().

2.4 How to use IdleHander in the Framework?

This is basically how to use IdleHandler and some details, let’s look at the system, where the IdleHandler mechanism is used.

Search for IdleHandler in AS.

A quick explanation:

  1. ActivityThread. Idler inActivityThread.handleResumeActivity()In the call.
  2. ActivityThread.GcIdler is forced to GC when out of memory;
  3. Instrumentation. ActivityGoing in Activity onCreate () before adding;
  4. Instrumentation.Idler call time is more, is the keyboard related call;
  5. TextToSpeechService SynthThread is after the completion of a TTS synthetic send broadcast;

Interested can chase after their own source code, these are the use of the scene, the specific use of IdleHander to do, or to see the business.

Some interview questions

So what does IdleHandler do? How does it work? What’s the problem? As well as the use of some principles of the explanation.

Here are some basic questions for you to understand.

Q: What is the use of IdleHandler?

  1. IdleHandler is an opportunity provided by the Handler to execute a task when the message queue is idle.
  2. If MessageQueue has no immediate message, IdleHandler is executed.

Q: MessageQueue provides add/remove IdleHandler methods. Do you need to use them in pairs?

  1. You don’t have to;
  2. Idlehandler.queueidle (), which can remove idleHandlers added to MessageQueue;

Q: Why not go into an infinite loop when mIdleHanders are never empty?

  1. MIdleHander is only attempted if pendingIdleHandlerCount is -1;
  2. PendingIdlehanderCount starts with -1 in next() and is set to 0 after a single execution, so it is not repeated;

Q: Is it possible to move some unimportant startup services to IdleHandler?

  1. Do not recommend;
  2. If MessageQueue is always waiting for messages to be processed, then the IdleHander will be executed later.

Q: Which thread is IdleHandler queueIdle() running on?

  1. Trap problem: queueIdle() runs on a thread that is only relevant to the current MessageQueue Looper.
  2. Child threads can also construct Looper and add IdleHandler;

Three. Summary moment

IdleHandler handler handler handler handler handler handler handler

IdleHandler is an opportunity provided by the Handler to execute a task when the message queue is idle. However, its execution timing depends on the situation of the MessageQueue, so if MessageQueue has been waiting for the message to be executed, IdleHandler will not be executed, that is, its execution timing is uncontrollable, not suitable for performing some tasks requiring high timing.

That’s all for this article. Is it helpful? If you have any questions, please leave a comment. Feel helpful don’t forget to forward, point good-looking, thank you!


Recommended reading:

  • TCP Three-way handshake four-way wave handling
  • As byteDance’s interviewer, there’s something I have to say
  • Executors abandoned by developers, what’s wrong with them?

“Growth”, will get the learning materials I prepared.