Story collection Android Handler

Posted by mnuzum on Sun, 02 Jan 2022 22:03:23 +0100

catalogue

preface

Story introduction

The story begins

        1. Role assignment

        2. Mailing process

        3. Distribution process

        4. Receiving

follow-up

summary

preface

Today, we are directly guided by the idea of telling stories to see the source code of Handler. We hope to strengthen everyone's understanding. We don't have much time left. Let's start!

Story introduction

After typing the code for 23 hours a day, I finally saved enough 100 million. I decided to mail it back to my wife far away in my hometown and let her enjoy it. As for why 23 hours, you must leave an hour to video with my wife.

The story begins

1. Role assignment

  • Handler , courier (courier brother)
  • Looper # express company (express company)
  • MessageQueue (express warehouse)
  • Message package (100 million)

2. Mailing process

Today, on the 15th, I finally paid my salary and earned enough of my 100 million. As soon as I thought of my wife in my hometown, I felt happy and could finally send the 100 million back to her; So I went off the morning shift at 23:00, came home and pulled out my lumps of cash from under the bed. I was sure it was just 100 million, and then I fell asleep.

At 6:00 a.m., he placed an express order in the small program. The express brother also worked hard. He came at 6:30 and helped me pack and carry for two hours. After carrying, I paid the postage of 5000 yuan. The little brother drove away in a truck and pulled my 100 million back to the express warehouse for packing.

Well, all the characters are involved in the scene of the above story. Now let's enter the Handler source code to see how to interpret the above story scene.

Message message = Message.obtain();
message.obj = "a hundred million";

Handler handler = new Handler();
handler.sendMessage(message);

This is our commonly used code for Handler to send messages. Then we will follow the sendMessage method to see how to transport a billion to the warehouse.

/**
 * Pushes a message onto the end of the message queue after all pending messages
 * before the current time. It will be received in {@link #handleMessage},
 * in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean sendMessage(@NonNull Message msg) {
   return sendMessageDelayed(msg, 0);
}

Always invoke your own methods internally, and finally call the following internal methods.

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

   if (mAsynchronous) {
      msg.setAsynchronous(true);
   }
   return queue.enqueueMessage(msg, uptimeMillis);
}

It is found that the queue attribute does not exist. In fact, it is the warehouse MessageQueue. How can we prove it? Back to the previous layer, in the method calling enqueueMessage, the local variable queue is assigned by the global variable mQueue in the Handler.

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
   MessageQueue queue = mQueue;
   if (queue == null) {
     RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
         Log.w("Looper", e.getMessage(), e);
       return false;
   }
   return enqueueMessage(queue, msg, uptimeMillis);
}

Let's look at mQueue again.

final MessageQueue mQueue;

mQueue is indeed a MessageQueue (warehouse), and the Handler will deliver my 100 million messages (packages) to the warehouse directly through the Send Message method. However, we still need to see where the MessageQueue (warehouse) is built.

one.First place
  /**
     * Use the {@link Looper} for the current thread with the specified callback interface
     * and set whether the handler should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
     *
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }



two.Second place
 /**
     * Use the provided {@link Looper} instead of the default one and take a callback
     * interface in which to handle messages.  Also set whether the handler
     * should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by conditions such as display vsync.
     *
     * @param looper The looper, must not be null.
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

There are two places in total. It can be seen that the first place is the static method of Looper. After obtaining Looper, it directly assigns the mQueue of Looper to the mQueue of Handler. The second place is the Looper passed in externally. The subsequent operations are consistent, It can be seen that the MessageQueue (warehouse) is created in the Looper (express company), and it is common sense that the warehouse belongs to the express company. As for the MessageQueue in the Looper, that is, the MessageQueue object directly.

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

Where is the Looper constructor called?

  /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

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

Now you can see why we have to call the prepare method in the sub thread. We need to create a Looper object, that is, a Looper (express company) to help me send this Message (a hundred million package).

3. Distribution process

Now that Looper (express company) has it, it must start operation. After receiving my Message (a hundred million package), it must be sent to my wife. How does it work?

   /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
           
        ...

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }

The loop method is an endless loop. I will always get messages (packages) through the next method of the warehouse MessageQueue. When I get my messages (one hundred million packages), I will send them through msg.target.dispatchMessage(msg), The target is the Handler. The courier will deliver the message package (100 million) to my wife. As for the target, it is assigned when calling the enqueueMessage method when sending the message (see the code below), just like the express brother, after receiving the express package, the warehouse will definitely mark the sender as himself, but the sender here also needs the same express brother. Therefore, the logic is to find the express brother through the target marked on the package, and then distribute it through dispatchMessage(msg).

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {

        msg.target = this;
        . . . 
        return queue.enqueueMessage(msg, uptimeMillis);
    }

At the same time, through the above, we also need to understand why the loop method must cooperate with the prepare call in the sub thread. One creates a Looper (express company) and the other starts the operation of the company.

4. Receiving

Generally speaking, after two days, my wife received a hundred million yuan I sent her. She received a video of her early in the morning. My wife smiled and I was very satisfied. After cleaning up, I went downstairs to eat a hot and dry noodles. I was happy to go to the company and strive to earn another 100 million. Later, my wife bought a sports car in her hometown, repaired a luxury house and lived a happy life.

follow-up

Because my 100 million mailing is a big order for the express company. I require the fastest delivery. How can the express company ensure that my packages are delivered first and how do they rank? Now let's go to the MessageQueue (warehouse) to find the answer.

 boolean enqueueMessage(Message msg, long when) {
    ...

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

         ...
        }
        return true;
    }

There is a when key attribute in the above code, which is actually a long timestamp, Messages (parcels) are assigned a time when they enter the warehouse, and they are sorted according to the time after they are put into the warehouse. After finding the answer to the warehousing process, let's see how the parcels are delivered out in order. We learned earlier that the loop method of Looper calls the next of MessageQueue to obtain messages, so let's go directly to the next method.

    @UnsupportedAppUsage
    Message next() {
        // 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 pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        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) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

If you look at the above code carefully, you can find that it is also an dead loop through now < MSG When this condition determines whether the package time of the current message is greater than the current time. If this condition is met, it proves that the latest message (package) has not reached the sending time. Then it is necessary to calculate the difference and call the next call during the next execution of the for loop

nativePollOnce(ptr, nextPollTimeoutMillis) is a local underlying method that blocks and waits for a certain time, and then continues to run. The condition will be met and message will be returned successfully.

summary

As a courier, the Handler receives and delivers goods, that is, the entrance and exit; Loop is an express company, so it must exist and run continuously, so it must call the prepare and loop methods. As for why we don't call it in the main thread, it is because it has been called by default in the ActivityThread. Message is a package and also contains some information, such as the courier's message (Handler). As a warehouse, MessageQueue is sorted by the when timestamp when the Handler performs receipt and delivery to ensure that messages are delivered according to time.

Topics: Android Handler