Summary of a.

The Android messaging mechanism mainly involves the following classes:

  • Message: The entity of the Message
  • MessageQueue: a queue that stores messages. It is not a queue in nature, but a linked list
  • Looper: An internal loop to extract messages
  • Handler: Entity that processes messages

The previous chapter explained how to use The Android messaging mechanism, which can be broken down into the following steps: 1. Create a thread and call looper. prepare 2. Initialize the thread Handler and hook it to Looper 3.

Then from the previous chapter to use the method to explain the source code, source analysis based on Android R version

2. Stars. Prepare

[-> Looper.java]

    private static void prepare(boolean quitAllowed) {
        if(sThreadLocal.get() ! =null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        / / see 2.1
        sThreadLocal.set(new Looper(quitAllowed));
    }

Copy the code

ThreadLoacal (TLS) allows threads to have their own variables, where key is currentThread and value is Looper object.

2.1 the new stars

[-> Looper.java ]

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

The Looper constructor creates MessageQueue and sets mThread to currentThread.

2.2 new MessageQueue

[-> MessageQueue.java]

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        See Chapter 3 of Android Messaging for Native layer analysis
        mPtr = nativeInit();
    }
Copy the code

Initialize Java layer MessageQueue, and set whether exit is allowed, and initialize Native layer MessageQueue.

Looper. Prepare initializes Looper and MessageQueue. The relationship is one-to-one.

Initialize Handler, hook to Looper

Let’s review how to initialize Handler.

[-> Initialize Handler]

public Handler mSubHandler = new Handler(Looper.myLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    Log.i(TAG, "mSubHandler handler handleMessage thread : " + Thread.currentThread());
                    switch (msg.what) {
                        case MSG_GET:
                            double number = Math.random();
                            Message message = new Message();
                            message.what = MSG_RESULT;
                            message.obj = "dopezhi : " + number;
                            mUiHandler.sendMessage(message);
                            break;
                        default:
                            break; }}};Copy the code

Why only msubHandler. sendMessage is needed to send a message?

Normally, if the main thread wants to send a message to the child thread, it takes the Looper of the child thread, gets its corresponding MessageQueue, and drops a message to the MessageQueue.

But this is too cumbersome, so Handler has another function besides processing messages: it helps us send messages to the corresponding MessageQueue of Handler, which simplifies operations. That’s what hooks are for.

[-> Handler.java]

    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

The so-called hook is when the new Handler is passed in the corresponding Looper, MessageQueue, etc. The subsequent handler. sendMessaage is actually sending a message to the previously linked MessageQueue.

Four stars. Loop

[-> Looper.java]

public static void loop(a) {
        // Get Looper stored in TLS
        finalLooper me = myLooper();final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        // Get a token for this communication, and then compare it with the token after sending the message to determine whether the process or thread state has changed
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            // Get messages in an infinite loop, see 4.1
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return; }...// To process messages through handlers see 4.3msg.target.dispatchMessage(msg); .final long newIdent = Binder.clearCallingIdentity();

            // See 4.4msg.recycleUnchecked(); }}Copy the code

Looper.loop is essentially an infinite loop that continues through next to retrieve pending messages. Does this infinite loop consume CPU resources?

The epoll mechanism is a Linux I/O reuse technology. The epoll mechanism can listen for one or more file descriptors to see if the event of interest (epoll_ctl) has occurred. The epoll mechanism will release CPU resources before the event of interest occurs. Until the fd writes the event of interest, epoll_wait immediately wakes up quickly for processing.

4.1 the queue. The next ()

[-> MessageQueue.java]

 Message next(a) {
     // PTR is the address of Native layer NativeMessageQueue
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //idleHandler will be covered in a later section
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }
            //nextPollTimeoutMillis is 0 and returns without waiting, -1 is infinite
            // The real block is here, where epoll_wait is followed to the Native layer
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                The mMessages variable is the header of the Message list
                Message msg = mMessages;
                // Synchronization barrier, see 5
                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) {
                // The time is not up, set the time, and we will deal with it next time
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        // Break the head node as MSG
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        returnmsg; }}else {
                    nextPollTimeoutMillis = -1;
                }
                If Looper exits, null is returned
                if (mQuitting) {
                    dispose();
                    return null;
                }
                // All of the following belong to idelHandler. }}Copy the code

When queue.next is called, nativePollOnce waits for the event to occur.

4.2 handler. SendMessage

The call chain that sends the message:

Can see if we are through a variety of ways, we are using the MessageQueue. EnqueueMessage method.

[-> MessageQueue.enqueueMessage]

    boolean enqueueMessage(Message msg, long when) {
       // Indicates that the App cannot post the synchronization barrier by setting target==null
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use."); }... msg.markInUse(); msg.when = when; Message p = mMessages;boolean needWake;
            // Initialize the header if it is null
            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 {
            // If the header is not empty, the list is inserted in chronological order, with the header executed first
                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;
            }

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

After the message is sent, needWake determines whether the nativeWake is needed to process the event.

You may feel like you’re not getting enough of this, but how did you get it? Details can be found in the Native layer section, where you can also reveal the plot.

In fact, when NativeMessageQueue of the native layer is initialized, epoll_ctl listens for the input event of the pipe. Once the pipe is written to the event (which is what nativeWake does, writing a character 1), epoll_wait wakes up and processes it. The bottom layer simply cleans up the pipe data, but the bottom function can return it, which is reflected in the top layer, Which is The nativePollOnce in Queue.next (), which can then return and process the extracted event for processing.

4.3 MSG. Target. DispatchMessage

The target is Handler.

[-> Handler.java]

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

Processing rules:

  • If Message Callback is not null, Message Callback is processed
  • If Message Callback is null
    • If Handler Callback is not null, Handler Callback is processed
    • If the Handler Callback is null, the message is processed normally

Y1u1 is a bit of a mess. What follows what, one by one!

4.3.1 the Message Callback?

In addition to sendMessage, there is a handler. post.

[-> Handler.java]

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
Copy the code
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
Copy the code

Handler. Post submits the runnable object, and message. callback sets the runnable object. Let the Message take precedence.

4.3.2 Handler Callback?

[-> Test.java]

            Handler.Callback callback = new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    switch (msg.what){
                        case MSG_PASS:
                            return true;
                        default:
                            return false; }}};Copy the code

The Handler Callback function allows the Handler to intercept the message. For example, if I determine what is MSG_PASS, return true and skip the message.

4.3.3 handleMessage

If the message is not filtered by Handler, the handleMessage method we wrote is executed.

4.4 MSG. RecycleUnchecked

[-> Message.java]

    @UnsupportedAppUsage
    void recycleUnchecked(a) {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this; sPoolSize++; }}}Copy the code

Message maintains a cache Message pool of 50 messages. When a message is reclaimed, if the message pool size has not exceeded the maximum, it is added to the cache message pool and sPool points to the first message in the message pool, which can be retrieved later.

The new Message constructor doesn’t do anything. It should go to the Message pool to fetch messages. Otherwise, every time I new, the Message pool can only cache 50 messages, which would be a waste of time and trigger GC.

It was later discovered that the most appropriate way to obtain information was message.obtain ().

[-> Message.java]

/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message(a) {}/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */
    public static Message obtain(a) {
        synchronized (sPoolSync) {
            if(sPool ! =null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                returnm; }}return new Message();
    }

Copy the code

As you can see from the comments, the official recommendation is to use message.obtain () to obtain messages instead of assigning unnecessary objects using new messages.

5. Synchronization barrier mechanism

5.1 the principle

First of all, let’s think about a problem. If I have an urgent message, I want MessageQueue to help me deal with it immediately. What should I do? All you need to do is add messages to the head of the queue, but MessageQueue is sorted based on processing time, so many messages will pile up before that point, and your message will not be executed first.

Hence the synchronization barrier mechanism.

All messages are synchronized by default, only set up a sign of an asynchronous message is asynchronous messaging, when the queue. The next () to extract messages, as long as the target, find a message is null, then into the mechanism of synchronous barrier, then iterate through all the rest of the message, as long as the message set the asynchronous flag, to extract the message processing, will be the priority Until removeBarrier.

[-> MessageQueue.java]

    // The return value is a token representing the barrier, which is the number of synchronization barriers currently maintained by MessageQueue.
    @UnsupportedAppUsage
    public int postSyncBarrier(a) {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if(when ! =0) {
                while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            returntoken; }}Copy the code

As you can see, post a synchronization barrier, i.e. Message target is null, which App test cannot do.

[-> Message.java]

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

Through the asynchronous message | Settings.

5.2 Application of synchronization barriers

All the message-related code in Choreographer has asynchronous flags set. This is to ensure that the upper app can perform the traversal and draw the view tree as soon as possible when it receives vsync.

First time here refers to perform ViewRootImpl. ScheduleTraversals after. [-> ViewRootImpl.java]

    void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Post synchronization barrier
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Commit mTraversalRunnable, doTraversal
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

You can see that a synchronization barrier is committed in scheduleTraversals, and then the MessageQueue takes precedence over asynchronous messages.

[-> Choreographer.java]

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {...synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
            // Set Message Callback to increase priority
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                // Set the asynchronous message
                msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

Set Message Callback to mTraversalRunnable (doTraversal), and set the Message to async to be processed first.

5.3 Some foresight about View

Since I am not familiar with View, I will write it roughly and analyze it in detail in the future.

Through WindowManagerGlobal. AddView, initialize ViewRootImpl. [-> WindowManagerGlobal.java]

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {... ViewRootImpl root; root =newViewRootImpl(view.getContext(), display); . root.setView(view, wparams, panelParentView, userId); }Copy the code

Perform setView.

[-> ViewRootImpl.java]

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) { mView = view; . requestLayout();// View refers to DectorView
            view.assignParent(this); ...}}}Copy the code

[-> View.java]

    @UnsupportedAppUsage
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent"); }}Copy the code

The argument is ViewParent, and ViewRootImpl is the interface that implements ViewParent. Here we bind the DecorView to ViewRootImpl. The root layout of each Activity is a DecorView, and the parent of the DecorView is a ViewRootImpl, so do something like invalidate() in the child View, loop back to the parent, loop back to the DecorView, Find ViewRootImpl, View refresh and update is controlled by ViewRootImpl.

So even a small View on the interface to initiate a redraw request, will have to go to ViewRootImpl (6.5 问 题 : subview add process? How to associate with this DecorView, the sub-view is also a View tree, right? A View tree is a View tree; If you add child views the same way, then you just associate the view with the view view PL, not the view tree), and it makes the redraw request, and then it starts traversing the view tree, Walk all the way to the View that needs to be redrawn and call its onDraw() method to draw.

The redraw operation View.invalidate() and setView end with a call to requestLayout(). [-> ViewRootImpl.java]

@Override
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true;
        // Back to the content of the synchronization barrierscheduleTraversals(); }}Copy the code

Six IdleHandler.

6.1 the principle

The IdleHandler is a static internal interface to the MessgeQueue. It is an IdleHandler that is executed only when there is no message in the message queue or when the queue is not ready for execution.

[-> How to use]

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle(a) {
        // Process logic when idle

        return false; }});Copy the code

When false is returned, the IdleHandler is removed.

[-> MessageQueue.java]

/ / the total IdleHandlers
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

// The IdeleHandlers need to be executed this time
private IdleHandler[] mPendingIdleHandlers;

Message next(a) {...// Number of idelHandlers to be processed this time
       int pendingIdleHandlerCount = -1; // -1 only during first iteration
         // Execute only if the team has no message, or if the message is not ready for execution
        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;
        }
        // Initialize the mPendingIdleHandlers array and store a copy of the total Handlers in the array to be handled this time
        if (mPendingIdleHandlers == null) {
            mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        // Start handling the IdelHandlers that need to be executed this time.
        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 false is returned, IdelHandler is removed
            if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}}Copy the code

6.2 Source Code Application

In the system source code, when the Activity executes Rusume, it will have a sentence at the end of the handleResumeActivity:

Looper.myQueue().addIdleHandler(new Idler());
Copy the code

[-> ActivityThread.java]

    private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle(a) {
            ActivityClientRecord a = mNewActivities;
            boolean stopProfiling = false;
            if(mBoundApplication ! =null&& mProfiler.profileFd ! =null
                    && mProfiler.autoStopProfiler) {
                stopProfiling = true;
            }
            if(a ! =null) {
                mNewActivities = null;
                IActivityTaskManager am = ActivityTaskManager.getService();
                ActivityClientRecord prev;
                do {
                    if (localLOGV) Slog.v(
                        TAG, "Reporting idle of " + a +
                        " finished="+ (a.activity ! =null && a.activity.mFinished));
                    if(a.activity ! =null && !a.activity.mFinished) {
                        try {
                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
                            a.createdConfig = null;
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                    prev = a;
                    a = a.nextIdle;
                    prev.nextIdle = null;
                } while(a ! =null);
            }
            if (stopProfiling) {
                mProfiler.stopProfiling();
            }
            applyPendingProcessState();
            return false; }}Copy the code

Mainly do some recycling operations. The interface shows that these important things have been handled, and the collection operation can be started when the idle value is that the main thread MessageQueue has no Message, so the collection can be started. What if there’s too much news? Too late to execute?)

ResumeTopActivityInnerLocked () – > completeResumeLocked () – > scheduleIdleTimeoutLocked () method will send a will send a delayed message (s), If the interface has not been closed for a long time (if the interface needs to be closed), the message will be triggered 10 seconds later to close the interface and execute methods such as onStop.

Seven runWithScissors.

Zhuanlan.51cto.com/art/202007/… The child process sends a task to the main thread via Handler, and the child thread blocks until the main thread finishes processing the message, or timeout

Problem: 1. If a timeout occurs, there is no cancellation logic, but the Message is still in the MessgeQueue and will be executed at a later time, which is against the business logic.

2. If timeout is large and the looper.quit () method is called during this time, all messages in MessageQueue will be removed and the lock will be released by wait(timeout). That if still hold other lock during this period, cause lock.