Re-learn Android-- message mechanism

Android messaging

This article Source Api28

When the main thread in the creation of Android, automatically creates a looper, you do not need to create our own.

So Android application startup process, will be called by the AMS ActivityThreadclass, its main entrance in the 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();
    	
    	//注意:
        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");
    }
复制代码

Which calls to two methods of Looper, Looper.prepareMainLooper and Looper.loop ().

Looper

Look at prepareMainLooper

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
复制代码

Here Looper call to one of two main methods: prepare, and to determine whether the latter is empty and sMainLooper assignment is to ensure prepareMainLooper only once.

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
复制代码

Then we can 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));
    }
复制代码

We can see, in fact, prepare to do two things

  1. Determine whether the current object is null, not empty throw an exception ---- This is a relatively comparative judgment wonderful, generally we write programs, are judged as abnormal or throw empty new, closer look at this period of English "there can be only one thread a looper", which represents a thread can not prepare multiple calls to create a looper.
  2. If the thread is not looper, a looper so that new ThreadLocal be set to go.

ThreadLocal

By the way, a mouth ThreadLocal

ThreadLoacl is an internal thread data storage class, through which data can be stored in the specified thread, the data is stored in a snap, and only in this thread can get to the data stored on other threads can not get to --by " Android developers artistic exploration. "

Characterized by using the set to store data, get to retrieve data.

We can be a test in the code:

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

The results obtained are really 2019-06-05 22: 05: 07.933 3076-4709 / com.apkcore.studdy D / MainActivity: run: null, in which the thread is visible on the data, which he will do in the thread out.

About ThreadLocal internal code, and space is limited, the next time a detailed look together.


Continuing with Looper, we have seen in the prepare, if there is no Looper thread object, the new a looper, and add it to a ThreadLocal, then look at its constructor

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

In the configuration process, the instance of the MessageQueue a message queue, and also the current thread acquired, assign it to mThread. That message queues and at this time the current thread has been bound, its action region for the current instance of the looper thread .

Then we can look Looper.loop Main method call () method

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

public static void loop() {
    //获取一个looper对象,可以看到prepare一定在loop()调用之前
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    ...

    for (;;) {
        //从消息队列MessageQueue中取消息,如果消息为空,跳出循环
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...
        //如果消息不为空,那么msg交给msg.target.dispatchMessage(msg)来处理
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
		//方法回收
        msg.recycleUnchecked();
    }
}
复制代码

Removed some of our analysis is not particularly important code, the key code to do the notes, in fact, to get the current looper object and enter an infinite loop, has been taking the message from the message queue, if the message is null it out of the loop, if not empty, then the message is to msg.target.dispatchMessage(msg)be handled, msg.target will see later, it is actually Handler, over-analyze. The last call recyleUnchecked be recovered.

There is a knowledge point queue.next(), we analyze the following analysis messageQueue time together.

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");
            }
        }
    };
复制代码

So usually use, so of course directly in Activity, it is possible memory leaks, but this is not what we want to say the focus of this section.

Then we start to see the beginning of the constructor handler

    public Handler() {
        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;
    }
复制代码

From constructor view, or the first to the acquired Looper myLooper method, the object taken from a ThreadLocal looper, after the determination whether the object is empty, empty exception is thrown. It is not empty then get to the looper's message queue, so, Handler holds in respect of the reference messageQueue.

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)}.
复制代码

Comment in detail talked about the role of mAsynchronous parameter is a synchronization barrier, barriers to limit synchronous, asynchronous messages are not introduced, this is the difference between synchronous and asynchronous message the message . It will be mentioned later.

We used when sending a message handler, generally transmitted using sendMessage, such as

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
            mHandler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
复制代码

Ignore the problem or direct code new Thread, and keep track of 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);
    }
复制代码

We can see, the layers are called, eventually transferred to sendMessageAtTime method in. First acquired messageQueue queue, queue judgment can not be null, and then call to enqueueMessage ways to bring its own queue, msg and uptimeMillis have passed this together.

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

This enqueueMessage method, the attention! We see the familiar words msg.target , in the constructor Handler, the mQueue = mLooper.mQueue;incoming method to enqueueMessage is this value, the msg.target assignment In order Handler, the looper in the messages to msg.target.dispatchMessage(msg)be processed , that is, Handler handed over to deal with.

That Handler issued to the news, all sent to the MessageQueue and calls enqueueMessage method, and loop the loop Looper, and call the next MessageQueue ways to bring this message to be processed dispatchMessage

Then look at dispatchMessage method

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
复制代码

Here, you can see the handler to set different priorities callback, and call a callback if the msg, then just call this callback, if not set, then the constructor for incoming callback, then the callback this callback, if these two who are not set, the call will be overwritten Handler method in the handleMessage

We look at inherited enqueueMessagemethod

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

Here you can see, eventually calls to queue.enqueueMessage(msg, uptimeMillis);the, then we are going to see MessageQueue

MessageQueue

messageQueue is generated in the constructor of Looper

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

We continue to look at its enqueueMessagemethods

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.
                //新消息会插入到链表的表头,这意味着队列需要调整唤醒时间
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                //新消息插入到链表的内部,一般情况下,这不需要调整唤醒时间
                //但还是要考虑到当表头是“同步分割栏的情况”
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //注意isAsynchronous是在上面if (mAsynchronous) {  msg.setAsynchronous(true); }这里有设置,默认false
                    if (needWake && p.isAsynchronous()) {
                        //当msg是异步的,也不是链表的第一个异步消息,所以就不用唤醒了
                        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;
    }
复制代码

The key local source gave a comment, just sync separator bar do not understand this immediately speak. Above the source, it is to find a suitable location in the message list, the message insertion node. Because the message chain is sorted by time, so the main message than when information carried by the first node corresponds to the first message is processed, if the message is inserted into the header, it means that the recent wake-up time has to be done adjustments to needwake on the set of the true, to come nativeWake (mPtr);

Synchronous split bar

Android in sync Handler split bar

Handler of the barrier synchronization mechanism (sync barrier)

So-called synchronous bays, can be understood as a special the Message, its target field is null, it can not pass through the queue sengMessageAtTime the like, only by calling the Looper postSyncBarrier ()

effect

It is stuck somewhere in the message list, and when asynchronous threads can be set in asynchronous clip in Handler, set up after the current sync Message Handler are not executed until the asynchronous thread clamp removed. In the message mechanism in Android, message and asynchronous message is this difference synchronization, that is, if the message list is not set sync separator bar, then in fact Talia process is the same.


Continue to look above nativeWake () method, which is a native method, correspondence in C ++, interested basin friends, you can find framworks/base/core/jni/android_os_MessageQueue.cppto see its source code, I am not here analyzes the main action is itself a pipeline write end written W.

Back to the top, we are talking about Looper.loop source, we also left a msg.next () no analysis

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

            //阻塞在此
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                //获取next消息,如能得到就返回
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //尝试拿消息队列里当前第一个消息
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    //如果队列里拿的msg是那个同步分割栏,那么就寻找后面的第一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //下一条消息未就绪。设置超时以在准备就绪时唤醒。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            //重新设置一下消息队列的头部
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;//返回
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

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

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

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

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //处理idleHandlers部分,空闲时handler
            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;
        }
    }
复制代码

In fact, the source code comments in English already put it in more straightforward, and I was only just translated a bit, this function for loop does not act as a message node removal cycle, but the first message to the event queue of it really coherent to time, if it arrives, it directly back to this msg, if the time has not arrived, you calculate a more accurate wait time (nextPollTimeoutMillis), after computing, for loop will call nativePollOnce again (mPtr, nextPollTimeoutMillis) into the obstruction, when waiting for the right length.

The above code also dealt with "sync separator bar" if it is in the queue, then, is that it must not be returned later to try to find the first asynchronous message.

next there is another more important IdleHandler, when the message queue is empty, it will determine whether the user has set IdleHandler, if any, will try to proceed, in turn call IdleHandler of queueIdle () function. This feature, we can use in optimizing performance, such as lazy loading: We often order to optimize the display speed of an Activity, possibly some non-essential first time to start the time-consuming task, on the interface after loading is complete, but even if the interface loading is complete, the time-consuming task will occupy the same cpu, just when we operate as likely to cause the phenomenon of Caton, then we can do the processing using the Idle Handler.

nativePollOnce

He said earlier the next call nativePollOnce played a blocking role in ensuring the message loop would not have been in that cycle, its implementation is still in android_os_MessageQueue.cpp file, same here do not do too much depth in the absence of message handling, interested the shoes themselves can in frameworks / base / core / jni / android_os_MessageQueue.cpp view.

Here we mention that, in c ++ in, pollonce called looper objects c ++ layer, Looper in the looper and our java is not the same, its interior in addition to creating a pipeline, but also created a epoll to listen to read the pipeline end.

Spread

Android why the main thread will not Looper.loop () method cause obstruction , detailed in this article we talked about why the main thread loop infinite loop and not stuck.

For thread since it is a piece of executable code, executable code execution when completed, will be the termination of the life cycle of the thread, and the thread exits. As for the main thread, we never want to be running for some time, and that they quit, how to ensure that it has been able to survive it? Simple approach is executable code that could have been executed down, the cycle of death will not be able to ensure the exit for example, binder thread is the method of infinite loop, loop through different ways and Binder drive to read and write, of course, not simply dead circulation, will sleep when there is no news. But here it may well lead to another problem, since it is the cycle of death and how to deal with other matters it? By creating a new thread.

Really will get stuck in the main thread of operation is the callback method onCreate / onStart / onResume and other operations for too long will lead to dropped frames, even ANR occurs, looper.loop itself does not cause the application stuck.

Message loop data communications using epoll mechanism, it can significantly improve the utilization of the CPU, the other main thread Android application message before entering the loop creates a Linux pipe (Pipe) inside, the role of this channel is such that Android application message queue of the main thread may enter an idle wait state for fear, and such that when the application's message queue have messages to process awaken the main thread of the application. That in the absence of news, circulation is in sleep state, and will not be stuck situation.

CSDN

Here is my public number, I welcome everyone's attention

Reproduced in: https: //juejin.im/post/5cf7eb15f265da1b916382b3

Guess you like

Origin blog.csdn.net/weixin_33738555/article/details/91411906
Recommended