從源碼看Handler消息機制

handler簡介

爲什麼需要handler
在介紹handler之前還得說說主線程。主線程運行所有的UI界面,設備會將用戶所有操作轉換爲消息並放入消息隊列中,然後主線程就位於一個循環中,在消息隊列取消息來一個個完成,每條消息處理用時不能超過5s,否則將ANR異常。所以對於超5s的任務都應該在子線程去完成,而子線程又沒有辦法對UI界面的內容進行操作,不然就報CalledFromWrongThreadException,於是就有了handler消息傳遞機制羅。那我們就可以在子線程進行耗時操作,然後把界面上需要的數據通過handler發送給主線程,然後主線程接受並處理,這樣就可以解決那些問題了。
再總結一下,handler是用來在各個線程之間發送數據,任何線程只要獲得另一個線程的handler,就可以通過handler向那個線程發送數據。

常用類

我們先大概瞭解一下他都設計到哪些常用的類,並且是負責做什麼。
- Message:消息。其中包含了消息ID,消息處理對象以及處理的數據等,由Handler處理。
- Handler:處理者。使用sendMessage()方法進行message的發送,最終由handleMessage()方法進行message處理。
- MessageQueue:消息隊列。用來存放Handler發送過來的消息,並按照FIFO規則執行。這裏的存放只是將Message串聯起來的,好讓Looper進行抽取。
- Looper:消息泵。使用Loop.loop()不斷地從MessageQueue中抽取Message執行。
- Thread:線程。負責整個消息循環的執行場所。
一個線程只會有一個MessageQueue和一個Looper,但可以有多個handler。

Handler主要成員變量

    final Looper mLooper; //若構造方法沒傳此值,則Handler在哪個線程創建就與該線程的Looper綁定;若傳了就是傳進來的值
    final MessageQueue mQueue; //mLooper的MessageQueue
    final Callback mCallback; //內部接口,用來處理消息的
    final boolean mAsynchronous;

Loop主要成員變量

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one
 * thread do not affect the other threads. The implementation supports {@code null} values.
 * 這個是ThreadLocal類的官方解釋,大意就是它實現一個線程本地存儲變量,能讓每個線程擁有自己value。所有的線程可以共用同一個  ThreadLocal對象,並且每個線程可以通過這個對象獲取自己的value,而且在不影響其它線程的情況下修改value。
 */
//注意到這裏是static,則無論有多少Looper對象也只會有一個ThreadLocal對象,就像上面講的所有線程共用同一個ThreadLocal對象。通過sThreadLocal.get()得到各個線程的Looper對象(對應value),若子線程未調用Looper.prepare()就會Looper對象爲空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // 主線程的Looper對象,android自動創建。
final MessageQueue mQueue; //與Looper對象關聯的消息隊列
final Thread mThread; //與Looper對象關聯的線程

Message的主要成員變量

long when; //消息發送的時間,也是根據它排隊進入消息隊列的,最早排最前
Handler target;//用來綁定發消息的handler對象,確保消息的執行也是此對象
Runnable callback; //若他不爲空,此消息分發成功就是處理callback任務,而不會讓handler處理了
Message next;//用來實現隊列這種鏈表結構

大體流程

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler() {
                handleMessage(){}
                };
                Looper.loop();
            }
        }) ;
然後在另一個線程裏發消息。

詳細講解

  1. 第一步Looper.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));//爲每個線程設置自己的Looper對象
    }
 private Looper(boolean quitAllowed) {
        //初始化成員變量
        mQueue = new MessageQueue(quitAllowed);//爲每個Looper對象關聯一個MessageQueue對象
        mThread = Thread.currentThread();//關聯此Looper對象所在線程
    }

其實這個方法就是創建了個Looper對象,給Looper做了一些初始化操作,主要就是將Looper對象和所在線程綁定,初始化自己的MessageQueue。
2. 第二步創建handler對象
他有7個構造方法,我就只寫重點並只留構造方法裏的幾個主要地方吧。

 public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper(); //沒指定Looper的情況就會取Handler創建所在線程的這個Looper,在另一個構造方法可以指定Looper,那就綁定給定Looper的所在線程,
        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;
    }
 //Looper.myLooper()方法
 public static Looper myLooper() {
        return sThreadLocal.get();//sThreadLocal前面有講,理解成單例吧,它能得到所在線程自己的value
    }

其實這裏也就是給Handler對象進行初始化,綁定相應的Looper,也就相當於綁定了相應的MessageQueue和自己在的那個線程。
3. 第三步使用Handler對象發消息
有如下可發消息的方法

post(); postDelayed(); //把Runnable任務作爲消息加入到消息隊列
postAtTime();
sendEmptyMessage(); sendMessage(); sendMessageDelayed(); 
sendMessageAtTime();
這些方法最終都是運行sendMessageAtTime //指定時間爲uptimeMillis發送消息
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;  //this是當前handler對象,要發送的消息msg與發送此msg的handler對象綁定了
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    //MessageQueue的enqueueMessage方法,根據when把消息加入消息隊列,只複製了重要代碼。
 boolean enqueueMessage(Message msg, long when) {
            msg.markInUse();
            msg.when = when; //此消息要發送的時間
            Message p = mMessages; //mMessages屬性代表MessageQueue的第一條消息
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { 
            //如果MessageQueue裏沒有消息,或者此消息執行時間小於等於0,則都將此消息設爲頭指針,也就是作爲第一條消息
                msg.next = p; 
                mMessages = msg;
                needWake = mBlocked; 
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) { //將消息根據時間when早晚找到此消息需要插入隊列的位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // 消息插入
                prev.next = msg;
            }
        }
        return true;
    }

不管handler以何種方式發消息,最終都會把這些消息根據msg.when,運行MessageQueue的enqueueMessage()存入消息隊列,這個消息隊列是在與此handler綁定的Looper對象裏面。
4.第四步Looper.loop()取消息;

public static void loop() {
        final Looper me = myLooper(); //獲取所在線程的Looper對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) { // 沒有消息就代表消息隊列已經取完了.
                return;
            }
 ***********msg.target.dispatchMessage(msg);//把從隊列取出的消息分發出去
            msg.recycleUnchecked();//回收消息
        }
    }
//msg.target.dispatchMessage(msg)方法,Handler的方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); //如果msg的callback不爲空就代表這個消息有自己的任務,於是運行此方法,此方法實際就是msg.callback.run();
        } else { //msg.callback爲空那就終於可以運行handleMessage了,你可以自己重寫它實現自己的需求
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

從此線程的Looper裏的MessageQueue裏取消息,取出一條就發送一條msg.target.dispatchMessage(msg),然後就可以相應的處理這些消息了。

常見問題

1.主線程中爲什麼沒有看到Looper對象?
Looper對象就是用來綁定所在線程,爲線程開啓一個消息循環,從而操作MessageQueue。默認情況下系統自動爲主線程創建Looper對象,開啓消息循環。因此主線程我們只會看到new Handler。而子線程就不行了。

2.handlerA發送的消息可以handlerB來處理嗎?
不可以。
不知你會不會有這樣疑問,不是一個線程只會有一個Looper對象一個MessagerQueue,可以有多個handler嘛,那說明多個handler共用一個MessagerQueue,這樣的話就隨便哪個handler發送的消息都在一個隊列裏羅,對的這都沒錯,那麼handlerA發的消息在這個隊列,handlerB用的也是這個隊列,那麼就能處理羅。其實若真正搞懂了這個機制也不會有這個問題,看看我的第三步發消息和第四步取消息裏在前面加了一串 * 的代碼,handlerA發消息時msg.target已經是handlerA了,而取消息時`msg.target.dispatchMessage(msg);則運行handlerA的dispatchMessage()方法,所以消息最終只會調用handlerA的相應處理消息的方法,(當然也可能會是消息本身處理掉)不管怎樣都跟handlerB沒關係。

3.使用handler在子線程發消息,主線程處理消息,爲什麼子線程不需要Loope對象呢?
其實只要確保取消息和存消息在同一個線程完成就行。表面上發消息是在子線程處理的,但實際發出的消息放入的隊列是根據此handler對象在創建時的線程決定的,而不是發送消息時的線程決定,那麼發消息時,給存放消息的隊列是主線程,而取消息也是主線程,主線程默認執行Loop.loop。簡單的說就是Handler對象在哪個線程創建哪個線程就得有Loop對象,除了主線程,它是默認創建了的。

4.主線程運行handler.post()這個方法裏面傳Runnable對象,裏面的run()方法是在哪個線程運行?
這是昨天我跟別人吹牛時他問的我一個問題,我當時覺得問的就有問題,於是補充了句handler是否是在主線程創建的,(其實準確說應該是handler創建時綁定的Looper對象是否是主線程的)若是那run()就在主線程運行。因爲Runnable都是當作一條消息放進隊列的嘛,並由與之相關的handler來處理,那消息隊列是在哪個線程,Looper就在哪個線程,而handler又與他們綁定了,那run方法就運行在哪個線程羅。所以就是主線程。但沒有底氣回答,總覺得哪裏有點不對,那就再來看看源碼吧。
運行handler.post(r);

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//這裏就是以前說的把它作爲一條消息發出去嗎,重點我們再看看getPostMessage(r)
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;//原來message的callBack屬性在這種情況賦值的。
        return m;
    }

再重點看看handler消息分發方法的這幾行代碼

if (msg.callback != null) {//看到沒,前面我post(r)時把這個callBack賦值了,所以不爲空,那就運行下一句了
            handleCallback(msg); //此方法實際就是msg.callback.run();終於看到run()方法運行了,那這個方法不就是運行於與handler綁定的那個線程,因爲這都是在Loop.loop()裏執行的操作。

所以之前我的大致想法是對的,run()方法是由與之綁定的handler處理,但當時不知道他最終是會去調用messaged的callBack的,現在算是對這條屬性也有進一步理解了。

最後,你若有關於handler的其它疑問,歡迎在評論裏諮詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章