Android系統(tǒng)源碼分析--消息循環(huán)機(jī)制

上一章我們講解SystemServer時(shí)涉及到了消息機(jī)制,因此這一章我們先介紹一下消息循環(huán)機(jī)制,幫助大家弄清楚消息循環(huán)的原理,有助于代碼的編寫(xiě)和優(yōu)化萤厅。

Looper-Message-MessageQueue-Handler消息處理機(jī)制

在Android系統(tǒng)有兩個(gè)通信機(jī)制橄抹,一個(gè)是Binder,一個(gè)是消息機(jī)制祈坠,前者是跨進(jìn)程通信害碾,后者是進(jìn)程內(nèi)部通信。消息通信主要包括幾個(gè)部分:

  • 消息發(fā)送者和處理者:Handler
  • 消息循環(huán)器:Looper
  • 消息隊(duì)列:MessageQueue
  • 消息:Message

我們先看一個(gè)時(shí)序圖:

005.jpg

圖中赦拘,1-11步是Looper的準(zhǔn)備過(guò)程,12-17步是獲取消息芬沉,處理消息躺同,回收消息的循環(huán)過(guò)程。

下面是一張消息循環(huán)過(guò)程圖丸逸,圖片來(lái)自網(wǎng)絡(luò)博客(blog.mindorks.com)蹋艺,Looper會(huì)通過(guò)loop方法不斷從消息隊(duì)列去取消息,然后交給handler處理黄刚,處理完成就回收消息捎谨,要注意的是只有一個(gè)looper,但是可能有多個(gè)handler:

002.jpg

1憔维、Looper

Looper是一個(gè)循環(huán)器涛救,通過(guò)里面的loop方法不斷去取消息,發(fā)送給Handler進(jìn)行處理业扒。根據(jù)上面時(shí)序圖以及SystemServer啟動(dòng)代碼我們開(kāi)始分析Looper的調(diào)用過(guò)程:

private void run() {
        try {
            ...
            
            // 準(zhǔn)備主線程的Looper
            Looper.prepareMainLooper();

            ...
        } finally {
            ...
        }

        ...

        // Loop(循環(huán)) forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我們先看Looper.prepareMainLooper方法:

    /**
     * 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() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

上面有段注釋检吆,我翻譯一下,就是:初始化當(dāng)前線程作為一個(gè)looper程储,并把它標(biāo)記為應(yīng)用的主looper蹭沛。這個(gè)looper是被Android環(huán)境(系統(tǒng))創(chuàng)建的,因此你不需要自己調(diào)用這個(gè)方法章鲤。也就是系統(tǒng)創(chuàng)建了這個(gè)looper摊灭,你不需要再創(chuàng)建了。我們接著看里面的內(nèi)容败徊,首先調(diào)用了prepare方法帚呼,需要注意的是Looper.prepare()在每個(gè)線程只允許執(zhí)行一次,該方法會(huì)創(chuàng)建Looper對(duì)象:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {// 確保ThreadLocal中只有一個(gè)Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

上面的ThreadLocal是聲明在類里的集嵌,并且是靜態(tài)的萝挤,因此,隨著類創(chuàng)建了該對(duì)象根欧,get方法是獲取Looper的怜珍,如果能獲取到,則拋出異常凤粗,也就是確保當(dāng)前線程只有一個(gè)Looper酥泛。如果是空今豆,那么我們創(chuàng)建一個(gè)Looper放到里面去。

我們先看一下ThreadLocal:線程本地存儲(chǔ)區(qū)(Thread Local Storage柔袁,簡(jiǎn)稱為TLS)呆躲,每個(gè)線程都有自己的私有的本地存儲(chǔ)區(qū)域,不同線程之間彼此不能訪問(wèn)對(duì)方的TLS區(qū)域捶索。(來(lái)自Android消息機(jī)制1-Handler(Java層))我們看一下它的set和get方法:

ThreadLocal的set方法:

    public void set(T value) {
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 獲取當(dāng)前線程里的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如果map不為空插掂,則以鍵值對(duì)放入進(jìn)行存儲(chǔ),此處map不是HashMap腥例,而是其他辅甥,這里不詳細(xì)解釋。如果map為空燎竖,則通過(guò)下面代碼創(chuàng)建map:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的get方法:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

我們看到獲取的時(shí)候也是根據(jù)當(dāng)前線程去獲取的璃弄。因此每個(gè)線程會(huì)保存一個(gè)Looper。

我們接著看Looper的構(gòu)造函數(shù)有哪些操作构回,也就是創(chuàng)建Looper做了哪些處理:

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

首先是創(chuàng)建了MessageQueue對(duì)象夏块,接著創(chuàng)建一個(gè)線程,也就是當(dāng)前線程纤掸,currentThread是一個(gè)native方法脐供,我們不再分析,我們看一下MessageQueue創(chuàng)建做了哪些事情:

    MessageQueue(boolean quitAllowed) {
        // 是否可以退出消息隊(duì)列
        mQuitAllowed = quitAllowed;
        // 返回底層的MessageQueue對(duì)象的內(nèi)存地址茁肠,如果為空返回0
        mPtr = nativeInit();
    }

上面的nativeInit是調(diào)用的jni患民,我貼一下代碼,不再解釋:

001.png

我們回到prepareMainLooper方法接著看垦梆,如果sMainLooper不為null匹颤,則拋出異常,提示sMainLooper已經(jīng)創(chuàng)建了托猩,如果是null印蓖,那么調(diào)用myLooper方法回去sMainLooper:

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

其實(shí)這個(gè)get方法就是我們上面new完Looper放進(jìn)去的,到此prepareMainLooper就完成了京腥,相關(guān)信息也準(zhǔn)備好了赦肃。接下來(lái)就是調(diào)用Looper.loop方法,方法下面是一個(gè)異常公浪,怎么樣才能保證異常不會(huì)拋出他宛,就是loop方法永遠(yuǎn)執(zhí)行不完。是不是只有我們接著看代碼:

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {// 無(wú)限循環(huán)
            Message msg = queue.next(); // might block
            if (msg == null) { // message為空為結(jié)束信號(hào)欠气,退出循環(huán)
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            
            try {
                // 將真正的處理工作交給message的target厅各,即handler
                msg.target.dispatchMessage(msg);
            } finally {
                ...
            }

            ...

            // 回收Message
            msg.recycleUnchecked();
        }
    }

首先是通過(guò)myLooper方法獲取Looper,如果為空预柒,則拋出異常队塘,提示還沒(méi)有調(diào)用Looper.prepare方法袁梗,如果不為空,則通過(guò)looper獲取MessageQueue對(duì)象憔古,然后進(jìn)入for循環(huán)遮怜,因?yàn)閒or語(yǔ)句中沒(méi)有條件,因此該for循環(huán)為無(wú)限循環(huán)鸿市,在這個(gè)循環(huán)中有三件事锯梁,一個(gè)是獲取消息隊(duì)列中的下一個(gè)消息,然后處理該消息焰情,最近處理完消息涝桅,回收消息。這三個(gè)過(guò)程就是Looper的主要作用:取消息烙样,處理消息,回收消息蕊肥。

2.Message

Message是整個(gè)循環(huán)中信息的載體谒获,它是一個(gè)鏈表結(jié)構(gòu),關(guān)于鏈表結(jié)構(gòu)可以參考下面文章:
Android自助餐--Handler消息機(jī)制完全解析--系列
鏈表數(shù)據(jù)結(jié)構(gòu)圖解 和 代碼實(shí)現(xiàn)
基本數(shù)據(jù)結(jié)構(gòu):鏈表(list)
鏈表結(jié)構(gòu)之單鏈表

我們看個(gè)圖:

006.png

上面就是一個(gè)示例圖壁却,每個(gè)Message中都有一個(gè)后面Message的引用next批狱,鏈表最后一個(gè)next為空,sPool是第一個(gè)Message展东。但是每個(gè)Message的內(nèi)存地址不是挨著的赔硫,這樣可以占用零碎的內(nèi)存。

我們先來(lái)看Message包含的參數(shù):

    public int what;
    public int arg1; 
    public int arg2;
    public Object obj;
    /*package*/ int flags;
    /*package*/ long when;
    /*package*/ Handler target;
    // 消息隊(duì)列中下一個(gè)消息的引用
    /*package*/ Message next;
    // sPool這個(gè)變量可以理解為消息隊(duì)列的頭部的指針盐肃,也就是當(dāng)前消息對(duì)象
    private static Message sPool;
    // sPoolSize是當(dāng)前的消息隊(duì)列的長(zhǎng)度
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;    

前四個(gè)參數(shù)很熟悉爪膊,不再解釋,flags是一個(gè)標(biāo)簽砸王,表示是否正在使用推盛;when是處理消息的時(shí)間;target就是我們上面提到的Handler谦铃;next是下一個(gè)Message的引用耘成;sPool是一個(gè)靜態(tài)變量,說(shuō)明只有一個(gè)院喜,其實(shí)這個(gè)是消息隊(duì)列的頭消息凡人;sPoolSize是消息隊(duì)列中消息個(gè)數(shù)行瑞;MAX_POOL_SIZE是消息隊(duì)列最大消息數(shù)量。

Message中有多個(gè)用來(lái)獲取Message對(duì)象的obtain復(fù)寫(xiě)方法师妙。因?yàn)楹竺娴膐btain方法都是通過(guò)第一個(gè)obtain方法獲取Message對(duì)象的,因此我們只看第一個(gè)參數(shù)為空的方法:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        // 避免多線程進(jìn)行爭(zhēng)搶資源骡显,給sPoolSync進(jìn)行加鎖
        synchronized (sPoolSync) {
            // 如果消息隊(duì)列的頭部不為空疆栏,則可以取出頭部重用
            if (sPool != null) {
                Message m = sPool;
                // 頭部消息取出后曾掂,將sPool指向后面的消息對(duì)象
                sPool = m.next;
                // next(隊(duì)列尾部)設(shè)置為null
                m.next = null;
                m.flags = 0; // clear in-use flag
                // 消息隊(duì)列長(zhǎng)度減一
                sPoolSize--;
                return m;
            }
        }
        // 如果消息隊(duì)列的頭部為空,則創(chuàng)建新的Message對(duì)象
        return new Message();
    }

系統(tǒng)提示盡量用這種方法獲取Message對(duì)象壁顶,避免創(chuàng)建大量新的對(duì)象珠洗,其實(shí)也可以通過(guò)Handler來(lái)獲取Message,這個(gè)我們?cè)趯andler時(shí)候再講若专。

在上面Looper中我們講到最后消息處理完后需要回收许蓖,這個(gè)回收方法recycleUnchecked也在Message類中:

 /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        // 避免多線程進(jìn)行爭(zhēng)搶資源,給sPoolSync進(jìn)行加鎖
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                // 回收當(dāng)前消息后時(shí)调衰,將sPool消息后移
                next = sPool;
                // 將當(dāng)前消息放到頭部
                sPool = this;
                // 隊(duì)列長(zhǎng)度加一
                sPoolSize++;
            }
        }
    }

消息回收時(shí)膊爪,將對(duì)應(yīng)消息的標(biāo)簽設(shè)置為使用中,其他標(biāo)簽設(shè)置為空或者默認(rèn)值嚎莉,如果消息隊(duì)列沒(méi)有超過(guò)最大值米酬,那么將sPool賦值給next,將這個(gè)Message賦值給sPool趋箩,消息隊(duì)列長(zhǎng)度加一赃额。也就是將處理完的消息清空,重新放回消息隊(duì)列等待使用叫确。

3.Handler

Handler是發(fā)送消息和處理消息的工具跳芳。我們先看構(gòu)造方法:

public Handler(Callback callback, boolean async) {
        ...

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler中的looper是獲取當(dāng)前線程中的looper,looper不能為空竹勉,MessageQueue也是looper中的飞盆。

首先是發(fā)送消息,發(fā)送消息的方法很多次乓,我們看一張圖:

004.jpg

我們看到Handler中有多個(gè)發(fā)送消息的方法吓歇,但是最終調(diào)用了enqueueMessage方法:

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

從代碼我們可以看到msg.target就是Handler,也就是在這里進(jìn)行賦值的檬输,然后是調(diào)用MQ(MessageQueue)的enqueueMessage方法照瘾,這個(gè)方法是添加消息隊(duì)列的,具體內(nèi)容我們后面再講丧慈。因此析命,發(fā)送消息就是講消息添加到消息隊(duì)列。我們前面還講過(guò)獲取Message對(duì)象可以通過(guò)Message中的obtain方法逃默,也可以通過(guò)Handler中的方法鹃愤,我們先看一張圖:

003.jpg

Handler是通過(guò)多個(gè)復(fù)寫(xiě)方法obtainMessage來(lái)獲取Message的,只是傳入?yún)?shù)不同完域,我們看一個(gè)沒(méi)有參數(shù)的方法代碼:

    public final Message obtainMessage(){
        return Message.obtain(this);
    }

我們看到其實(shí)還是調(diào)用了Message.obtain方法软吐,并且傳入了this,也就是Handler吟税,通過(guò)Message.obtain方法將Handler賦值給Message中的target凹耙。從這姿现,我們基本對(duì)Handler與Message的關(guān)系基本明確了,獲取Message的方法我們也完全知道了肖抱,因此我們?cè)谝院笥玫臅r(shí)候不需要再去new一個(gè)Message對(duì)象备典,而是通過(guò)obtain方法去獲取,如果有就不需要new了意述,如果沒(méi)有系統(tǒng)會(huì)自己去創(chuàng)建提佣。

4.MessageQueue

MessageQueue是消息隊(duì)列,其實(shí)是管理消息鏈表的荤崇。它主要功能是取出消息--next方法拌屏,將消息加入隊(duì)列--enqueueMessage方法。

我們先看加入消息隊(duì)列方法enqueueMessage术荤,也就是Handler中發(fā)送消息后加入隊(duì)列的方法:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            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;
                    }
                    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) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

插入消息隊(duì)列有兩種情況倚喂,一種是消息隊(duì)列處于空閑狀態(tài),直接將消息放在消息隊(duì)列前面瓣戚,可能需要喚醒主線程务唐,另一種是消息隊(duì)列處于忙碌狀態(tài),就不需要喚醒带兜,而是根據(jù)消息處理時(shí)間將消息插入到消息隊(duì)列的對(duì)應(yīng)位置中。

第一種狀態(tài):插入隊(duì)列頭

if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
}

if語(yǔ)句的三個(gè)條件是:一吨灭、隊(duì)列為空刚照,二、插入消息需要立即處理喧兄,三无畔、插入消息處理時(shí)間比消息隊(duì)列頭消息早,這三個(gè)條件說(shuō)明消息隊(duì)列處于閑置狀態(tài)吠冤,此時(shí)要把消息放置到消息隊(duì)列頭部浑彰,即將插入消息的next指向消息隊(duì)列的頭p,然后將消息隊(duì)列要處理的消息指向插入消息對(duì)象拯辙,最后判斷是否需要喚醒郭变,如果隊(duì)列阻塞則需要喚醒,否則不需要涯保。

第二種狀態(tài):插入隊(duì)列中間或者后面诉濒,這種情況比較復(fù)雜

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;

因?yàn)椴皇窃陉?duì)列頭,所以需要for循環(huán)去查找應(yīng)該的位置夕春,首先將第一個(gè)消息用prev進(jìn)行緩存未荒,然后當(dāng)前消息引用指向下一個(gè)消息對(duì)象,依次類推及志,直到p == null(到隊(duì)列最后)片排,或者當(dāng)前消息觸發(fā)時(shí)間小于后面這個(gè)消息的觸發(fā)時(shí)間寨腔,停止循環(huán),說(shuō)明找到了位置率寡,此時(shí)執(zhí)行最后兩行代碼迫卢,也就是將當(dāng)前出入消息的next指向p,也就是勇劣,如果p==null靖避,則說(shuō)明插入到最后一個(gè),如果不為空比默,則插入到p前面幻捏,然后將前一個(gè)prev的next指向插入的消息,此時(shí)插入成功命咐。最后的if語(yǔ)句中如果需要喚醒消息隊(duì)列篡九,則調(diào)用底層方法nativeWake喚醒消息隊(duì)列開(kāi)始循環(huán)。到此醋奠,消息插入就講完了榛臼。我們上面說(shuō)到loop方法是通過(guò)MessageQueue的next方法取出消息,那么下面我們看一下next方法是怎么取出消息的窜司。

    Message next() {
        // 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 (; ; ) {// 死循環(huán)
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                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.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {// 未到執(zhí)行時(shí)間
                        // 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.
            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;
        }
    }

mPtr是MessageQueue初始化的時(shí)候通過(guò)調(diào)用底層方法獲取的底層NativeMessageQueue的對(duì)象沛善,如果底層不能初始化則返回0,如果可以初始化返回對(duì)象地址塞祈,此處判斷金刁,如果沒(méi)有初始化也就沒(méi)有底層的NativeMessageQueue對(duì)象,因此返回null议薪。緊接著開(kāi)始for循環(huán)尤蛮,開(kāi)始遍歷消息隊(duì)列,查找需要處理的消息斯议,在這里产捞,如果消息隊(duì)列為空,或者沒(méi)有需要立即處理的消息都要使線程開(kāi)始等待哼御。接著調(diào)用nativePollOnce方法來(lái)查看當(dāng)前隊(duì)列中有沒(méi)有消息坯临,傳入?yún)?shù)nextPollTimeoutMillis表示要等待的時(shí)間,如果nextPollTimeoutMillis為0則說(shuō)明不需要等待恋昼。接著獲取當(dāng)前時(shí)間now尿扯,初始化prevMsg來(lái)緩存消息,初始化msg來(lái)緩存當(dāng)前消息mMessages焰雕,下面if語(yǔ)句判斷消息不為空但target為空衷笋,則說(shuō)明該消息為“同步分隔欄”(關(guān)于“同步分隔欄”請(qǐng)參看聊一聊Android的消息機(jī)制一文),如果該消息為同步分隔欄,則后面的同步消息都不會(huì)被查找辟宗,只能查找異步消息來(lái)處理爵赵,也就是do-while語(yǔ)句中的代碼,如果沒(méi)有同步分割欄或者找到了后面的異步消息(可能沒(méi)有)泊脐,則接著判斷空幻。

如果消息不為空,則還有消息容客,開(kāi)始判斷時(shí)間秕铛,如果當(dāng)前時(shí)間小于下一個(gè)消息的執(zhí)行時(shí)間,說(shuō)明還需要等待缩挑,那么計(jì)算需要等待的時(shí)間nextPollTimeoutMillis但两,如果當(dāng)前時(shí)間不小于當(dāng)前消息執(zhí)行時(shí)間時(shí),并且前一個(gè)消息prevMsg不為空供置,說(shuō)明出現(xiàn)了“同步分隔欄”谨湘,也就是執(zhí)行了do-while代碼,do-while執(zhí)行完芥丧,說(shuō)明找到了異步消息或者遍歷完整個(gè)隊(duì)列沒(méi)有異步消息紧阔,如果有異步消息,此時(shí)prevMsg.next = msg.next续担,也就是跳過(guò)同步消息擅耽,將異步消息msg.next賦值給prevMsg.next,然后將取出的msg的next賦值為null物遇,因?yàn)橐幚砹孙ぃ圆辉僦赶蚝竺骊?duì)列的消息對(duì)象,然后將msg設(shè)置為正在使用挎挖,并且返回,如果prevMsg為空航夺,則說(shuō)明沒(méi)有出現(xiàn)“同步分隔欄”蕉朵,此時(shí)將當(dāng)前消息mMessages的下一個(gè)消息賦值給mMessages,然后將msg.next設(shè)置為空阳掐,就是不再引用始衅,然后設(shè)置為正在使用,返回該消息缭保。

如果消息為空汛闸,則nextPollTimeoutMillis = -1,說(shuō)明沒(méi)有消息了艺骂,則接著向下執(zhí)行诸老,如果退出消息隊(duì)列,則說(shuō)明所有消息都執(zhí)行完了钳恕,最終調(diào)用nativeDestroy方法别伏,如果不退出消息隊(duì)列蹄衷,則要進(jìn)入等待狀態(tài)。如果第一次進(jìn)入厘肮,并且當(dāng)前消息為空或者消息不為空愧口,但是處于等待狀態(tài),那么要獲取IdleHandler個(gè)數(shù)类茂,如果小于等于0耍属,則說(shuō)明沒(méi)有IdleHandler運(yùn)行,調(diào)用continue執(zhí)行下一次循環(huán)巩检,如果IdleHandler個(gè)數(shù)大于0厚骗,但是等待的Handler(mPendingIdleHandlers)為空,則要?jiǎng)?chuàng)建IdleHandler數(shù)組碴巾,將mIdleHandlers放入數(shù)據(jù)溯捆,然后for循環(huán)調(diào)用每個(gè)IdleHandler的queueIdle方法,如果這個(gè)方法返回false厦瓢,則從數(shù)組移除這個(gè)對(duì)象提揍,否則保留改對(duì)象,下次空閑繼續(xù)執(zhí)行煮仇,最后將pendingIdleHandlerCount置為0劳跃,nextPollTimeoutMillis置為0,繼續(xù)下一次循環(huán)浙垫。

那么到此刨仑,整個(gè)循環(huán)就講完了,因?yàn)椴欢瓹++代碼夹姥,所以底層沒(méi)法分析杉武,只能分析framework層代碼,說(shuō)了很多還是需要自己對(duì)比代碼多理解辙售。

5.Handler的使用方法

我們?cè)谑褂肏andler的時(shí)候軟件會(huì)提示我們有問(wèn)題轻抱,那么到底該怎么寫(xiě)Handler呢,我從Stack Overflow找到了答案旦部,在這就分享一下:

首先祈搜,定義一個(gè)靜態(tài)MxHandler繼承Handler,里面使用弱引用:

public abstract class MxHandler<T> extends Handler {

    private WeakReference<T> weak;

    public MxHandler(T t) {
        this.weak = new WeakReference<T>(t);
    }

    @Override
    public void handleMessage(Message msg) {
        if (null == weak || null == weak.get()) {
            return;
        }
        handleMessage(msg, weak);
        super.handleMessage(msg);
    }

    protected abstract void handleMessage(Message msg, WeakReference<T> weak);
}

然后我們?cè)賹?xiě)具體的MyHandler繼承這個(gè)MxHandler:

private static final class MyHandler extends MxHandler<HandlerDemo> {

        public MyHandler(HandlerDemo handlerDemo) {
            super(handlerDemo);
        }

        @Override
        protected void handleMessage(Message msg, WeakReference<HandlerDemo> weak) {
            switch (msg.what) {
                case 0:
                    HandlerDemo h = weak.get();
                    h.doSomething();
                    break;
                default:
                    break;
            }
        }
    }

這樣我們?cè)贏ctivity中使用是不會(huì)出現(xiàn)內(nèi)存泄漏之類的錯(cuò)誤士八。

參考:

android的消息處理機(jī)制(圖+源碼分析)——Looper,Handler,Message
Android中Thread容燕、Handler、Looper婚度、MessageQueue的原理分析
Android 異步消息處理機(jī)制 讓你深入理解 Looper蘸秘、Handler、Message三者關(guān)系
Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析

原文地址:Android系統(tǒng)源碼分析--消息循環(huán)機(jī)制

Android開(kāi)發(fā)群:192508518

微信公眾賬號(hào):Code-MX


注:本文原創(chuàng)秘血,轉(zhuǎn)載請(qǐng)注明出處味抖,多謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灰粮,一起剝皮案震驚了整個(gè)濱河市仔涩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粘舟,老刑警劉巖熔脂,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異柑肴,居然都是意外死亡霞揉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門晰骑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)适秩,“玉大人,你說(shuō)我怎么就攤上這事硕舆』嘬瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抚官,是天一觀的道長(zhǎng)扬跋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凌节,這世上最難降的妖魔是什么钦听? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮倍奢,結(jié)果婚禮上朴上,老公的妹妹穿的比我還像新娘。我一直安慰自己卒煞,他們只是感情好痪宰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著跷坝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碉碉。 梳的紋絲不亂的頭發(fā)上柴钻,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音垢粮,去河邊找鬼贴届。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毫蚓。 我是一名探鬼主播占键,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼元潘!你這毒婦竟也來(lái)了畔乙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤翩概,失蹤者是張志新(化名)和其女友劉穎牲距,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钥庇,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牍鞠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了评姨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片难述。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吐句,靈堂內(nèi)的尸體忽然破棺而出胁后,到底是詐尸還是另有隱情,我是刑警寧澤蕴侧,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布择同,位于F島的核電站,受9級(jí)特大地震影響净宵,放射性物質(zhì)發(fā)生泄漏敲才。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一择葡、第九天 我趴在偏房一處隱蔽的房頂上張望紧武。 院中可真熱鬧,春花似錦敏储、人聲如沸阻星。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)妥箕。三九已至,卻和暖如春更舞,著一層夾襖步出監(jiān)牢的瞬間畦幢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工缆蝉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宇葱,地道東北人瘦真。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像黍瞧,于是被迫代替她去往敵國(guó)和親诸尽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容