1. 程式人生 > >今天做到一道面試題:Handler+Looper+MessageQueue+Message的關係

今天做到一道面試題:Handler+Looper+MessageQueue+Message的關係

Handler+Looper+MessageQueue+Message的關係

Handler+Looper+MessageQueue這三者的關係其實就是Android的訊息機制。這塊內容相比開發人員都不陌生,在面試中,或者日常開發中都會碰到,今天就來講這三者的關係。

概述:

Handler 、 Looper 、Message 這三者都與Android非同步訊息處理執行緒相關的概念。那麼什麼叫非同步訊息處理執行緒呢?

非同步訊息處理執行緒啟動後會進入一個無限的迴圈體之中,每迴圈一次,從其內部的訊息佇列中取出一個訊息,然後回撥相應的訊息處理函式,執行完成一個訊息後則繼續迴圈。若訊息佇列為空,執行緒則會阻塞等待。 那麼Android訊息機制主要是指Handler的執行機制,Handler執行需要底層的MessageQueue和Looper支撐。其中MessageQueue採用的是單鏈表的結構,Looper可以叫做訊息迴圈。由於MessageQueue只是一個訊息儲存單元,不能去處理訊息,而Looper就是專門來處理訊息的,Looper會以無限迴圈的形式去查詢是否有新訊息,如果有的話,就處理,否則就一直等待著。 我們知道,Handler建立的時候會採用當前執行緒的Looper來構造訊息迴圈系統,需要注意的是,執行緒預設是沒有Looper的,如果需要使用Handler就必須為執行緒建立Looper,因為預設的UI主執行緒,也就是ActivityThread,ActivityThread被建立的時候就會初始化Looper,這也是在主執行緒中預設可以使用Handler的原因。

關係圖:

先給出這三者之間的關係圖

原因:

前面提到的Handler+Looper+MessageQueue,這三者實際上是一個整體,我們在開發過程中接觸的最多是Handler。Handler的主要作用是將一個任務切換到某個指定的執行緒中去執行,那麼Androd為什麼要提供這個功能呢? 這是因為Android規定UI只能在主執行緒中進行,如果在子執行緒中訪問UI,那麼程式就會崩潰,丟擲異常,這就是導致我們不能在主執行緒中進行耗時操作,否則會導致程式無法響應,即ANR,那要是我們想要從服務端獲取資料在UI上顯示怎麼辦呢,耗時的話我們一般在子執行緒中進行獲取,如何把獲取的資料呈現在主執行緒中呢,這其中就用到了Handler,Handler主要原因就是為了解決在子執行緒中無法訪問UI的矛盾。 可以在延伸下,系統為什麼不允許在子執行緒中訪問UI呢,這是因為Android的UI控制元件不是執行緒安全的,如果是多執行緒中併發訪問可能會導致UI控制元件處於不可控的狀態。

Looper類

Looper扮演的角色就是訊息迴圈,不斷從MessageQueue中檢視是否有新訊息,如果有新訊息到來就會立刻處理,否則就一直祖塞在那裡,在它的構造方法,預設會建立一個MessageQueue的訊息佇列,然後將當前執行緒的物件儲存起來。

?
1 2 3 4 5 private Looper(boolean quitAllowed) { mQueue =new MessageQueue(quitAllowed); mRun =true; mThread = Thread.currentThread(); }

對於Looper主要是prepare()和loop()兩個方法,首先看prepare()方法。

public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true));
}

sThreadLocal是一個ThreadLocal物件,可以在一個執行緒中儲存變數。可以看到,在第5行,將一個Looper的例項放入了ThreadLocal,並且2-4行判斷了sThreadLocal是否為null,否則丟擲異常。這也就說明了Looper.prepare()方法不能被呼叫兩次,同時也保證了一個執行緒中只有一個Looper例項

然後我們看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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

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

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycle();
        }
}
複製程式碼

方法直接返回了sThreadLocal儲存的Looper例項,如果me為null則丟擲異常,也就是說looper方法必須在prepare方法之後執行。
拿到該looper例項中的mQueue(訊息佇列)
就進入了我們所說的無限迴圈。
取出一條訊息,如果沒有訊息則阻塞。
使用呼叫 msg.target.dispatchMessage(msg);把訊息交給msg的target的dispatchMessage方法去處理。Msg的target是什麼呢?其實就是handler物件,下面會進行分析。
釋放訊息佔據的資源。

Looper主要作用:

1、 與當前執行緒繫結,保證一個執行緒只會有一個Looper例項,同時一個Looper例項也只有一個MessageQueue。

2、 loop()方法,不斷從MessageQueue中去取訊息,交給訊息的target屬性的dispatchMessage去處理。

好了,我們的非同步訊息處理執行緒已經有了訊息佇列(MessageQueue),也有了在無限迴圈體中取出訊息的哥們,現在缺的就是傳送訊息的物件了,於是乎:Handler登場了。

Handler類

Handler的工作主要是包含訊息的傳送和接受的過程。使用Handler之前,我們都是初始化一個例項,比如用於更新UI執行緒,我們會在宣告的時候直接初始化,或者在onCreate中初始化Handler例項。 複製程式碼
public Handler() {
        this(null, false);
}
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;
    }
複製程式碼

通過Looper.myLooper()獲取了當前執行緒儲存的Looper例項,然後在19行又獲取了這個Looper例項中儲存的MessageQueue(訊息佇列),這樣就保證了handler的例項與我們Looper例項中MessageQueue關聯上了。

然後看我們最常用的sendMessage方法

複製程式碼
 public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 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然後呼叫了enqueueMessage方法,我們再來看看此方法:

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

enqueueMessage中首先為meg.target賦值為this,如果大家還記得Looper的loop方法會取出每個msg然後交給msg,target.dispatchMessage(msg)去處理訊息,也就是把當前的handler作為msg的target屬性。最終會呼叫queue的enqueueMessage的方法,也就是說handler發出的訊息,最終會儲存到訊息佇列中去。

現在已經很清楚了Looper會呼叫prepare()和loop()方法,在當前執行的執行緒中儲存一個Looper例項,這個例項會儲存一個MessageQueue物件,然後當前執行緒進入一個無限迴圈中去,不斷從MessageQueue中讀取Handler發來的訊息。然後再回調建立這個訊息的handler中的dispathMessage方法,下面我們趕快去看一看這個方法:

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

  /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
複製程式碼

可以看到這是一個空方法,為什麼呢,因為訊息的最終回撥是由我們控制的,我們在建立handler的時候都是複寫handleMessage方法,然後根據msg.what進行訊息處理。

總結下:

1、首先Looper.prepare()在本執行緒中儲存一個Looper例項,然後該例項中儲存一個MessageQueue物件;因為Looper.prepare()在一個執行緒中只能呼叫一次,所以MessageQueue在一個執行緒中只會存在一個。

2、Looper.loop()會讓當前執行緒進入一個無限迴圈,不端從MessageQueue的例項中讀取訊息,然後回撥msg.target.dispatchMessage(msg)方法。

3、Handler的構造方法,會首先得到當前執行緒中儲存的Looper例項,進而與Looper例項中的MessageQueue想關聯。

4、Handler的sendMessage方法,會給msg的target賦值為handler自身,然後加入MessageQueue中。

5、在構造Handler例項時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終呼叫的方法。

Handler Post

看程式碼

複製程式碼
 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
  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);
    }
複製程式碼

可以看到,在getPostMessage中,得到了一個Message物件,然後將我們建立的Runable物件作為callback屬性,賦值給了此message.

注:產生一個Message物件,可以new  ,也可以使用Message.obtain()方法;兩者都可以,但是更建議使用obtain方法,因為Message內部維護了一個Message池用於Message的複用,避免使用new 重新分配記憶體。最終和handler.sendMessage一樣,呼叫了sendMessageAtTime,然後呼叫了enqueueMessage方法,給msg.target賦值為handler,最終加入MessagQueue.

可以看到,這裡msg的callback和target都有值,那麼會執行哪個呢?

dispatchMessage方法

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

Handler使用流程圖:

====================================================================================================================================

Handler sendMessage 與obtainMessage--sendToTarget的區別

Message msg = new Message()
msg.what = xxx;
msg.arg1  = xxx;
msg.arg2  = xxx;
handler.sendMessage(msg);

這樣寫也沒有絕得不好,反正當時專案的功能實現了。(效能上還可以)

後來沒事的時候看了看handler 的其他的方法,就看到了obtainMessage()這個方法.很奇怪,不知道為何還要出來的方法

本來上面的那段code 就能實現handler 的功能了,為什麼還要出現他,後來百度google 一把,大家說 什麼效能上有差別之

類的。。。。。結果可想而知(大部分的文章都是你抄我的我抄你的,技術就是這樣出來的。。鄙視這種抄寫別人部落格文章而

不著名轉載出處的人)。於是我就去看原始碼能否看到一些好的描述。

複製程式碼
Message msg = handler.obtainMessage();
msg.what = xxx;
msg.arg1  = xxx;
msg.arg2  = xxx;
msg.obj    = xxx;

.................... 
複製程式碼

看看這兩段程式碼其實就是方法不一樣 ,引數都一樣。但是為何實現的效果一樣還要分離出來這麼多方法呢?

到原始碼去看個究竟吧!

先去看sendMessage()這個方法。。。。它呼叫的是Handler 中的sendMessage(Message msg)

原始碼 片段1  如下:

?
/** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in {@link #handleMessage}, * in the thread attached to this handler. * @return Returns true if the message was successfully placed in to the *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting. */ publicfinal booleansendMessage(Message msg) { return sendMessageDelayed(msg,0); }

 再看handler.obtainMessage()原始碼 片段2 如下:

?
/** * Returns a new {@link android.os.Message Message} from the global message pool. <br>   * More efficient than creating and allocating new instances. <br>    * The retrieved message has its handler set to this instance    <br>   * (Message.target == this). * If you don't want that facility, just call Message.obtain() instead. */ publicfinal Message obtainMessage() { returnMessage.obtain(this); }

上面這兩段都是Handler 裡面的方法,不過在片段1  我們可以看到Message是我們作為引數傳過去的,片段2的則是我們

Message幫我們處理,它呼叫obtain(Handler h)方法,之後我們還要呼叫Message中sendToTarget()這個方法。

看一下Message.obtain(Hanlder h) 的原始碼  程式碼片段3如下:

?
/** * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message return   * ed. * @param h  Handler to assign to the returned Message object's <em>target</em> member. * @return A Message object from the global pool. */ publicstatic Message obtain(Handler h) { Message m = obtain(); m.target = h; returnm; }

  再看 sendToTarget() 原始碼     程式碼片段4 如下:

?
/** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field has not been set. */ publicvoid sendToTarget() { <span style="background-color: rgb(255, 0, 0);"> target.sendMessage(this);</span> }

  這裡的target就是handler,sendToTarget()又在呼叫handler的 sendMessage 方法了。。。

     看到這裡也許有的人很疑惑,這樣轉來轉去,轉了一圈怎麼又回到Handler的 sendMessage方法了?那麼效能比較一說

     還有其他的證據麼?(難道呼叫的方法多效能就低麼?也許有這麼一點原因,不過這裡幾乎可以不考慮這一點效能損耗的)

     那麼效能的比較證據應該從哪裡找呢?

  其實細心的同學已經看到了,注意看原始碼的註釋,

/**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */

    這裡我們的Message 已經不是 自己建立的了,而是從MessagePool 拿的,省去了建立物件申請記憶體的開銷。。。。。

   到這裡大家應該都明白了。所以以後使用的時候儘量使用 Message msg = handler.obtainMessage();的形式創

   建Message,不要自己New Message 至於message產生之後你使用obtainMessage 或者是 sendMessage 效率影響

   並不大.同時我們也要注意以後談論效能的時候要找準位置,譬如這裡效能的問題不是在呼叫 obtainMessage 和 sen

  dMessage 的方法上,而是呼叫他們之前物件的建立問題上。