Handler 消息機制

說起Handler機制大家都不陌生,我也看過不少網(wǎng)上相關(guān)文章,都分析的很好,這里作為筆記,通過Handler,Looper,Message,MessageQueue這幾個類,從源碼中來解讀從handler發(fā)送消息到接收到消息的過程。

通常,項目中都會用到Handler來發(fā)送一個異步消息,例如

                Message msg = Message.obtain();
//                msg.what = 0;
//                msg.obj = obj;
//                msg.arg1 = 1;
//                ...
                mHandler.sendMessage(msg);

又或者發(fā)送一個延時消息,例如

                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        // TODO ...
                        Toast.makeText(MainActivity.this, "" , Toast.LENGTH_SHORT).show();
                    }
                }, 1000);
一 、首先,我們來看看發(fā)送一個Message

進(jìn)入sendMessage(msg)方法中,再一步步尋找


    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    // >>>>>>>>>>>>>>>
    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);
    }
    // >>>>>>>>>>>>>>>
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最終進(jìn)入到enqueueMessage方法,**msg.target = this; **這是綁定了當(dāng)前Handler到發(fā)送的這個Message的target屬性,最后調(diào)用的是MessageQueue的enqueueMessage方法,將消息放入到消息隊列中。

其中,sendMessageAtTime方法中,MessageQueue queue = mQueue;賦值MessageQueue,在Handler構(gòu)造方法中發(fā)現(xiàn) mQueue = mLooper.mQueue;也就是說MessageQueue和Looper有關(guān),我們再來看看Looper類。


先說說Looper是干什么的:Loop字面意思 回路 圈 。Looper類其實是用來循環(huán)取消息隊列中消息的,類中有個兩個主要的方法,prepare和loop方法,這里有的疑問好像要解開了,往下看

  • 我們先來看看prepare方法:
    public static void prepare() {
        prepare(true);
    }

    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方法是創(chuàng)建了Looper對象,同時設(shè)置給了sThreadLocal

sThreadLocal 是一個ThreadLocal對象。簡單點說,就是當(dāng)前Looper對象和當(dāng)前的線程對象關(guān)聯(lián)起來

仔細(xì)一看,"Only one Looper may be created per thread",沒錯,每個線程只允許一個Looper對象

進(jìn)入Looper的構(gòu)造方法,很顯然,是在這里**MessageQueue被創(chuàng)建了 **

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }   
  • 再來看看loop方法
    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 (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // 省略部分代碼...
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            // 省略部分代碼...
            msg.recycleUnchecked();
        }
    }

源碼中Message msg = queue.next(); // might block 可以看到這個方法可能會阻塞,也就是說,消息隊列中沒有消息的時候會阻塞,這里是從消息隊列中取消息出來,調(diào)用個MessageQueue的next方法(具體的實現(xiàn)算法就不說了,包括入隊的算法,涉及到native層)有興趣的可以去看MessageQueue的源碼。

然后往下 msg.target.dispatchMessage(msg);這句代碼很好理解,前面我們說過在Handler發(fā)送消息的時候,會關(guān)聯(lián)當(dāng)前的Handler到所發(fā)送的Message的target屬性,這里看到就是調(diào)用Handler的dispatchMessage()方法,

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

方法很簡單,最終走的是handleMessage(msg);這行代碼,點過去看到是調(diào)用空方法,就是我們需要重寫的handleMessage方法。方法中我們還發(fā)現(xiàn)還可能會走h(yuǎn)andleCallback(msg);這行代碼,其實,這就是另一種情況,是利用Handler發(fā)送延時消息最后走的方法,請往下看。

二、延時發(fā)送一條消息

從Handler的postDelayed方法看起

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
    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);
    }

最后調(diào)用的還是和上面分析發(fā)送一條消息時一樣,不過,這里多了一步getPostMessage,這是創(chuàng)建一個Message,并且賦值了Message的callback屬性,也就是我們在postDelayed方法中傳入的Runnable

到這里,其實已經(jīng)分析結(jié)束了,Looper和MessageQueue分析如上,在Handler的dispatchMessage方法中執(zhí)行的是handleCallback(msg);因為msg.callback != null成立,最終,執(zhí)行的是Message的callback,也就是我們傳入的Runnable

    private static void handleCallback(Message message) {
        message.callback.run();
    }

前面我們說了從Handler發(fā)送消息,然后進(jìn)入消息隊列,Looper再循環(huán)從MessageQueue中取出消息,最后執(zhí)行相應(yīng)的方法。細(xì)心的觀眾發(fā)現(xiàn),Handler我們可以自己new出來,Message可以obtain,MessageQueue是由Looper中生成的,那么Looper又是怎么初始化的呢?

這里涉及到了ActivityThread這個類,我們都知道,ActivityThread中的main方法是整個應(yīng)用進(jìn)程的入口

查看源碼

    public static void main(String[] args) {
        // 省略代碼...
        Looper.prepareMainLooper();
        // 省略代碼...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

看到了,Looper被初始化了,并且調(diào)用了loop,開始輪詢消息隊列。

解釋一下prepareMainLooper()和prepare方法有什么不同,就是true|false的區(qū)別,表示是否可以被終止,因為這是主線程的Looper,所以false.

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

有點恍然大悟的感覺,為什么主線程中可以更新UI控件了,原來是系統(tǒng)主線程中會主動初始化一個Looper(我們?nèi)绻謩釉賞repare就會報錯,因為每個線程只允許一個Looper,也確保我們可以在不同的線程中創(chuàng)建各自的Handler,進(jìn)行各自的通信而不會互相干擾)。那么,子線程中除了使用Handler或者調(diào)用runOnUiThread來更新UI控件,當(dāng)然也可以前后加上Looper.prepare();和Looper.loop();,不過實際開發(fā)中不會這么用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,570評論 2 379

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