Android messaging mechanism

Message-driven is a mode in which processes or threads run. Various events, both internal and external, can be placed in message queues to be processed in order. This pattern is particularly well suited for handling a large number of interactive events.

The UI thread of Android application also adopts message-driven mode. All external keystroke events, touch screen events, various system intents and broadcasts are converted into internal messages, which are then distributed and processed in the main thread.

A message model

Today’s operating systems are generally message-driven. The Windows operating system is the classic message-driven type. However, Android’s message processing mechanism is not quite the same as Windows’s, so let’s take a look at the comparison between the two:

Above:

  • inWindowsIn the message processing model ofSystem message queueThis queue is the core of the entire process. Almost all actions are converted into messages and then put into this queue. Messages can only be processed in theThe main threadTo complete.
  • AndroidThere is no global message queue,The message queueIt’s associated with a thread. Each thread has at most oneThe message queueMessages are fetched and processed in the thread.

By comparison:

  • WindowsThe message model is relatively simple
    • Because of the global queue
      • Sending messages is simple and convenient
    • but
      • It can become a bottleneck, and if a message is not completed in a timely manner, the entire process will hang
      • Global queues, which require frequent synchronization, increase system overhead
  • AndroidThe message model of
    • It’s much more complicated
      • Must be constructed for threads before useThe message queue
      • Send messages must first getThe message queuetheHandlerobject
    • But because theThe message queueIn each thread
      • There is no additional overhead for intra-thread messages

Let’s take a look at the Android classes related to messaging, mainly:

  • LooperClass:LooperObject is a message loop handler for a thread, and only one per threadLooperObject.
    • LooperThere is an internal message queueMessageQueueMessages from all threads are stored in this queue.
    • When a new thread is created, the system does not immediately create one for that threadLooperObject that needs to be created by the program itself
    • AndroidOn startup, will beThe UI threadCreated aLooperobject
  • HandlerClass: The object isMessageThe receiver and handler of.
    • You can useHandlerObject toMessageAdd to message queue
    • throughHandlerThe callback method ofhandleMessageIn the message queueMessageFor processing
    • HandlerObject is constructed with aLooperObjects are associated together
    • HandlerandLooperIt’s many-to-one. MultipleHandlerCan be the same asLooperObjects are associated together
  • MessageClasses andMessageQueue:MessageIs the carrier of messages.
    • MessageinheritedParcelableClass, this shows thatMessageObjects can be accessed throughbinderTo send across processes
    • MessageQueuestoreMessageA linked list of objects

The relationship between these classes is shown below:

To understand which kind of

The main member variables and methods of the Looper class are as follows:

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    final MessageQueue mQueue;
    final Thread mThread;
    
    public static void prepare(a) {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {... }public static void prepareMainLooper(a) {... }public static Looper getMainLooper(a) {... }public static void loop(a) {... }}Copy the code
  • There can only be one per threadLooperClass
  • LooperClass instance objects must passprepare()create
    • prepare()Will create aLooperObject and save to a static variablesThreadLocalIn the

Note that multiple calls to prepare() in a thread will throw an exception

    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

Once an instance object of the Looper class is created, it can be obtained through myLooper()

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

The static sThreadLocal variable is of type ThreadLocal

, which implements thread-local storage by associating the object to be saved with the thread ID.

The getMainLooper() method of the Looper class returns the main thread’s Looper object.

    public static Looper getMainLooper(a) {
        synchronized (Looper.class) {
            returnsMainLooper; }}Copy the code

The prepareMainLooper() function is not intended for use by the application layer. The prepareMainLooper() function is not intended for use by the application layer.

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper(a) {
        prepare(false);
        synchronized (Looper.class) {
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

Once you have a Looper object, you can call the loop() method of the Looper class to enter the message loop.

Let’s look at the typical use of Looper on the official website

class LooperThread extends Thread {
      public Handler mHandler;

      public void run(a) {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here}}; Looper.loop(); }}Copy the code

The loop() method is used to distribute messages in the message queue. The code for this function is as follows:

    public static void loop(a) {
        final Looper me = myLooper(a);/ /... Some exception judgments about Looper objects
        final MessageQueue queue = me.mQueue;
        / /... Some thread ID related information processing
        for (;;) {
            Message msg = queue.next(a);// might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            / /...
            msg.target.dispatchMessage(msg);
            / /...
            msg.recycleUnchecked();
        }
    }
Copy the code
  • loop()The method loops fromMessageQueueThe message is fetched from the queue and then distributed
  • Message distribution passMessageThe object of thetargetThe variable is done. This variable is of typeHandler
  • MessageIs the message carrier, the sender of the message to be delivered in theMessageIn the object
  • whileMessageWhen an object is created, it needs to specify its processing objecttarget, that is,Handler

Understand the Handler class

The Handler class is responsible for sending and processing messages.

You can use only one Handler object to process all messages in a thread, or you can use more than one.

To construct a Handler object, two arguments are required:

  • The threadLooperobject
    • Used to specify which thread to give toLooperObject sending messages
    • necessary
  • A handler for the messagecallback
    • throughcallbackImplements centralized processing of messages
    • Also can put thecallbackDirectly onMessage objectIn the
    • Not necessary

Handler class is a part of the message framework, in the message definition and response design is very flexible, specific message type and response logic need to be completed in the application layer code

  • The traditionalA message modelThe types of messages that can be processed by a thread must be defined so that a user can only use them to send messages to a thread
  • AndroidThe definition and processing of messages are completely separate, and threads provide only oneThe message queueandMessage deliveryThe operating environment of

For example, the main thread of Android is implemented in the Framework, but we can use the following method to construct a message to the main thread with a callback method

public static Message obtain(Handler h, Runnable callback)
Copy the code

Thus, the callback method will be executed on the main thread

The message sending interface of the Handler class can be divided into two categories:

  • One kind issendclass
  • One kind ispostclass

However, before diving into the two interfaces of the Handler class, let’s take a quick look at the key interfaces in the MessageQueue class:

  • And when you look at it,MessageQueueClass has only one interface for inserting messages
  • isboolean enqueueMessage(Message msg, long when)

The MessageQueue class has only one interface. Where does the Handler class have so many instructions to send?

Not so fast. Let’s see what these two are?

sendThe class interface

Handler: send (); send ();

    public final boolean sendMessage(Message msg)
    public final boolean sendEmptyMessage(int what)
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    public final boolean sendMessageAtFrontOfQueue(Message msg)
Copy the code

If you just follow the code, it ends up in the enqueueMessage method in the Handler class

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code
  • The so-calledsendIn fact, it’s just theMessageInserted into theMessageQueue, and specify the processing time of the message
  • If the specified time is 0, immediate processing is required.MessageQueueIt looks up the message to the head of the queue

In addition to message parameter MSG, the insertion method in MessageQueue has only one time parameter, uptimeMillis.

Therefore, the Handler class in the interface to send messages although many, but are in the time to do action, so that the application is easy to use. The send interface is summarized as follows:

  • If you want immediate action, but do not plan to jump the queue, usesendMessage
  • If it is urgent and you want to deal with it as soon as possible, usesendMessageAtFrontOfQueue
  • If you want to delay processing for a period of time, usesendMessageDelayed
  • If you want to process at a specified time, usesendMessageAtTime
  • If the message defined is onlyMessage ID, and can be used without additional parameterssendEmptyMessage

postThe class interface

In fact, you can see what the POST interface is doing when you see the implementation

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
    / /... Omit some of the POST functions
    public final boolean postDelayed(Runnable r, Object token, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r, token), delayMillis);
    }
    public final boolean postAtFrontOfQueue(Runnable r)
    {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }
    private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }
Copy the code

From the implementation of the code

  • thesepostType methods are also usedsendMethod of type
  • Only in thesendType method addedRunnableThe parameters of the class
  • throughgetPostMessageLet’s pack one upMessageobject
  • At last,sendType to add an object toMessageQueueIn the

Here we can summarize the features of the two interfaces:

  • postThe class interface is used to send messages with processing methods (RunnableObject)
  • sendThe class interface is used to send traditional messages with message ids

Do you rememberLoopertheloopIt’s called in the loopmsg.target.dispatchMessageFunction?

Let’s look at the implementation, the comments are very detailed yo:

    // Note that there are three callbacks in this code
    public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {
            // Here is the first callback function
            // If the message contains a Runnable object
            // Execute the run function in the message directly
            / / and exit
            handleCallback(msg);
        } else {
            // Here is the second callback function
            // The Handler.Callback object that may be passed in when we create the Handler object
            if(mCallback ! =null) {
                // If the handler. Callback object is not empty, the Callback function is executed
                if (mCallback.handleMessage(msg)) {
                    // Please note here
                    // If true is returned in the Handler.Callback Callback
                    // The third callback will not be executed
                    return; }}// Here is the third callback function
            // When handler. Callback is empty
            // Or execute this function when the handler. Callback object returns false
            // This function is an empty method body
            // Subclasses of Handler can override this method to handle messageshandleMessage(msg); }}private static void handleCallback(Message message) {
        message.callback.run();
    }
    public void handleMessage(Message msg) {}Copy the code

From the dispatchMessage code:

  • The message is given priority to the callback method inherent in the message
  • Otherwise, ifHandlerA callback method is defined that is called first for processing
  • ifHandlerThe callback method is not processed and will be calledHandlerTheir ownhandleMessage

MessageQueueClass foreshadowing content

This section complements the study of MessageQueue class. It’s not much, but focuses on a setAsynchronous function of the Message class.

If you don’t know the setAsynchronous function of the Message class, you might be confused by some of the logic behind the MessageQueue class

In the Android Message class, there is a setAsynchronous(Boolean async) method that sets a flag bit as follows:

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else{ flags &= ~FLAG_ASYNCHRONOUS; }}Copy the code

So how do I use this function?

  • inMessageQueueThere is a method calledpostSyncBarrier()
    • Calling this method inserts a null in the message queueHandlerObject messages
    • There is noHandlerObject is calledSyncBarrier
  • MessageQueueWill pause processing in the queueSyncBarrierFuture news
    • It’s like a group of people waiting in line to buy a ticket, and someone puts a sign in the lineFrom here, stop selling
  • However, if there are still messages that need to be processed, you can use itsetAsynchronous(boolean async)Method to mark the message
  • MessageQueueWhen this flag is detected, the message is processed normally
  • Other unmarked messages are paused until they passremoveSyncBarrierRemove theSyncBarrier

Good drop, foreshadowing completed, continue to learn ~

Analysis of theMessageQueueclass

MessageQueueClass constructor

Let’s first look at the constructor of the MessageQueue class

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
Copy the code

Very succinct… :

  • MessageQueueObject is constructed to call local methodsnativeInit()To complete the
  • nativeInit()Method correspondingJNIThe function isNative layertheandroid_os_MessageQueue_nativeInit()function
    • I still call itNative layerOh, that’s really bad

Android_os_MessageQueue_nativeInit ()

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(a);/ /...
}
Copy the code

The android_os_MessageQueue_nativeInit() function creates a local NativeMessageQueue object. Let’s look at the NativeMessageQueue constructor again:

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread(a);if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper); }}Copy the code
  • NativeMessageQueueThe constructor simply creates a localLooperClass objects.

From the code of the NativeMessageQueue class, it is essentially a proxy class. It turns calls to the Java layer into calls to the Looper class of the Native layer

The Looper class in the Native layer also implements a complete message processing mechanism. But there is no direct relationship between the Java layer Looper class and the Native layer Looper class.

MessageQueue uses the Looper class of the Native layer, but only uses its wait/wake mechanism. The rest, such as message queuing, is implemented in the Java layer.

Therefore, for JNI calls in MessageQueue, we can directly analyze the Looper class of the Native layer. Let’s first briefly look at the constructor of the Looper class of the Native layer:

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(- 1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    // Create an eventfd object by calling eventfd
    // This function creates a file descriptor for event notification.
    // Set the flag to
        // EFD_CLOEXEC: the file is set to O_CLOEXEC and does not inherit the parent's file descriptor when the child process (fork) is created.
        // EFD_NONBLOCK: the file is set to O_NONBLOCK and is not blocked when read/write operations are performed.
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    / /... Omitting exception judgment
    // Listen for the mWakeEventFd file descriptor with the epoll operation
    rebuildEpollLocked(a); }void Looper::rebuildEpollLocked(a) {
    // Close old epoll instance if we have one.
    / /... Ellipsis operation
    // Allocate the new epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    / /...
    // Some object data to populate
    struct epoll_event eventItem;
    memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    // Add epoll listener
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    / /...
}
Copy the code

In the above code, the Looper class constructor does two things:

  • Create a file descriptor for event notification,eventfdobject
  • useepollTo listen for data

So much for the constructor of the MessageQueue class

MessageQueueMessage processing in

MessageQueue message loop in the next() method, the code is a bit long, first briefly describe the function:

  1. Check whether the first message in the queue isSyncBarrierThe message
    • If so, look for the queue marked asFLAG_ASYNCHRONOUSThe first message found as the current processing message
    • If not, the first message in the current queue is taken as the current processing message
  2. If the currently fetched message is notNULLTo check whether the processing time of the message has timed out
    • If there is no timeout, calculate the wait time
    • If the time is up,next()Method returns the message and exits
  3. If the retrieved message isNUll, indicating that there are no messages in the queue that can be processed.
    • Set the wait time to- 1Wait forever
  4. Check for exit flags in queues
    • If an exit flag is detected, the object created in the Native layer is destroyed, and thennext()Method of
  5. Check for presenceIdleHandlerThe callback function of
    • If not, continue the loop throughnativePollOnce()Method suspends the thread and waits for a new message to arrive
    • If there is
      • Call allIdleHandlerThe callback function of
      • For the returnfalseIs removed from the queue after the call completes
      • Finally, theepollThe wait time is set to0

Before looking at the next() code, let’s look at the flow of calling the nativePollOnce() method:

  • nativePollOnce()Call theNativeMessageQueue::pollOnce
  • NativeMessageQueue::pollOnceCall theLooper::pollOnce
  • Looper::pollOnceCall theLooper::pollInner
  • In the end,Looper::pollInnerCall theepoll_wait

Let’s look at the code for calling epoll_wait:

/ /...
    // We are about to idle.
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;
/ /...
Copy the code
  • callepoll_waitBlocks the current thread
  • When data comes in or a timeout period is reached,epoll_waitWill return

Let’s look at the next() code:

    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 pendingIdleHandlerCount = - 1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands(a); }// Call a local method and wait for nextPollTimeoutMillis in milliseconds
            // -1 means permanently blocked
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // Synchronization is used here
            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis(a); Message prevMsg = null; Message msg = mMessages;if(msg ! = null && msg.target == null) {// Stalled by a barrier. Find the next asynchronous message in the queue.
                    // SyncBarrier is marked by a message target of null
                    // If the first message in the queue is SyncBarrier
                    // Ignore the normal message and look for the first asynchronous message
                    // that is a setAsynchronous(true) message
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous());
                }
                if(msg ! = null) {// Find the message, the first time to determine the time
                    if (now < msg.when) {
                        // It is not time to process the message. Calculate how long you need to wait
                        // 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.
                        // Cancel the blocking flag
                        mBlocked = false;
                        // Fetch the message from the queue
                        if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        // The tag uses and returns a message
                        msg.markInUse(a);returnmsg; }}else {
                    // Indicates that there are no messages in the queue that need to be processed
                    // No more messages.
                    nextPollTimeoutMillis = - 1;
                }
                if (mQuitting) {
                    // If the exit flag is set, the native object is destroyed and returned
                    dispose(a);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.
                // Check whether IdleHandler is installed
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size(a); }if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                // Put Idle handlers in the mPendingIdleHandlers array
                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.
            // All Idle handlers are processed. If the callback result is false, no further processing is performed
            // Remove the Idle Handler from the list
            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 {
                    // You can register an IdleHandler callback on the main thread
                    // The callback is delayed by 10 seconds to see if there is anything strange, hahaha
                    // You can also try to pop a window or something, you will be surprised
                    keep = idler.queueIdle(a); }catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if(! keep) {// This is removed
                    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; }}Copy the code

toMessageQueueSend a message

The enqueueMessage() function is used to send messages to MessageQueue.

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            // If target is null, no Handler is specified and an exception is thrown
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            // If the message to be inserted is in use, the message is added repeatedly, and an exception is thrown
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        // Add a synchronization operation
        synchronized (this) {
            if (mQuitting) {
                // If it is already in the exit state, return false and print a warning message
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(a);return false;
            }
            // Do some state processing with insert messages
            msg.markInUse(a); msg.when = when; Message p = mMessages;// Whether a wakeup flag is required
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // p == null indicates that there is no message in the queue
                / / or when the = = 0 | | the when < p.w hen) shows that the current message need to insert into the head
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // Set whether to wake up based on the blocking state of next()
                // If the thread is blocked, it needs to wake up
                needWake = mBlocked;
            } else {
                // There are messages in the queue
                // Only in the header p.target == null, SyncBarrier
                MSG. IsAsynchronous ()
                // You need to wake up
                needWake = mBlocked && p.target == null && msg.isAsynchronous(a);// Find the right place to insert the message
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // Note here that this is only done before the message is inserted
                    // If a message is already set to asynchronous before the message is inserted
                    // This message does not need to be woken up
                    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) {
                // Wake up the thread operation
                nativeWake(mPtr); }}return true;
    }
Copy the code
  • enqueueMessageMethod inserts messages in order of time, the earliest before
  • The message queueThe organizational structure of theMessageIn the classnextPointers form a one-way linked list
  • When inserting a message, it calculates whether it needs to wake up the thread,enqueueMessageYou want to avoid waking up the processing thread as much as possible
    • The thread wakes up after inserting a message to be processed immediately
    • inSyncBarrierState inserts another entryAsynchronous messagingThe thread is then woken up

Finally, let’s look at the operation to wake up the thread:

void Looper::wake(a) {
    / /... Remove some debug code
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    / /... Remove some debug code
}
Copy the code
  • TEMP_FAILURE_RETRYJust ado.. whileMacro definition, the core is the system callwritefunction
  • wake()By system callwriteWrite data to wake up the message processing thread
  • Processing thread passepollListening to theeventfdThe data on the
  • As soon as the data arrives, the thread wakes up,next()Method continues processing the message

Here we string together the MessageQueue part:

  1. inLooperClass, passLooper.prepare,Looper.loopTo initialize and start the messaging service, which includes:
    • MessageQueueThe initialization
    • Create an infinite loop to process the message
    • Cycle throughMessageQueuethenext()Function fetch message
    • next()Waits and executes based on the status of messages in the queueepoll_waitThe operation blocks the thread waiting for a message to arrive at a notification
  2. HandlerClass throughsend*orpost*To complete the message sending, which includes:
    • The real execution is throughenqueueMessageInsert the message
    • Need to wake up case via system callwriteTo wake up the thread
  3. And forMessageQueueThe state that we can passIdleHandlerTo listen for whether the queue is empty

Interprocess messaging

Android messages can be passed between processes. Of course, interprocess messaging is based on Binder communication.

We know that with a Binder reference object you can make remote calls. If you want to send messages to another process’s Handler in Android, you must use a Binder proxy object to do so.

The Handler method getIMessage() creates a Binder object:

    final IMessenger getIMessenger(a) {
        synchronized (mQueue) {
            if(mMessenger ! = null) {return mMessenger;
            }
            mMessenger = new MessengerImpl(a);returnmMessenger; }}Copy the code

The object type created in getIMessenger is MessengerImpl, a Binder service class inherited from the IMessenger.Stub class. The send() method in MessengerImpl sends a message to the Handler. The code is as follows:

    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid(a); Handler.this.sendMessage(msg); }}Copy the code

Okay, we have the interface.

  • callHandlerThe object’sgetIMessengerThe method will give you thisHandlermessage-sendingBinder object

But to send messages across processes, how do you get the Binder object in the calling process? Let’s take a closer look at the Messenger class

understandMessengerclass

The Messenger class is essentially a wrapper around the IMessenger object that contains a MessengerImpl associated with the Handler object.

The class information is as follows:

public final class Messenger implements Parcelable {
    private final IMessenger mTarget;
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }
    / /... Omit some methods
    // Note that this constructor is used in bindService
    public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }}Copy the code
  • From the code,MessengerOr use ofBinderThe logic of communication
    • To achieve theParcelableInterface,BinderWhen communicating, it can be transmitted directly
    • whileMessengerholdHandlertheIMessengerobject
    • In this way,The calling processAfter gettingMessengerObject is also obtainedHandlerThe ability to send messages
  • useMessengerThe advantage is hidden from use in communicationBinderThe details make the whole process seem as simple as sending a local message

Let’s look at a simple example:

  1. Define a remoteService, remember to configureandroid:processProperties,
public class RemoteService extends Service {
    private static final String TAG = "HuaLee";
    private static final Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.d(TAG, "handleMessage=" + msg);
            return true; }});private static final Messenger messenger = new Messenger(mHandler);

    @Override
    public IBinder onBind(Intent intent) {
        returnmessenger.getBinder(); }}Copy the code
  1. Looking for aActivity, bind the service, and convert toMessengerobject
    private Messenger mRemoteMessenger = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        / /...
        Intent intent = new Intent(this, RemoteService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d(TAG, "onServiceConnected");
                mRemoteMessenger = new Messenger(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
    }
Copy the code
  1. That way, we can get throughMessengerObject to send a message to the server
    Message msg = Message.obtain();
    mRemoteMessenger.send(msg);
Copy the code

Is there an example that makes it easier to understand? Ha ha ha

If you only need a simple message exchange between two processes, this will suffice. For the convenience of applications, Android also provides the AsyncChannel class to establish two-way communication.

Set up a communication channel – the AyncChannel class

AyncChannel classes for the upper application is a hidden, source path in: frameworks/base/core/Java/com/android/internal/util/AsyncChannel Java. Is used to establish a communication channel between two handlers. These two handlers can be in one process or in two processes.

The status of the communication parties established through the AyncChannel class is not equal

  • One party is going to beServiceAnd respond toAyncChannelA message defined in a class
  • The other party acts asclientTake the initiative to connect with each other

The first step in using the AyncChannel class is to determine whether the communication parties are in semi-connected mode or fully connected mode

  • Semi-connected modeAfter the connection is established, onlyThe clientTake the initiative to giveThe service sideSend a message,The service sideUpon receipt ofThe clientAfter the message, use the attached messageMessenger objectTo giveThe clientA reply message
  • Full connection modeBoth parties can actively send messages

Obviously, fully connected mode consumes more system resources than semi-connected mode.

Semi-connected mode

The AyncChannel class provides a number of connection methods that the client needs to use as needed. But before you can connect, you still need to get the Messenger object on the server.

As mentioned earlier, Binder delivery for the Java layer can be done in two ways:

  • One is through what’s already establishedBinderChannel to passBinderobject
  • One is through componentsServiceTo obtain aServiceIncluded in theBinderobject

The AyncChannel class supports both approaches.

If the client and server have a Binder channel established, the Messenger objects on the server can be passed to the client through this channel. We can then use the AyncChannel class’s Connect method to establish a connection between the two:

    public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
        // We are connected
        connected(srcContext, srcHandler, dstMessenger);
        // Tell source we are half connected
        replyHalfConnected(STATUS_SUCCESSFUL);
    }
Copy the code
  • srcContext:The clientThe context in which the
  • srcHandler:The clienttheHandlerobject
  • dstMessengerFrom:The service sidePass to theMessengerobject

The AyncChannel class also defines a very simple handshake protocol for the communicating parties

  • connectMethod will callreplyHalfConnectedmethods
  • replyHalfConnectedMethod will sendCMD_CHANNEL_HALF_CONNECTEDA message toThe clientthesrcHandler
    • How is theThe clientthesrcHandler? Should not beThe service side?
      • First of all,Handshake protocolIs to giveFull connection modePrepared, forSemi-connected modeThe handshake protocol is strictly enforced only in exceptional circumstances
        • In general,Semi-connected modeAs long as you get the other side’sMessengerIt doesn’t matter if you shake hands or not
        • butFull connection modeYou must make sure that both parties are ready before you can communicate, so a simple handshake is required
      • Therefore, in theSemi-connected mode,srcHandlerObject does not need processingCMD_CHANNEL_HALF_CONNECTEDThe message
      • Semi-connected modeAfter callingconnectMethod and you can start sending messages

If the client and server do not have existing Binder channels, a Binder channel can be created by starting the component Service. Follow the example of the Messenger class above.

If you don’t want to rewrite a Service, Andorid provides an abstract class AsyncService that the server can inherit to create a Service. In this case, the client needs to use another connect method of the AsyncChannel class to establish a connection:

    public void connect(Context srcContext, Handler srcHandler, String dstPackageName,String dstClassName) {
        / /...
        final class ConnectAsync implements Runnable {
            / /...
            @Override
            public void run(a) {
                int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,
                        mDstClassName);
                replyHalfConnected(result);
            }
        }
        ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
        new Thread(ca).start();
        / /...
    }
    public int connectSrcHandlerToPackageSync( Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
        / /...
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName(dstPackageName, dstClassName);
        boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
    }
    class AsyncChannelConnection implements ServiceConnection {
        / /...
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mDstMessenger = new Messenger(service);
            replyHalfConnected(STATUS_SUCCESSFUL);
        }
        @Override
        public void onServiceDisconnected(ComponentName className) { replyDisconnected(STATUS_SUCCESSFUL); }}Copy the code

The key methods are listed in the code above. The process of using a Service is as follows:

  • connectMethods will be passed inThe package nameandThe name of the classTo start theService, the startup time may be relatively long, thereforeconnectMethod to create a thread to start:new Thread(ca).start();
  • run()Method is calledconnectSrcHandlerToPackageSyncMethod, it callsbindServiceTo start a componentService
  • ServiceAfter startup, useServiceConnectionThe implementation class of the interfaceAsyncChannelConnectionTo receive theServicePassed onBinderobject
  • Then, inonServiceConnectedIn theBinderObject wrapped asMessengerobjectmDstMessenger
    • Semi-connected modeNext to theThe service sideTo send a messagemDstMessengerobject

Note that replyHalfConnected will be executed twice during this process.

  • connectSrcHandlerToPackageSyncIn thereplyHalfConnectedMainly for binding failure after the prompt

In this case, the client’s Handler object must receive the CMD_CHANNEL_HALF_CONNECTED message to indicate that the communication has been successfully established.

I don’t think it’s necessary to use AsyncService, so I’m going to use Service instead. Write your own more relief, ha ha ha

Full connection mode

Fully connected mode is based on semi-connected mode:

  • whenThe clienttheHandlerObject receives messageCMD_CHANNEL_HALF_CONNECTEDafter
  • If you want to establishAll connection.The clientNeed to:
    • toThe service sidesendCMD_CHANNEL_FULL_CONNECTIONThe message
    • At the same time withThe clienttheMessengerobject
      • MessageObject has areplyToha
  • The service sideAfter receiving the message
    • toThe clientreplyCMD_CHANNEL_FULLY_CONNECTED
    • ifThe service sideAgreed to establishAll connectionWill take the first argument of the messagemsg.arg1Is set to0Otherwise, it is set toNon-zero value.

The following isFull connection modeMessage interaction diagram of

WifiService and WifiManager(client) in Android are taken as examples to understand the establishment process of the full connection mode:

Wifi this part of the details is still a lot of, we just briefly introduced the client and server Handler communication process ha

The service sideandThe clientOf the twoHandler

  1. First of all, asThe clienttheWifiManagerDefined in theHandlerAs follows:
private class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            synchronized(sServiceHandlerDispatchLock) { dispatchMessageToListeners(message); }}private void dispatchMessageToListeners(Message message) {
            Object listener = removeListener(message.arg2);
            switch (message.what) {
                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                    // The half-connection part is successfully connected
                    if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                       // Send a full connection request
                       mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                    } else {
                        Log.e(TAG, "Failed to set up channel connection");
                        // This will cause all further async API calls on the WifiManager
                        // to fail and throw an exception
                        mAsyncChannel = null;
                    }
                    mConnected.countDown();
                    break;
                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
                    // Ignore
                    break;
                / /...}}}Copy the code
  1. As aThe service sidetheWifiServiceIn the classHandlerThe definition is as follows:
    private class ClientHandler extends WifiHandler {
        / /...
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                        Slog.d(TAG, "New client listening to asynchronous messages");
                        // We track the clients by the Messenger
                        // since it is expected to be always available
                        mTrafficPoller.addClient(msg.replyTo);
                    } else {
                        Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
                    }
                    break;
                }
                / /...
                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
                    // After receiving a full connection request from the client, create an AsyncChannel object
                    AsyncChannel ac = mFrameworkFacade.makeWifiAsyncChannel(TAG);
                    // Establish a connection based on the Messenger object carried by the client message
                    ac.connect(mContext, this, msg.replyTo);
                    break;
                }
                / /...}}Copy the code

According to the above two handlers, we can deduce the flow as follows:

  • WifiManagerAs aThe clientIn return for theHalf a connectionAfter, a message is sentCMD_CHANNEL_FULL_CONNECTIONThe news of the
  • WifiServiceAs aThe service side, after receiving theCMD_CHANNEL_FULL_CONNECTIONafter
    • So let’s create oneThe service sideTheir ownAsyncChannelobject
    • Then throughAsyncChannelThe object’sconnectMethod to disconnectThe client
    • And then,connectThe method will giveThe service sideTheir ownHandlerObject sends aCMD_CHANNEL_HALF_CONNECTEDThe message
  • WifiServiceAs aThe service side, after receiving theCMD_CHANNEL_HALF_CONNECTEDafter
    • theThe clientSent overMessengerObject to a member variablemTrafficPollerIn the
    • mTrafficPollerObject that holds oneMessengertheListA collection of
  • Please note that,WifiServiceThere was no reply.CMD_CHANNEL_FULLY_CONNECTED. It’s not a surprise, it’s not a surprise

Sending synchronization messages

The sendMessageSynchronously() method of the AyncChannel class sends synchronization messages. The sendMessageSynchronously() method suspends the thread to wait after sending a message, and resumes the thread after receiving the reply.

The core code for the AyncChannel class sendMessageSynchronously() is as follows:

    private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
            SyncMessenger sm = SyncMessenger.obtain();
            Message resultMsg = null;
            try {
                if(dstMessenger ! =null&& msg ! =null) {
                    // Note that the replyTo is set to the SyncMessenger object
                    msg.replyTo = sm.mMessenger;
                    synchronized (sm.mHandler.mLockObject) {
                        if(sm.mHandler.mResultMsg ! =null) {
                            Slog.wtf(TAG, "mResultMsg should be null here");
                            sm.mHandler.mResultMsg = null;
                        }
                        // Send a message to the server
                        dstMessenger.send(msg);
                        // Call wait to wake up
                        sm.mHandler.mLockObject.wait();
                        resultMsg = sm.mHandler.mResultMsg;
                        sm.mHandler.mResultMsg = null; }}}/ /...
            sm.recycle();
            return resultMsg;
    }
Copy the code
  • createSyncMessengerobject
  • willMessage.replyToSet toSyncMessengerobject
    • The receipt of the message is then forwarded toSyncMessengerThe object of the
  • Send a message and callwait()Method suspends the thread

The reply message sent by the server is processed in SyncHandler, which is defined in SyncMessenger:

        private class SyncHandler extends Handler {
            /** The object used to wait/notify */
            private Object mLockObject = new Object();
            /** The resulting message */
            privateMessage mResultMsg; /.../** Handle of the reply message */
            @Override
            public void handleMessage(Message msg) {
                Message msgCopy = Message.obtain();
                msgCopy.copyFrom(msg);
                synchronized(mLockObject) { mResultMsg = msgCopy; mLockObject.notify(); }}}Copy the code

The processing logic of this part is also relatively simple:

  • Saves the received message to a variablemResultMsgIn the
  • callnotify()Method to wake up a thread

At this point, threads suspended by wait() are awakened and returned to resultMsg

Concepts related to Android synchronization

This part of the book added here feel a little bit inconsistent, multithreading and synchronization related knowledge is very much, first pick a part of the concept of learning, Java part of the multithreading has been completed, but not all, and then separate comb

As mentioned earlier, Binder threads are automatically generated when Android applications run, so multiple threads are running in an Android application even if the upper application code does not create any threads.

This will inevitably encounter the problem of resource competition. Threads in Linux operate in preemptive mode. Therefore, the synchronization mechanism provided by the system is required to ensure thread safety for accessing shared resources.

Although the synchronization mechanism can solve the problem of resource access conflict, it inevitably brings performance loss. Therefore, the synchronization mechanism should be avoided as far as possible without affecting the correctness.

Atomic operation

Even some simple operations, such as addition and subtraction, require multiple instructions at the assembly level when operating on global variables of simple types.

The completion of the entire operation process requires:

  • First read the value in memory
  • Computes in the CPU
  • And then write it back to memory

If a thread switch occurs and the value in memory is changed, the result of the final execution will be different from the expected result. We can solve this problem by adding locks; But the best way to avoid this problem is to use atomic manipulation

Android implements a set of atomic manipulation functions in assembly language, which are widely used in the implementation of synchronization mechanisms

Android atomic manipulation functions

This part of the function in the system/core/libcutils/include/atomic. H is defined

Let’s look at the functions related to atomic variable manipulation:

// Atomic variable increment operation
int android_atomic_inc(volatile int32_t* addr);
// Decrement of atomic variables
int android_atomic_dec(volatile int32_t* addr);
// Atomic variable addition operation
int android_atomic_add(int32_t value, volatile int32_t* addr);
// Atomic variables and operations
int android_atomic_and(int32_t value, volatile int32_t* addr);
// The or operation of an atomic variable
int android_atomic_or(int32_t value, volatile int32_t* addr);
// Atomic variable read operation
int android_atomic_acquire_load(volatile const int32_t* addr);
int android_atomic_release_load(volatile const int32_t* addr);
// Set the atomic variable
void android_atomic_acquire_store(int32_t value, volatile int32_t* addr);
void android_atomic_release_store(int32_t value, volatile int32_t* addr);
// Compare and swap atomic variables
int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue,volatile int32_t* addr);
int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue,volatile int32_t* addr);
// Macro definitions for two atomic variables
#define android_atomic_write android_atomic_release_store
#define android_atomic_cmpxchg android_atomic_release_cas
Copy the code

The way atomic operations are implemented is closely related to the CPU architecture, and atomic operations are now generally implemented at the CPI instruction level. This method is not only simple, but also very efficient.

Memory barriers and compilation barriers

Background knowledge

  1. In modern CPUS, the order of instruction execution is not necessarily in the order written by the developer. Instructions without correlation can be executed in disordered order to make full use of the CPU’s instruction pipeline and improve execution speed. useThe memory barrierCountermeasures to
  2. The compiler itself also optimizes instructions. For example, adjust the instruction sequence to take advantage of the CPU’s instruction pipeline. useCompile the barrierCountermeasures to

These are actually out-of-order memory accesses. The reason for out-of-order memory access behavior is to improve the performance of program runtime. For out-of-order execution, we can refer to Wikipedia

However, for some programs with strict requirements on the order of execution, out-of-order execution may have disastrous consequences.

This situation requires us to do some special treatment.

The concept description

Also called memory barrier, memory gate barrier, barrier instruction, etc. It allows the CPU or compiler to perform operations on memory in a strict order, which means that instructions before and after a memory barrier cannot be out of order due to system optimization.

Most modern computers execute out of order to improve performance, which makes memory barriers necessary.

Semantically, all writes prior to the memory barrier are written to memory; Any read operation after the memory barrier can obtain the result of any write operation before the synchronous barrier. Therefore, for sensitive blocks, memory barriers can be inserted after write operations but before read operations.