講講Handler+Looper+MessageQueue關係
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的訊息佇列,然後將當前執行緒的物件儲存起來。
12345 | private Looper( boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true ; mThread = Thread.currentThread(); } |
對於Looper主要是prepare()和loop()兩個方法,首先看prepare()方法。
public staticfinal 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); } }