Android messaging

This article source code Api28

Android automatically creates a looper when creating the main thread, so we don’t need to create it ourselves.

The ActivityThread class, called by AMS during the Android app startup process, has its main entry method:

public static void main(String[] args) {... Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if(args ! =null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if(args[i] ! =null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
    	
    	/ / note:
        thread.attach(false, startSeq);

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

It calls two Looper methods, looper.prepareMainLooper and looper.loop ().

Looper

Look at the prepareMainLooper

    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

Here, Looper is called using one of two methods: prepare, which determines whether sMainLooper is null, and assignment, which ensures that it is prepareMainLooper only once.

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

So we can just look at prepare:

    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

As you can see, you’ve actually done two things in preparing

  1. A thread can only have one looper. This means that a thread cannot create a looper by calling prepare more than once.
  2. If there is no looper in the thread, then a new looper sets it to ThreadLocal.

ThreadLocal

Mention ThreadLocal, by the way

ThreadLoacl is an internal data store class that can store data in a specified thread. The data store is stored in the specified thread, and the data store is not accessible to other threads.

It uses set to store data and GET to retrieve data.

We can do an experiment in code:

    final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    threadLocal.set(1);
    new Thread(new Runnable() {
        @Override
        public void run(a) {
            Log.d(TAG, "run: "+threadLocal.get());     
        }
    }).start();
Copy the code

/com.apkcore.studdy D/MainActivity: run: null

Space is limited for the internal code of ThreadLocal, but we’ll look at it in more detail next time.


Moving on to Looper, we have seen that in prepare, if no Looper object is present in the thread, a new Looper is created and added to ThreadLocal. Look at its constructor

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

In the constructor, a MessageQueue MessageQueue is instantiated and the current thread is retrieved and assigned to the mThread. That is, the message queue is now bound to the current thread and its region is the thread that is currently instantiating looper.

Then we can look at the looper.loop () method called by Main

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

public static void loop(a) {
    // Get a looper object. Prepare must precede loop()
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }...for (;;) {
        // Fetch the message from MessageQueue. If the message is empty, break out of the loop
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return; }.../ / if the news is not null, then the MSG to MSG. Target. DispatchMessage (MSG) to deal with
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if(traceTag ! =0) { Trace.traceEnd(traceTag); }}...// Method collectionmsg.recycleUnchecked(); }}Copy the code

We removed some code that wasn’t particularly important for our analysis, commented out the key code, which basically gets the current Looper object, and goes into an infinite loop that keeps getting messages from the message queue, and if it’s null it breaks out of the loop, if it’s not null, Then give the message to MSG. Target. DispatchMessage (MSG), MSG. The target will see at the back, it is actually a Handler,’ll analysis. Finally call the recyleUnchecked method to recycle.

Queue.next () : messageQueue: messageQueue: messageQueue

Handler

When we use Handler

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Log.d(TAG, "handleMessage: 0"); }}};Copy the code

This is usually the case, and of course there is the possibility of memory leaks when an Activity is directly used in this way, but that is not the focus of this section.

So let’s start with the constructor of handler

    public Handler(a) {
        this(null.false);
    }

    public Handler(Callback callback, boolean async) {... 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;
    }
Copy the code

From the constructor’s point of view, we get the myLooper method of Looper, fetch the Looper object from ThreadLocal, and then determine whether the object is empty. If it is empty, we throw an exception. If it is not empty, looper’s messageQueue is retrieved, and a reference to messageQueue is held in the Handler.

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)}.
Copy the code

The comment details the mAsynchronous parameter, which is a synchronization barrier. Asynchronous messages are not limited by the introduced synchronization barrier, which is the difference between synchronous and asynchronous messages. We’ll talk about that later.

When we use handler to send messages, we usually use sendMessage, such as

new Thread(new Runnable() {
    @Override
    public void run(a) {
        try {
            Thread.sleep(5000);
            mHandler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
Copy the code

Let’s ignore the direct new Thread problem in the code and continue tracing the source code

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

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

    public boolean sendMessageAtTime(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);
    }
Copy the code

As you can see, it’s all layer upon layer, finally calling into the sendMessageAtTime method. EnqueueMessage: queue, MSG, uptimeMillis: queue, MSG, uptimeMillis: queue, MSG, uptimeMillis

    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

In this enqueueMessage method, pay attention! We see the familiar word MSG. Target, in the Handler constructor, mQueue = mlooper.mqueue; , introduced to enqueueMessage method is that the value of the MSG. The target assignment to the Handler, in which, give the news to MSG. Target. DispatchMessage (MSG) to deal with, is in the hands of the Handler to deal with.

This means that the Handler sends all messages to MessageQueue and calls enqueueMessage. The Looper loop calls MessageQueue’s next method. Send this message to dispatchMessage for processing

So the dispatchMessage method

    public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {
            handleCallback(msg);
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code

If MSG calls callback, then only that callback will be called. If not, then the constructor will call that callback. If neither is set, then the constructor will call that callback. The handleMessage method in the overridden Handler is called

Let’s inherit the enqueueMessage method

    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

As you can see here, queue. EnqueueMessage (MSG, uptimeMillis) is finally called; MessageQueue

MessageQueue

MessageQueue is generated in the Looper constructor

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

Let’s move on to its enqueueMessage method

boolean enqueueMessage(Message msg, long when) {... 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.
                // New messages are inserted into the head of the linked list, which means that the queue needs to adjust its wake up time
                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.
                // New messages are inserted inside the linked list. In general, there is no need to adjust the wake time
                // But still consider when the table header is "synchronous split column case"
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // note that isAsynchronous is at the top if (mAsynchronous) {msg.setasynchronous (true); } There is a setting, default false
                    if (needWake && p.isAsynchronous()) {
                        // When MSG is asynchronous, it is not the first asynchronous message in the list, so there is no need to wake up
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr ! = 0 because mQuitting is false.
            if(needWake) { nativeWake(mPtr); }}return true;
    }
Copy the code

The key places of the source code have been annotated, but do not understand the synchronization segmentation bar, which will be talked about in a moment. Insert the message node at the right place in the message list. If message is inserted into the table header, it means that the latest wake time is also adjusted. Set needWAKE to true. In order to walk to nativeWake(mPtr);

Synchronous split bar

The Handler in Android synchronizes the split bar

Handler synchronization barrier

A synchronous partition bar can be understood as a special Message whose target field is NULL, which cannot be passed into a queue through methods such as sengMessageAtTime, but only through postSyncBarrier() of Looper.

role

It is stuck somewhere in the list of messages, and when an asynchronous thread can set an asynchronous block in a Handler, no synchronous Message from the current Handler will be executed until the asynchronous thread removes the block. This is the difference between synchronous and asynchronous messages in Android messaging, which means that if there is no synchronous split bar in the message list, it will be treated the same.


Continue to look at the nativeWake () method, this is a native method and corresponding in c + +, interested friends, can find framworks/base/core/jni/android_os_MessageQueue CPP to view its source, here I am not analyzed, The main action is to write W to the writing end of a pipe.

Back at the top, we are talking about the source of looper.loop, and we have left msg.next() unparsed

    Message next(a) {...int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            // block here
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                // Get the next message and return it if available
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // Try to get the first message currently in the message queue
                Message msg = mMessages;
                if(msg ! =null && msg.target == null) {
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                    // If the MSG in the queue is the synchronous split bar, then look for the first asynchronous message after it
                    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.
                        // The next message is not ready. Set a timeout to wake up when 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 {
                            // Reset the header of the message queue
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;/ / return}}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.
            // Handles the idleHandlers part, and handlers are idle
            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; }}Copy the code

Source code comments have to speak in both English and Chinese are straightforward, I just literally translated, the for loop in this function is not circular node gather news role, but for the sake of coherent event queue first message really to time, if arrive, will directly back to this MSG, if time hasn’t arrived yet, After calculating an exact wait time (nextPollTimeoutMillis), the for loop calls nativePollOnce(mPtr, nextPollTimeoutMillis) again to block and wait for an appropriate time.

The above code also handles the “synchronous split bar”, which should never be returned if it is in the queue, instead trying to find the first asynchronous message after it.

Another important thing in Next is the IdleHandler. When the message queue is idle, it determines whether the user has set up an IdleHandler and, if so, attempts to process it, calling the queueIdle() function of the IdleHandler in turn. This feature can be used for performance optimizations such as lazy loading: In order to optimize the display speed of an Activity, we may put some time-consuming tasks that are not necessary to start at the first time after the interface loading is completed. However, even after the interface loading is completed, time-consuming tasks will occupy CPU as well. When we are in operation, it may also cause the phenomenon of lag. Then we can use Idle Handler to do the processing.

nativePollOnce

The next call to nativePollOnce blocks to ensure that the message loop does not stay in the loop when there is no message processing. It is implemented in android_OS_messagequeue.cpp. Interested in the children’s shoes will be allowed in the frameworks of/base/core/jni/android_os_MessageQueue CPP in view.

In c++, pollonce calls the c++ layer looper object, which is different from our Java looper object. In addition to creating a pipe, pollonce also creates an epoll to listen on the reading side of the pipe.

extension

Looper.loop() does not block the main thread on Android. This article goes into more detail about why we can loop indefinitely on the main thread without getting stuck.

Since a thread is a piece of executable code, when the executable code is completed, the thread life cycle is terminated and the thread exits. For the main thread, we do not want to be run for a period of time, and then exit, so how to ensure that it is always alive? Binder threads, for example, also use an infinite loop. They write and write to binder drivers in a different way. Of course, they do not simply loop forever, falling asleep when there is no message. But that might raise another question: how do you do anything else if it’s an infinite loop? By creating a new thread.

Really getting stuck the operation of the main thread is in the callback method onCreate/onStart/onResume operating time is too long, can lead to a frame, even ANR, stars. The loop itself does not result in application.

Message loop data communication uses epoll mechanism, which can significantly improve CPU utilization. In addition, the main thread of The Android application will create a Linux Pipe before entering the message loop. The purpose of this pipe is to make the Main thread of the Android application go into idle wait state when the message queue is exhausted, and to wake up the main thread of the application when the message queue has a message that needs to be processed. That is, when there is no message, the cycle is asleep and does not get stuck.

CSDN

Below is my public number, welcome everyone to pay attention to me