轉載一篇我以前發在csdn上的博客。java
在咱們去討論Handler,Looper,MessageQueue的關係以前,咱們須要先問兩個問題:android
這一套東西搞出來是爲了解決什麼問題呢?程序員
若是讓咱們來解決這個問題該怎麼作?網絡
以上者兩個問題,是我最近總結出來的,在咱們學習瞭解一個新的技術以前,最好是先能回答這兩個問題,這樣你才能對你正在學習的東西有更深入的認識。異步
第一個問題:google的程序員們搞出這一套東西是爲了解決什麼問題的?這個問題很顯而易見,爲了解決線程間通訊的問題。咱們都知道,Android的UI/View這一套系統是運行在主線程的,而且這個主線程是死循環的,來看看具體的證據吧。async
public final class ActivityThread { public static void main(String[] args) { //... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
如上面的代碼示例所示,ActivityThread.main()
方法做爲Android程序的入口,裏面我省略了一些初始化的操做,而後就執行了一句Looper.loop()
方法,就沒了,再下一行就拋異常了。ide
loop()
方法裏面實際上就是一個死循環,一直在執行着,不斷的從一個MQ(MessageQueue,後面我都縮寫成MQ了)去取消息,若是有的話,那麼就執行它或者讓它的發送者去處理它。函數
通常來講,主線程循環中都是執行着一些快速的UI操做,當你有手touch屏幕的時候,系統會產生事件,UI會處理這些事件,這些事件都會在主線程中執行,並快速的響應着UI的變化。若是主線程上發生一些比較耗時的操做,那麼它後面的方法就沒法獲得執行了,那麼就會出現卡頓,不流暢。oop
所以,Android並不但願你在主線程去作一些耗時的操做,這裏對「耗時」二字進行樸素的理解就好了,就是執行起來須要消耗的時間比較多的操做。好比讀寫文件,小的文件也許很快,但你沒法預料文件的大小,再好比訪問網絡,再好比你須要作一些複雜的計算等等。學習
爲了避免阻礙主線程流暢的執行,咱們就必須在須要的時候把耗時的操做放到其餘線程上去,當其餘線程完成了工做,再給一個通知(或許還帶着數據)給到主線程,讓主線程去更新UI什麼的,固然了,若是你要的耗時操做只是默默無聞的完成就好了,並不須要通知UI,那麼你徹底不須要給通知給到UI線程。這就是線程間的通訊,其餘線程作耗時操做,完成了告訴UI線程,讓它進行更新。爲了解決這個問題,Android系統給咱們提供了這樣一套方案來解決。
第二個問題:若是讓咱們來想一套方案來解決這個線程間通訊的問題,該怎麼作呢?
先看看咱們如今已經有的東西,咱們有一個一直在循環的主線程,它實現起來大概是這個樣子:
public class OurSystem { public static void main(String [] args) { for (;;) { //do something... } } }
爲何主線程要一直死循環的執行呢?
關於這一點,我我的並無特別透徹的認知,但我猜想,對於有GUI的系統/程序,應該都有一個不斷循環的主線程,由於這個GUI程序確定是要跟人進行交互的,也就是說,須要等待用戶的輸入,好比觸碰屏幕,動動鼠標,敲敲鍵盤什麼的,這些事件確定是硬件層先得到一個響應/信號,而後會不斷的向上封裝傳遞。
若是說咱們一碰屏幕,一碰鼠標,就開啓一個新線程去處理UI上的變化,首先,這固然是能夠的!UI在什麼線程上更新其實都是能夠的嘛,並非說必定要在主線程上更新,這是系統給我設的一個套子。而後,問題也會複雜的多,若是咱們快速的點擊2下鼠標,那麼一瞬間就開啓了兩個新線程去執行,那麼這兩個線程的執行順序呢?兩個獨立的線程,咱們是沒法保證說先啓動的先執行。
因此第一個問題就是執行順序的問題。
第二個問題就是同步,幾個相互獨立的線程若是要處理同一個資源,那麼形成的結果都是使人困惑,不受控制的。另外一方面強行給全部的操做加上同步鎖,在效率上也會有問題。
爲了解決順序執行的問題,很是容易就想到的一種方案是事件隊列,各類各樣的事件先進入到一個隊列中,而後有個東西會不斷的從隊列中獲取,這樣第一個事件必定在第二個事件以前被執行,這樣就保證了順序,若是咱們把這個「取事件」的步驟放在一個線程中去作,那麼也順便解決了資源同步的問題。
所以,對於GUI程序會有一個一直循環的(主)線程,可能就是這樣來的吧。
這是一個很是純淨的死循環,咱們想要作一些事情的話,就得讓它從一個隊列裏面獲取一些事情來作,就像打印機同樣。所以咱們再編寫一個消息隊列類,來存放消息。消息隊列看起來應該是這樣:
public class OurMessageQueue() { private LinkedList<Message> mQueue = new LinkedList<Message>(); // 放進去一條消息 public void enQueue() { //... } // 取出一條消息 public Message deQueue() { //... } // 判斷是否爲空隊列 public boolean isEmpty() { //... } }
接下來咱們的循環就須要改形成能從消息隊列裏獲取消息,並可以根據消息來作些事情了:
public class OurSystem { public static void main(String [] args) { // 初始化消息隊列 OurMessageQueue mq = ... for (;;) { if (!mq.isEmpty()) { Message msg = mq.deQueue(); //do something... } } } }
如今咱們假象一下,咱們須要點擊一下按鈕,而後去下載一個超級大的文件,下載完成後,咱們再讓主線程顯示文件的大小。
首先,按一下按鈕,這個事件應該會被觸發到主線程來(具體怎麼來的我還尚不清楚,但應該是先從硬件開始,而後插入到消息隊列中,主線程的循環就能獲取到了),而後主線程開啓一個新的異步線程來進行下載,下載完成後再通知主線程來更新,代碼看上去是這樣的:
// 腦補的硬件設備…… public class OurDevice { // 硬件設備可能有一個回調 public void onClick() { // 先拿到同一個消息隊列,並把咱們要作的事情插入隊列中 OurMessageQueue mq = ... Message msg = Message.newInstance("download a big file"); mq.enQueue(msg); } }
而後,咱們的主線程循環獲取到了消息:
public class OurSystem { public static void main(String [] args) { // 初始化消息隊列 OurMessageQueue mq = ... for (;;) { if (!mq.isEmpty()) { Message msg = mq.deQueue(); // 是一條通知咱們下載文件的消息 if (msg.isDownloadBigFile()) { // 開啓新線程去下載文件 new Thread(new Runnable() { void run() { // download a big file, may cast 1 min... // ... // ok, we finished download task. // 獲取到同一個消息隊列 OurMessageQueue mq = ... // 消息入隊 mq.enQueue(Message.newInstance("finished download")); } }).start(); } // 是一條通知咱們下載完成的消息 if (msg.isFilishedDownload()) { // update UI! } } } } }
注意,主線程循環獲取到消息的時候,顯示對消息進行的判斷分類,不一樣的消息應該有不一樣的處理。在咱們獲取到一個下載文件的消息時,開啓了一個新的線程去執行,耗時操做與主線程就被隔離到不一樣的執行流中,當完成後,新線程中用同一個消息隊列發送了一個通知下載完成的消息,主線程循環獲取到後,裏面就能夠更新UI。
這樣就是一個我隨意腦補的,簡單的跨線程通訊的方案。
有以下幾點是值得注意的:
主線程是死循環的從消息隊列中獲取消息。
咱們要將消息發送到主線程的消息隊列,咱們須要經過某種方法能獲取到主線程的消息隊列對象
消息(Message)的結構應該如何設計呢?
Android中有一個Looper
對象,顧名思義,直譯過來就是循環的意思,Looper
也確實幹了維持循環的事情。
Looper的代碼是很是簡單的,去掉註釋也就300多行。在官方文檔的註釋中,它推薦咱們這樣來使用它:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }
先來看看prepare方法幹了什麼。
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)); }
注意prepare(boolean)
方法中,有一個sThreadLocal
變量,這個變量有點像一個哈希表,它的key是當前的線程,也就是說,它能夠存儲一些數據/引用,這些數據/引用是與當前線程是一一對應的,在這裏的做用是,它判斷一下當前線程是否有Looper
這個對象,若是有,那麼就報錯了,"Only one Looper may be created per thread",一個線程只容許建立一個Looper
,若是沒有,就new一個新的塞進這個哈希表中。而後它調用了Looper
的構造方法。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在Looper
的構造方法中,很關鍵的一句,它new了一個MessageQueue
對象,並本身維持了這個MQ的引用。
此時prepare()
方法的工做就結束了,接下來須要調用靜態方法loop()
來啓動循環。
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; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); //... } }
loop()方法,我作了省略,省去了一些不關心的部分。剩下的部分很是的清楚了,首先調用了靜態方法myLooper()獲取一個Looper對象。
public static Looper myLooper() { return sThreadLocal.get(); }
myLooper()
一樣是靜態方法,它是直接從這個ThreadLocal
中去獲取,這個剛剛說過了,它就相似於一個哈希表,key是當前線程,由於剛剛prepare()
的時候,已經往裏面set了一個Looper
,那麼此時應該是能夠get到的。拿到當前線程的Looper
後,接下來,final MessageQueue queue = me.mQueue;
拿到與這個Looper
對應的MQ,拿到了MQ後,就開啓了死循環,對消息隊列進行不停的獲取,當獲取到一個消息後,它調用了Message.target.dispatchMessage()
方法來對消息進行處理。
Looper的代碼看完了,咱們獲得了幾個信息:
Looper
調用靜態方法prepare()
來進行初始化,一個線程只能建立一個與之對應的Looper
,Looper
初始化的時候會建立一個MQ,所以,有了這樣的對應關係,一個線程對應一個Looper
,一個Looper
對應一個MQ。能夠說,它們三個是在一條線上的。
Looper
調用靜態方法loop()
開始無限循環的取消息,MQ調用next()
方法來獲取消息
對於MQ的源碼,簡單的看一下,構造函數與next()
方法就行了。
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
MQ的構造方法簡單的調用了nativeInit()來進行初始化,這是一個jni方法,也就是說,多是在JNI層維持了它這個消息隊列的對象。
MessageQueue.next()
Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } 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 (false) Log.v("MessageQueue", "Returning message: " + msg); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } } } }
next()
方法的代碼有些長,我做了一些省略,請注意到,這個方法也有一個死循環,這樣作的效果就是,在Looper
的死循環中,調用了next()
,而next()
這裏也在死循環,表面上看起來,方法就阻塞在Looper
的死循環中的那一行了,知道next()
方法能返回一個Message
對象出來。
簡單瀏覽MQ的代碼,咱們獲得了這些信息:
MQ的初始化是交給JNI去作的
MQ的next()
方法是個死循環,在不停的訪問MQ,從中獲取消息出來返回給Looper
去處理。
Message
對象是MQ中隊列的element,也是Handler
發送,接收處理的一個對象。對於它,咱們須要瞭解它的幾個成員屬性便可。
Message
的成員變量能夠分爲三個部分:
數據部分:它包括what(int)
, arg1(int)
, arg2(int)
, obj(Object)
, data(Bundle)
等,通常用這些來傳遞數據。
發送者(target):它有一個成員變量叫target
,它的類型是Handler
的,這個成員變量很重要,它標記了這個Message
對象自己是誰發送的,最終也會交給誰去處理。
callback:它有一個成員變量叫callback
,它的類型是Runnable
,能夠理解爲一個能夠被執行的代碼片斷。
Handler
對象是在API層面供給開發者使用最多的一個類,咱們主要經過這個類來進行發送消息與處理消息。
一般咱們調用沒有參數的構造方法來進行初始化,使用起來大概是這樣的:
Handler mHandler = new Handler() { handleMessage(Message msg) { //... } }
沒有參數的構造方法最終調用了一個兩個參數的構造方法,它的部分源碼以下:
public Handler(Callback callback, boolean async) { //... 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; }
注意到,它對mLooper
成員變量進行了賦值,經過Looper.myLooper()
方法獲取到當前線程對應的Looper
對象。上面已經提到過,若是Looper
調用過prepare()
方法,那麼這個線程對應了一個Looper
實例,這個Looper
實例也對應了一個MQ,它們三者之間是一一對應的關係。
而後它經過mLooper
對象,獲取了一個MQ,存在本身的mQueue
成員變量中。
Handler
的初始化代碼說明了一點,Handler
所初始化的地方(所在的線程),就是從將這個線程對應的Looper
的引用賦值給Handler
,讓Handler
也持有
對於主線程來講,咱們在主線程的執行流中,new一個Handler
對象,Handler對象都是持有主線程的Looper
(也就是Main Looper
)對象的。
一樣的,若是咱們在一個新線程,不調用Looper.prepare()
方法去啓動一個Looper
,直接new一個Handler
對象,那麼它就會報錯。像這樣
new Thread(new Runnable() { @Override public void run() { //Looper.prepare(); //由於Looper沒有初始化,因此Looper.myLooper()不能獲取到一個Looper對象 Handler h = new Handler(); h.sendEmptyMessage(112); } }).start();
以上代碼運行後會報錯:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
小結:Handler
的初始化會獲取到當前線程的Looper
對象,並經過Looper
拿到對應的MQ對象,若是當前線程的執行流並無執行過Looper.prepare()
,則沒法建立Handler對象
Handler.sendMessage()
sendMessage
消息有各類各樣的形式或重載,最終會調用到這個方法:
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
方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
注意到它對Message
的target
屬性進行了賦值,這樣這條消息就知道本身是被誰發送的了。而後將消息加入到隊列中。
Handler.dispatchMessage()
Message
對象進入了MQ後,很快的會被MQ的next()
方法獲取到,這樣Looper
的死循環中就能獲得一個Message
對象,回顧一下,接下來,就調用了Message.target.dispatchMessage()
方法對這條消息進行了處理。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } public void handleMessage(Message msg) { //這個方法是空實現,讓客戶端程序員去覆寫實現本身的邏輯 }
dispatchMessage
方法有兩個分支,若是callback
(Runnable
)不是null
,則直接執行callback.run()
方法,若是callback
是null
,則將msg
做爲參數傳給handleMessage()
方法去處理,這樣就是咱們常見的處理方法了。
Message.target與Handler
特別須要注意Message
中的target
成員變量,它是指向本身的發送者,這一點意味着什麼呢?
意味着:一個有Looper
的線程能夠有不少個Handler
,這些Handler
都是不一樣的對象,可是它們均可以將Message
對象發送到同一個MQ中,Looper
不斷的從MQ中獲取這些消息,並將消息交給它們的發送者去處理。一個MQ是能夠對應多個Handler
的(多個Handler
均可以往同一個MQ中消息入隊)
下圖能夠簡要的歸納下它們之間的關係。
Looper
調用prepare()
進行初始化,建立了一個與當前線程對應的Looper
對象(經過ThreadLocal
實現),而且初始化了一個與當前Looper
對應的MessageQueue
對象。
Looper
調用靜態方法loop()
開始消息循環,經過MessageQueue.next()
方法獲取Message
對象。
當獲取到一個Message
對象時,讓Message
的發送者(target
)去處理它。
Message
對象包括數據,發送者(Handler
),可執行代碼段(Runnable
)三個部分組成。
Handler
能夠在一個已經Looper.prepare()
的線程中初始化,若是線程沒有初始化Looper
,建立Handler
對象會失敗
一個線程的執行流中能夠構造多個Handler
對象,它們都往同一個MQ中發消息,消息也只會分發給對應的Handler
處理。
Handler
將消息發送到MQ中,Message
的target
域會引用本身的發送者,Looper
從MQ中取出來後,再交給發送這個Message
的Handler
去處理。
Message
能夠直接添加一個Runnable
對象,當這條消息被處理的時候,直接執行Runnable.run()
方法。