Android 源碼分析 - 消息處理機制

??在學(xué)習(xí)Android過程中峰伙,Android的消息機制是必須需要掌握的。樓主也只能算是一個Android的初學(xué)者,對Android的消息機制掌握的也不算很好。這里記錄一下我自己對Android消息機制的理解蜂林,錯誤之處,希望各位指正拇泣。
??本文參考文章:
??1.Android 異步消息處理機制 讓你深入理解 Looper噪叙、Handler、Message三者關(guān)系
??2.Android應(yīng)用程序消息處理機制(Looper挫酿、Handler)分析

1.概述

??要講消息處理機制构眯,不得不講的就是Handler、Looper早龟、MessageQueue這個重量級的人物惫霸。在這里對他們做一個概述,待會會詳細的介紹他們葱弟。
??從大體上來說壹店,Handler的主要作用就是發(fā)送Message和處理Message;Looper的作用就是不斷從MessageQueue(消息隊列)中取Message芝加,然后交給Handler處理硅卢;MessageQueue存放由Handler發(fā)送的Message。通常來說藏杖,Message里面就是需要主線程執(zhí)行的操作将塑,比如,我們從網(wǎng)絡(luò)獲取的文字內(nèi)容信息蝌麸,需要顯示到TextView上面去点寥。

2.Handler

??首先我們從上面走下去,從Handler開始走来吩。
??通常來說敢辩,使用Handler來處理異步消息,有基本的步驟弟疆。這里做一個簡單的例子戚长,在線程中發(fā)送一條String,讓TextView來顯示怠苔。

    private Button mButton = null;
    //創(chuàng)建一個Handler的對象同廉,注意我們重寫了handleMessage消息
    //handleMessage方法就是用來處理我們發(fā)送的消息
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            mButton.setText(msg.obj.toString());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button: {
                sendMessage();
                break;
            }
        }
    }

    private void sendMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //創(chuàng)建一個Message的對象
                Message message = Message.obtain();
                message.obj = "我被點擊了";
                //發(fā)送消息
                mHandler.sendMessage(message);
            }
        }).start();
    }

??上面代碼中,我相信不難吧。現(xiàn)在我們需要理解的是Handler是怎么將一個Message從子線程傳遞到主線程中恤溶,然后執(zhí)行的乓诽!

(1).sendMessage方法

??我們需要理解其中的緣由,我們來sendMessage方法的代碼咒程。

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

??我們在一步一步的跟蹤下去鸠天,最終到了enqueueMessage方法中

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

??其中,這個方法的三個參數(shù),queue表示message需要進入的消息隊列帐姻,msg就是我們之前創(chuàng)建的Message對象稠集,uptimeMillis表示延遲時間。延遲時間這里不重要饥瓷,我們先不看剥纷。
??我們從源代碼中可以看出,首先msg.target = this呢铆。這句話將一個handler賦值到message的target屬性晦鞋,這一步非常重要,后面需要用的棺克。這里我們先記住悠垛,這里的意思就是,表示當(dāng)前的這個message屬于哪個handler娜谊,因為有可能有很多的handler都在發(fā)送消息确买!
?? mAsynchronous我們不看,這里不重要纱皆。最后是調(diào)用了queue.enqueueMessage(msg, uptimeMillis)方法湾趾,從這個方法的字面意思中,我們就可以看出來派草,這個方法是將一個message添加到queue的消息隊列搀缠。
??我們先不看queue里面的代碼。我們先來總結(jié)我們得到的信息近迁。

??1.我們在子線程中發(fā)送一個Message胡嘿,最終會進入到一個queue的消息隊列中去。
??2.在Message進入消息隊列之前钳踊,使用target字段來標(biāo)記了當(dāng)前的Message是哪個Handler發(fā)送的。

??到這里勿侯,我們好像沒有收獲拓瞪,不急,我們來看看在構(gòu)造一個Handler的時候助琐,給我們創(chuàng)建那些東西祭埂!

(2).Handler的構(gòu)造方法

??Handler的構(gòu)造方法最終調(diào)用到了Handler(Callback callback, boolean async)方法里面。

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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的構(gòu)造方法中,我們發(fā)現(xiàn)了幾個小細節(jié)蛆橡,mLooper舌界、mQueue都被賦值了。Looper我們待會講泰演,我們在調(diào)用sendMessage方法的時候呻拌,發(fā)現(xiàn)其中調(diào)用到這一步:

    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);
    }

??我們注意到的是,之前的enqueueMessage里面的queue就是mQueue睦焕。
??到這里藐握,我們知道了:

??1.Handler在創(chuàng)建的時候,會創(chuàng)建一個MessageQueue和一個Looper垃喊,也就是說猾普,一個Handler同時綁定一個MessageQueue和一個Looper。
??2.我們在調(diào)用sendMessage等方法來發(fā)送消息的時候本谜,消息進入的消息隊列就是發(fā)送消息的Handler自己的MessageQueue初家。

2.Looper

??記得在概述中描述,Handler是發(fā)送消息和處理消息乌助,現(xiàn)在我們對發(fā)送消息有了一個簡單的認(rèn)識溜在,但是我們對處理消息沒有介紹。不急眷茁,要想知道處理消息炕泳,我們必須先知道,消息是怎么被獲取出來的上祈,難道是Handler直接伸手從MessageQueue中去拿嗎培遵?怎么可能這么簡單嗎?接下來登刺,我們介紹一下Looper籽腕,又一位重量級的人物

(1).Looper構(gòu)造方法

??我們先來看看Looper的構(gòu)造方法

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

??我們在這個構(gòu)造方法里面發(fā)現(xiàn),Looper創(chuàng)建一個MessageQueue纸俭。但是除了這個信息皇耗,似乎沒看其他有用的信息了。
??怎么辦揍很?通常來說使用Looper不會直接的創(chuàng)建郎楼,從這里我們也看到它的構(gòu)造方法是private的,而是調(diào)用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方法中呜袁,需要注意的是:sThreadLocal變量是一個ThreadLocal變量。這種類型的變量有什么呢简珠?具體的解釋大家可以到網(wǎng)上去找(其實我也不是很懂阶界,哈哈!!1烊凇)芙粱。這里簡單的解釋一下,ThreadLocal里面封裝了一個變量氧映,用來存儲相應(yīng)的信息春畔,關(guān)鍵是不同的線程從ThreadLocal里面獲取的變量是不同的,每個線程從ThreadLocal獲取的是自己的變量屯耸,線程之間的是不會相互影響拐迁。
??從這里,我們可以得到是疗绣,每個線程都會有一個自己的Looper對象线召。
??到這里,我們知道:

??1.Looper的對象不能直接new多矮,而是調(diào)用靜態(tài)方法prepare方法來創(chuàng)建
??2.同一個線程不能調(diào)用兩次prepare方法缓淹,因為Looper對象保存在一個ThreadLocal對象。
??3.從之前的Handler中塔逃,我們可以看到Handler里面持有了一個Looper對象(通過調(diào)用方法mLooper = Looper.myLooper())讯壶。這個對象就是TreadLocal封裝的對象。從而得出湾盗,如果這個Handler在主線程中創(chuàng)建的伏蚊,那么Looper肯定在屬于主線程。

??從上面的結(jié)論中格粪,我們可以得出躏吊,如果我們在一個線程中創(chuàng)建一個Handler,必須先創(chuàng)建當(dāng)前線程的Looper對象帐萎。但是我們比伏,你會發(fā)現(xiàn),之前那個例子中疆导,我們直接在一個Activity中創(chuàng)建一個Handler對象赁项,而沒有創(chuàng)建主線程的Looper,這個為什么沒有報錯呢澈段?我們可以假設(shè)悠菜,我們在創(chuàng)建Handler時,主線程的Looper早已經(jīng)被prepare了败富!但是具體在哪里調(diào)用的呢悔醋?待會我們會展示!

(2).loop方法

??在之前說過囤耳,Looper的作用就是不斷從MessageQueue(消息隊列)中取Message,然后交給Handler處理。但是我們到現(xiàn)在還不知道Looper到底是怎么從MessageQueue里面去消息的充择,這個就得引出我們的loop方法德玫。
??我們先來看看loop方法的源代碼,沒有貼出完整的代碼,而是刪除了一些自己認(rèn)為不重要的代碼:

    public static void loop() {
         //取得當(dāng)前線程的Looper--還記得我們之前的說的ThreadLocal變量椎麦,這個變量里面保存的就是當(dāng)前線程的
         //Looper對象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲取當(dāng)前Looper的消息隊列對象
        final MessageQueue queue = me.mQueue;
       // 死循環(huán)
        for (;;) {
            //如果沒有消息的話宰僧,當(dāng)前線程會被阻塞住,
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //將當(dāng)前的Message分發(fā)到Message自己的Handler
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            msg.recycleUnchecked();
        }
    }

??我們先來整理一下观挎,這個方法主要描述內(nèi)容:

??1.首先獲取當(dāng)前線程的Looper對象和MessageQueue對象
??2.從MessageQueue里面去獲取Message琴儿;如果獲取到Message,則分發(fā)到Message自己的Handler去處理嘁捷;反之造成,沒有獲取到信息,線程會被阻塞在next方法里面雄嚣。
??3.通過Handler的dispatchMessage方法將獲取的到Message從MessageQueue傳遞到了Handler晒屎,讓Handler去處理。

??到這里缓升,我們來整理一下思路鼓鲁。首先,在主線程創(chuàng)建了一個Handler港谊,表示當(dāng)前的Looper是放在主線程中的骇吭,當(dāng)然MessageQueue也是屬于主線程的。這里需要說明的是歧寺,就是一個線程只能有一個Looper燥狰,由于MessageQueue在Looper里面,所以MessageQueue也是只有一個成福,但是一個線程中Handler可以會被多次創(chuàng)建碾局,所以,屬于同一個線程的Handler中持有的是同一個MessageQueue和Looper奴艾。
??我們使用在主線程中創(chuàng)建的Handler通過調(diào)用sendMessage等方法來發(fā)送消息净当,最終進入的消息隊列是主線程的MessageQueue。我們在Looper的loop方法中獲取Message蕴潦,然后將Message分發(fā)到Message的target像啼,也就是發(fā)送Message的Handler,讓它來處理。
??整個消息傳遞的過程就是這樣的潭苞,這個就能解釋忽冻,為什么我們在子線程中發(fā)送一個Message,在主線程的Handler能接收得到此疹,從而進行對消息處理僧诚。因為Handler本身就是屬于主線程遮婶,包括Handler里面持有的Looper和MessageQueue都是屬于主線程。
??這里有一個疑問湖笨,那就是旗扑,我們知道我們在主線程中創(chuàng)建Handler時,其實主線程的Looper早就已經(jīng)創(chuàng)建好了慈省,這一點是怎么看出來得呢臀防?
??例如:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //這里將Looper的prepare方法注釋了
                //表示當(dāng)前的線程Looper沒有被初始化
//                Looper.prepare();
                Handler mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", msg.obj.toString() + " " + Thread.currentThread().getName());
                    }
                };
                Message message = Message.obtain();
                message.obj = "pby123";
                mHandler.sendMessage(message);
            }
        }).start();

??然后我們來運行一下程序,會發(fā)現(xiàn)一個臭名昭著的異常边败!



??這個異常我們應(yīng)該不陌生袱衷,因為凡是要使用Looper的地方,都用check一下Looper是否preapre完成笑窜。
??上面的樣例致燥,我們是從一個新的線程里面創(chuàng)建Handler,由于是新的線程怖侦,就必須將這個線程的Looper和MessageQueue創(chuàng)建好篡悟。
??從而得出,我們在主線程中可以直接創(chuàng)建Handler匾寝,表示主線程的Looper和MessageQueue在我們創(chuàng)建Handler之前就已經(jīng)創(chuàng)建完畢了残腌!
??那到底是在哪里創(chuàng)建主線程的Looper导盅。這個得引出ActivityThread類的main方法环凿,我們都知道m(xù)ain方法是Java程序的入口讥电,我們來看看main方法:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        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");
    }

??是不是看到main方法就感覺非常的親切,終于可以回到當(dāng)初學(xué)習(xí)Java的時候了猜年。我也是抡锈,看到main方法之后,感覺之前有好多的問題都解決了乔外!
??在main方法里面床三,我們看到:Looper.prepareMainLooper();這一句話的意思就是初始化的主線程的Looper。從這里杨幼,我們知道了撇簿,為什么我們在主線程里面可以創(chuàng)建Handler,而不用去prepare Looper差购。
??同時四瘫,我們還注意的是,Looper.loop()欲逃。我們之前已經(jīng)分析過了找蜜,loop方法主要是在MessageQueue里面去取消息來處理。由于這里是主線程的Looper稳析,所以這里的loop方法是從主線程的MessageQueue里面取Message洗做。
??之前弓叛,我們使用一個在主線程創(chuàng)建的Handler在一個子線程發(fā)送Message,最后發(fā)送到了主線程的MessageQueue诚纸。但是什么時候調(diào)用的loop呢邪码?其實就是在main方法里面調(diào)用了的。
??同樣的道理咬清,如果我們在一個新的線程里面分別創(chuàng)建Handler、Looper奴潘、MessageQueue旧烧,當(dāng)調(diào)用Handler的sendMessage方法來發(fā)送一條Message,在Handler的handleMessage方法是不會接收到這個Message對象画髓,這個是由于Looper的loop方法啟動掘剪,當(dāng)前線程
??我們知道,loop方法是一個死循環(huán)奈虾,但是為什么不會導(dǎo)致應(yīng)用ANR呢?(Application Not Response)夺谁。其實網(wǎng)絡(luò)上有很多的解釋,可能是自己太笨了肉微,理解不了那些思路匾鸥。下面將來結(jié)合Looper的loop方法和MessageQueue的部分方法來解釋一下原因。

4.MessageQueue

??我們知道MessageQueue相當(dāng)于是一個存放Message的容器碉纳,Handler往這個容器里面放入Message勿负,Looper從這個容器取數(shù)據(jù)。我們先來看看存放數(shù)據(jù)的部分
??在Looper的loop方法里面有這么一句:

Message msg = queue.next(); // might block

??上面這一句劳曹,就是Looper從MessageQueue里面取Message的操作奴愉,我們注意一下官方的注釋,這一句可能會導(dǎo)致阻塞铁孵!
??那么什么情況下會被阻塞呢锭硼?是不是當(dāng)前MessageQueue為空的時候,會被阻塞呢蜕劝?如果MessageQueue為空時檀头,會被阻塞,這一句又是什么作用呢熙宇?

            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

(1).next方法

??我們先來看看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 (;;) {
            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) {
                        // 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;
        }
    }

??首先烫止,我們來看看這一句話

            nativePollOnce(ptr, nextPollTimeoutMillis);

??根據(jù)Android應(yīng)用程序消息處理機制(Looper蒋荚、Handler)分析中介紹,nativePollOnce方法是用來表示當(dāng)前線程會被阻塞,nextPollTimeoutMillis表達的意思就是當(dāng)前的線程需需要阻塞多久馆蠕。其中如果nextPollTimeoutMillis為0期升,表示不阻塞惊奇;-1表示無限期的阻塞,除非有線程喚醒它播赁;其他的正數(shù)值表示阻塞多久颂郎。
??然后我們來看看下面的代碼

                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;
                }

??其中,msg表示當(dāng)前的消息隊列中第一個可以用的Message容为。其中先判斷了當(dāng)前的時間與消息執(zhí)行的時間的大小乓序,如果表示當(dāng)前的時間小于Message執(zhí)行的時間,也就是說坎背,還沒有到達該Message執(zhí)行的時間替劈,因此給nextPollTimeoutMillis賦值,表示線程要阻塞多久得滤。
??但是陨献,如果當(dāng)前的時間符合Message的執(zhí)行時間了,就得馬上返回這個Message懂更,去執(zhí)行里面的操作眨业。
??如果當(dāng)前取得的第一個Message是空的,表示當(dāng)前的MessageQueue里面沒有了Message了沮协,所以將nextPollTimeoutMills重置為-1龄捡,表示當(dāng)前將無限期的阻塞下去,除非其他的線程將喚醒慷暂!
??其中我們還需要注意的是:

                if (mQuitting) {
                    dispose();
                    return null;
                }

??這個return是整個next方法里面第二個return null墅茉,還記得我們在loop方法里面的一段代碼嗎?就是如果從next方法里面獲取的方法是null的話呜呐,那么直接退出了loop循環(huán)就斤。這個return null的返回條件就是就能解釋我們之前的那個疑問--如果MessageQueue為空時,會被阻塞蘑辑,這一句又是什么作用呢洋机?
??第一個return null的條件是mPtr等于0時,關(guān)于mPtr等于0是表示什么情況洋魂,我也不是清楚绷旗,大概應(yīng)該說,當(dāng)前的MessageQueue對頭的地址吧副砍,這個只是我的猜測不一定正確衔肢!
??還是來看看第二個return null的條件吧,從中可以知道只有當(dāng)mQuiting為true豁翎,才能返回 null角骤。從這個變量名,我們可以知道心剥,當(dāng)這個MessageQueue正在quit時邦尊,會返回null背桐。也就是說,當(dāng)MessageQueue要被回收時蝉揍,Looper會退出它的loop死循環(huán)链峭。其他的情況下,loop都是在進行死循環(huán)的又沾。
??但是弊仪,在哪一種情況下MessageQueue會被回收呢?我們發(fā)現(xiàn)MessageQueue的quit方法是默認(rèn)修飾符杖刷,也就是說撼短,只能在相同的包下,這個方法才能被調(diào)用挺勿!在我們的應(yīng)用程序代碼中是不能直接調(diào)用的,那哪里可以調(diào)用了quit方法呢喂柒?我們在Looper的quit方法里面找到了答案:

    public void quit() {
        mQueue.quit(false);
    }

??我們知道不瓶,每一個Looper對象是被放在了一個ThreadLocal變量中,也就是說灾杰,在哪個線程調(diào)用Looper的quit方法蚊丐,就是quit哪個線程的MessageQueue,從而退出這個Looper的loop循環(huán)艳吠。
??例如:

    private void sendMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", (msg.obj) + "");
                    }
                };
                Message message = Message.obtain();
                message.obj = "我是Message";

                handler.sendMessageDelayed(message, 2000);
                Looper.loop();
                Log.i("pby123", "123");


            }
        }).start();
    }

??上面的代碼中麦备,我們在一個新的線程里面創(chuàng)建了一個Handler、Looper昭娩,然后使用Handler發(fā)送了一個消息凛篙,最后將調(diào)用Looper的loop方法,讓這個Looper活起來栏渺!然后我們來看看log:



??我們會發(fā)現(xiàn)呛梆,123這個log始終沒有打印出來,也就是說磕诊,根本沒有執(zhí)行到這一句話填物,從而推出此時Looper的loop方法還在執(zhí)行,當(dāng)然如果沒有消息的話霎终,應(yīng)該是被阻塞住了滞磺。這個現(xiàn)象間接的證明了,我們對源碼的理解莱褒!
??這里需要注意的是击困,在同一個線程中,代碼不能這樣寫:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //這里將Looper的prepare方法注釋了
                //表示當(dāng)前的線程Looper沒有被初始化
                Looper.prepare();
                Handler mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i("pby123", (msg.obj) + "");
                    }
                };
                
                Looper.loop();

                Message message = Message.obtain();
                message.obj = "我是Message";

                mHandler.sendMessageDelayed(message, 2000);
                Log.i("pby123", "123");


            }
        }).start();

??這樣寫的話广凸,loop一直在執(zhí)行沛励,根本不能執(zhí)行到loop的代碼责语!
??從而得知:

??1.除主線程之外,所有線程的Looper的loop方法不會自動調(diào)用目派,盡管我們調(diào)用Handler發(fā)送消息的相關(guān)方法坤候。只有當(dāng)我們在線程中明確調(diào)用loop方法,才會進行l(wèi)oop的循環(huán)企蹭。
??2.loop方法是一個死循環(huán)白筹,它不斷的從MessageQueue里面去拿Message。只有當(dāng)取出的Message為null時谅摄,loop的死循環(huán)才能被結(jié)束徒河。
??2.在MessageQueue的next方法里面,如果取出的Message的執(zhí)行時間大于當(dāng)前的時間的話送漠,當(dāng)前線程就會被阻塞相應(yīng)的差值時間顽照。但是如果當(dāng)前的MessageQueue為空的話,那么就會當(dāng)前的線程就會被無限期的阻塞闽寡。
??3.如果當(dāng)前的MessageQueue被調(diào)用了quit方法代兵,Looper的loop方法從MessageQueue里面取出的是null,從而導(dǎo)致Looper的loop方法執(zhí)行完成爷狈。
??4.MessageQueue的quit方法不能被直接調(diào)用植影,而是通過Handler的quit來間接實現(xiàn)的!

??到這里涎永,我們可以知道思币,為什么loop方法不會導(dǎo)致ANR。這是因為loop不是一直執(zhí)行羡微,而是當(dāng)前MessageQueue里面有Message時谷饿,才會不斷的去獲取Message;如果MessageQueue是空的話妈倔,線程會被阻塞各墨,只有當(dāng)消息的時候才會被喚醒,loop方法才算是又活起來了启涯!

(2).enqueueMessage方法

??說實話贬堵,next方法真的不是很好的理解,特別是結(jié)合了Handler结洼、Looper之后!但是黎做,我們最終還是對next有了一個大概的理解,我可不敢說深入的理解松忍,哈哈蒸殿!因為自己本來就是一個菜雞!
??對next方法有了一個大概的理解,那么理解enqueueMessage方法應(yīng)該不是很難的宏所!先來看看enqueueMessage方法的代碼:

    boolean enqueueMessage(Message msg, long when) {
        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;
    }

??上面的代碼中酥艳,我刪除了部分我認(rèn)為不重要的代碼。這個方法非常好理解爬骤,首先判斷這個MessageQueue是否被標(biāo)記為回收充石。如果被回收了的話,返回false霞玄,表示插入新的Message失斨枇濉;其次如果沒有被回收坷剧,那么插入將Message插入到正確的位置惰爬,這個正確的位置表示的是,按照Message的執(zhí)行時間從小到大排序惫企,時間最小的撕瞧,放在隊頭!
??我覺得這里最重要的一步是這個:

            if (needWake) {
                nativeWake(mPtr);
            }

??如果當(dāng)前需要喚醒的話狞尔,就去喚醒丛版!

5.總結(jié)

??說實話,現(xiàn)在這篇文章寫得差不多了沪么,該寫一個總結(jié),將理解到的知識概括一下锌半!

??1.在創(chuàng)建Handler之前禽车,必須將當(dāng)前線程的Looper線程創(chuàng)建完畢,也就是Looper的preapre方法必須放在Handler創(chuàng)建之前刊殉。之所以在線程中不用創(chuàng)建Looper殉摔,那是因為Google爸爸在ActivityThread的main方法里面已經(jīng)給我們創(chuàng)建好了!
??2.Looper不會自動調(diào)用记焊,必須在本線程中的最后一行代碼來調(diào)用逸月!因為一旦loop方法執(zhí)行完畢,表示當(dāng)前的線程即將執(zhí)行完畢遍膜。這個可以參考ActivityThread的main方法代碼碗硬!
??3.在調(diào)用Handler的發(fā)送Message的相關(guān)方法時,最終會將該Message放入與該線程綁定的MessageQueue里面瓢颅。在放入Message恩尾,需要注意的是,Message本身帶了when屬性挽懦,這個表示Message時間翰意,MessageQueue內(nèi)部使用的這個When來排的序。
??4.在Looper的loop方法中,會不斷的調(diào)用MessageQueue的next來獲取一個Message冀偶。在獲取Message時醒第,要分為兩種情況:1.Message不為null,那么就該Message分發(fā)到Message屬于的那個Handler里面去消耗进鸠;2.如果為null的話稠曼,表示當(dāng)前的MessageQueue已經(jīng)被調(diào)用quit方法了,loop方法就會結(jié)束堤如,整個線程就會結(jié)束蒲列!
??5.在MessageQueue的next方法里面,如果獲取的Message執(zhí)行時間大于當(dāng)前的時間搀罢,表示還沒有達到該Message的執(zhí)行時機蝗岖,于是讓當(dāng)前的線程阻塞相應(yīng)的時間;如果獲取的Message符合執(zhí)行時機的話榔至,那么立即返回抵赢;如果當(dāng)前的MessageQueue為空了,立即阻塞當(dāng)前的線程唧取!

??最后铅鲤,在結(jié)合的知識來解釋一下,為什么我們在一個子線程中發(fā)送一個Message枫弟,主線程會被接受得到邢享。
??首先,我們我們使用的是在主線程中創(chuàng)建Handler來發(fā)送Message淡诗,雖然說是在子線程發(fā)送Message骇塘,但是Message有一個target用來記錄發(fā)送它的Handler。同時用于Handler在創(chuàng)建的時候韩容,會持有當(dāng)前線程的Looper和MessageQueue款违,所以這個Message最終還是發(fā)送了主線程的MessageQueue中。而在ActivityThread的main方法里面群凶,主線程的Looper在不斷的loop插爹,所以不斷的從自己線程的MessageQueue取Message來消耗,Message被獲取之后请梢,然后交給了target的handlerMessage方法去消耗赠尾,這個target就是在主線程我們自己創(chuàng)建的Handler。最終毅弧,我們就能知道為什么在子線程里面發(fā)送的Message能夠到達主線程中的Handler

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萍虽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子形真,更是在濱河造成了極大的恐慌杉编,老刑警劉巖超全,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異邓馒,居然都是意外死亡嘶朱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門光酣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疏遏,“玉大人,你說我怎么就攤上這事救军〔埔欤” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵唱遭,是天一觀的道長戳寸。 經(jīng)常有香客問我,道長拷泽,這世上最難降的妖魔是什么疫鹊? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮司致,結(jié)果婚禮上拆吆,老公的妹妹穿的比我還像新娘。我一直安慰自己脂矫,他們只是感情好枣耀,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著庭再,像睡著了一般捞奕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佩微,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天缝彬,我揣著相機與錄音萌焰,去河邊找鬼哺眯。 笑死,一個胖子當(dāng)著我的面吹牛扒俯,可吹牛的內(nèi)容都是我干的奶卓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼撼玄,長吁一口氣:“原來是場噩夢啊……” “哼夺姑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掌猛,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤盏浙,失蹤者是張志新(化名)和其女友劉穎眉睹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體废膘,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡竹海,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丐黄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斋配。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灌闺,靈堂內(nèi)的尸體忽然破棺而出艰争,到底是詐尸還是另有隱情,我是刑警寧澤桂对,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布甩卓,位于F島的核電站,受9級特大地震影響接校,放射性物質(zhì)發(fā)生泄漏猛频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一蛛勉、第九天 我趴在偏房一處隱蔽的房頂上張望鹿寻。 院中可真熱鬧,春花似錦诽凌、人聲如沸毡熏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痢法。三九已至,卻和暖如春杜顺,著一層夾襖步出監(jiān)牢的瞬間财搁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工躬络, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尖奔,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓穷当,卻偏偏與公主長得像提茁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子馁菜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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