重學Android——消息機制

Android消息機制

本文源碼Api28html

在Android在主線程的建立時,會自動建立一個looper,不須要咱們本身來建立。java

那麼Android應用啓動流程中,會由AMS調用的ActivityThread類,它的main入口方法裏:android

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");
    }
複製代碼

裏面調用了到Looper的兩個方法,Looper.prepareMainLooper以及Looper.loop()。c++

Looper

先看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();
        }
    }
複製代碼

在這裏調用到了Looper主要有兩個方法之一:prepare,然後面的判斷sMainLooper是否爲空以及賦值,都是爲了保證只prepareMainLooper一次。less

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

那麼咱們能夠只看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));
    }
複製代碼

能夠看到,其實就是在prepare就作了兩件事async

  1. 判斷當前對象是否爲空,不爲空就拋出異常----這是一個相對來講比較奇葩的斷定,通常咱們寫程序,都是判斷爲空拋異常或者new,再仔細看這一段英文「一個線程只能有一個looper」,這就表明一個線程不能屢次調用prepare來建立looper。
  2. 若是線程中沒有looper,那麼那new一個looper將其set到ThreadLocal中去。

ThreadLocal

順便說一嘴ThreadLocalide

ThreadLoacl是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據,數據存儲以扣,只有在這個線程中能夠獲取到存儲的數據,對其它線程是沒法獲取到的——by《Android開發藝術探索》函數

特色在於使用set來存儲數據,get來取出數據。

咱們能夠在代碼中作個試驗:

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

獲得的結果果真是2019-06-05 22:05:07.933 3076-4709/com.apkcore.studdy D/MainActivity: run: null,可見在哪一個線程放在數據,就必作在哪一個線程取出。

關於ThreadLocal的內部代碼,篇幅有限,下次再一塊兒詳細看。


繼續來看Looper,咱們已經看到在prepare中,若是線程中沒有Looper對象時,會new一個looper,並把它加入到ThreadLocal中,那看一下它的構造函數

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

在構造方法中,實例化了一個消息隊列MessageQueue,並還會獲取到當前的線程,把它賦值給mThread。也就是說消息隊列此時已經和當前線程綁定,其做用的區域爲當前實例化looper的線程

那麼咱們接下來能夠看Main方法調用的Looper.loop()方法

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();
    }
}
複製代碼

去掉了一些對咱們分析不是特別重要的代碼,對關鍵代碼作了註釋,其實就是獲取當前的looper對象,並進入一個死循環中,一直從消息隊列中取消息,若是消息爲null就跳出循環,若是不爲空,那麼把消息交給msg.target.dispatchMessage(msg)來處理,msg.target在後面會看到,它其實就是Handler,過會分析。最後調用recyleUnchecked來方法回收。

這裏有一個知識點queue.next(),咱們到下面分析messageQueue的時候一塊兒來分析。

Handler

咱們在使用Handler的時候

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

通常是這麼使用,固然在Activity直接這麼使用,是有可能內存泄露的,但這不是咱們這一節要講的重點。

那麼咱們先從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;
    }
複製代碼

從構造方法來看,仍是先要獲取到Looper的myLooper方法,從ThreadLocal中取出looper對象,以後判斷對象是否爲空,爲空則拋出異常。不爲空則獲取到了looper的消息隊列,這樣,Handler中就持有了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)}.
複製代碼

註釋中詳細講了mAsynchronous這個參數的做用,是一個同步障礙,異步消息不受引入的同步障礙的限制,這一點也是同步消息和異步消息的區別了。在後面會講到。

咱們在使用handler發送消息時,通常使用sendMessage來發送,如

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

仍是先忽略代碼中直接new Thread的問題,繼續跟蹤源碼

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);
    }
複製代碼

能夠看到,都是層層調用,最終調到sendMessageAtTime方法裏。先獲取了隊列的messageQueue,判斷queue不能爲空,而後調用到了enqueueMessage方法,把它本身的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);
    }
複製代碼

這enqueueMessage方法裏,注意了!咱們看到了熟悉的字眼msg.target,在Handler的構造方法中,mQueue = mLooper.mQueue;,傳入到enqueueMessage方法中的就是這個值,則msg.target賦值爲了Handler時,在looper中,把消息交給msg.target.dispatchMessage(msg)來處理,就是交給了Handler來處理。

也就是說Handler發出來的消息,所有發送給了MessageQueue中,並調用enqueueMessage方法,而Looper的loop循環中,又調用了MessageQueue的next方法,把這個消息給dispatchMessage來處理

那麼再來看dispatchMessage方法

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

在這裏,就能看到handler設置不一樣的回調的優先級了,若是msg調用了callback,那麼只調用此callback,若是沒有設置,那麼構造方法中有傳入callback的話,回調此callback,若是這兩者都沒有設置,纔會調用覆寫的Handler裏的handleMessage方法

咱們繼承來看enqueueMessage方法

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

在這裏能夠看到,最終會調用到queue.enqueueMessage(msg, uptimeMillis);中,那咱們接下來看MessageQueue

MessageQueue

messageQueue是在Looper的構造方法中生成的

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

咱們繼續看它的enqueueMessage方法

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;
    }
複製代碼

源碼的關鍵地方都給了註釋,只是對同步分割欄不理解,這個立刻就講。上述源碼,就是在消息鏈表中找到合適的位置,插入message節點。由於消息鏈是按時間進行排序的,因此主要是比對message攜帶的when信息,首個節點對應着最早處理的消息,若是message被插入到表頭了,就意味着最近喚醒時間也要作調整,把以needwake就設爲了true,以便走到nativeWake(mPtr);

同步分割欄

Android 中的 Handler 同步分割欄

Handler之同步屏障機制(sync barrier)

所謂的同步分割欄,能夠理解爲一個特殊的Message,它的target域爲null,它不能經過sengMessageAtTime等方法傳入隊列中,只能經過調用Looper的postSyncBarrier()

做用

它卡在消息鏈表的某個位置,當異步線程能夠在Handler中設置異步卡子,設置好了之後,當前Handler的同步Message都再也不執行,直到異步線程將卡子去掉。在Android的消息機制裏,同步的message和異步的message也就是這點區別,也就是說,若是消息列表中沒有設置同步分割欄的話,那麼其實它倆處理是同樣的。


繼續看上面的nativeWake()方法,這是一個native方法,對應在C++裏,有興趣的盆友,能夠找framworks/base/core/jni/android_os_MessageQueue.cpp來查看它的源碼,這裏我就不分析了,主要動做就是身一個管道的寫入端寫入了W。

回到最上面,咱們在講Looper.loop的源碼中,咱們還留下了一個msg.next()沒有分析

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;
        }
    }
複製代碼

其實源碼中英文註釋已經講得比較直白了,我不過是隨便翻譯了一下,這個函數裏的for循環並非起循環摘取消息節點的做用,而是爲了連貫事件隊列的首條消息是否真的到時間了,若是到了,就直接返回這個msg,若是時間尚未到,就計算一個比較精確的等待時間(nextPollTimeoutMillis),計算完後,for循環會再次調用nativePollOnce(mPtr, nextPollTimeoutMillis)進入阻塞,等待合適的時長。

上面代碼中也處理了「同步分割欄」,若是隊列中有它的話,是千萬不能返回的,要嘗試尋找其後第一個異步消息。

next裏面另外一個比較重要的就是IdleHandler,當消息隊列處於空閒時,會判斷用戶是否設置了IdleHandler,若是有的話,則會嘗試處理,依次調用IdleHandler的queueIdle()函數。這個特性,咱們能夠用在性能優化裏,好比延遲加載:咱們常常爲了優化一個Activity的顯示速度,可能把一些非必要第一時間啓動的耗時任務,放在界面加載完成後進行,可是就算界面加載完成了,耗時任務同樣的會佔用cpu,當咱們正好在操做時,同樣可能會形成卡頓的現象,那麼咱們徹底能夠利用Idle Handler來作處理。

nativePollOnce

前面說了next中調用nativePollOnce起到了阻塞做用,保證消息循環不會在無消息處理時一直在那循環,它的實現仍是在android_os_MessageQueue.cpp文件中,一樣在這裏不作過多的深刻,有興趣的童鞋能夠自行在frameworks/base/core/jni/android_os_MessageQueue.cpp中查看。

這裏提一點,在c++中,pollonce調用了c++層的looper對象,在這個looper和咱們java中的Looper是不同的,其內部除了建立一個管道外,還建立了一個epoll來監聽管道的讀取端。

擴展

Android中爲何主線程不會由於Looper.loop()方法形成阻塞,在此文中詳細講了爲何咱們在主線程loop死循環而不會卡死。

對於線程既然是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,咱們是毫不但願會被運行一段時間,本身就退出,那麼如何保證能一直存活呢?簡單作法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出例如,binder線程也是採用死循環的方法,經過循環方式不一樣與Binder驅動進行讀寫操做,固然並不是簡單地死循環,無消息時會休眠。但這裏可能又引起了另外一個問題,既然是死循環又如何去處理其餘事務呢?經過建立新線程的方式。

真正會卡死主線程的操做是在回調方法onCreate/onStart/onResume等操做時間過長,會致使掉幀,甚至發生ANR,looper.loop自己不會致使應用卡死。

消息循環數據通訊採用的是epoll機制,它能顯著的提升CPU的利用率,另外Android應用程序的主線程在進入消息循環前,會在內部建立一個Linux管道(Pipe),這個管道的做用是使得Android應用主線程在消息隊列惟恐時能夠進入空閒等待狀態,而且使得當應用程序的消息隊列有消息須要處理是喚醒應用程序的主線程。也就是說在無消息時,循環處於睡眠狀態,並不會出現卡死狀況。

CSDN

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索