Android中Thread Handler Looper MessageQueue的原理分析
在Android開發當中,Thread、Handler、Looper這幾個類是特別常見,在剛開始學習Android的時候對這些類可能並不是很清晰。下面我們就一起從原始碼的角度剖析一下這幾個類的工作原理。
Thread
首先是Thread, 我們都知道一個Thread就是一個執行緒物件,只要在run方法中填寫自己的程式碼然後啟動該執行緒就可以實現多執行緒操作。例如 :
new Thread(){ public void run() { // 耗時的操作 }; }.start();
我們知道,針對上面的程式碼中,當執行完run中的操作時,整個執行緒就會結束,並不會一直執行下去。而我們的應用程式會一直執行,除非你退出或者應用程式丟擲異常。這又引入了另外一個概念,即訊息佇列。在Android應用啟動時,會預設有一個主執行緒(UI執行緒),在這個執行緒中會關聯一個訊息佇列,所有的操作都會被封裝成訊息然後交給主執行緒來處理。為了保證主執行緒不會主動退出,會將取訊息的操作放在一個死迴圈中,這樣程式就相當於一直在執行死迴圈,因此不會退出。示例圖如下 :
Android應用程式的入口為ActivityThread.main方法,詳情請參考Android應用程式程序啟動過程的原始碼分析,UI執行緒的訊息迴圈就是在這個方法中建立的,原始碼如下:
public static void main(String[] args) { SamplingProfilerIntegration.start(); CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper();// 1、建立訊息迴圈Looper ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); // UI執行緒的Handler } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); // 2、執行訊息迴圈 throw new RuntimeException("Main thread loop unexpectedly exited"); }
執行ActivityThread.main方法後,應用程式就啟動了,並且會一直從訊息佇列中取訊息,然後處理訊息。那麼系統是如何將訊息投遞到訊息佇列中的?又是如何從訊息佇列中獲取訊息並且處理訊息的呢? 答案就是Handler。Handler
在我們在子執行緒中執行完耗時操作後很多情況下我們需要更新UI,但我們都知道,不能在子執行緒中更新UI。此時最常用的手段就是通過Handler將一個訊息post到UI執行緒中,然後再在Handler的handleMessage方法中進行處理。但是有一個點要注意,那就是該Handler必須在主執行緒中建立!!簡單示例如下: class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // 更新UI } } MyHandler mHandler = new MyHandler() ; // 開啟新的執行緒 new Thread(){ public void run() { // 耗時操作 mHandler.sendEmptyMessage(123) ; }; }.start();
為什麼必須要這麼做呢?其實每個Handler都會關聯一個訊息佇列,訊息佇列被封裝在Lopper中,而每個Looper又會關聯一個執行緒(ThreadLocal),也就是每個訊息佇列會關聯一個執行緒。Handler就是一個訊息處理器,將訊息投遞給訊息佇列,然後再由對應的執行緒從訊息佇列中挨個取出訊息,並且執行。預設情況下,訊息佇列只有一個,即主執行緒的訊息佇列,這個訊息佇列是在ActivityThread.main方法中建立的,通過Lopper.prepareMainLooper()來建立,然後最後執行Looper.loop()來啟動訊息迴圈。那麼Handler是如何關聯訊息佇列以及執行緒的呢?我們看看如下原始碼 :
public Handler() { 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(); // 獲取Looper if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; // 獲取訊息佇列 mCallback = null; }
從Handler預設的建構函式中我們可以看到,Handler會在內部通過Looper.getLooper()來獲取Looper物件,並且與之關聯,最重要的就是訊息佇列。那麼Looper.getLooper()又是如何工作的呢?我們繼續往下看. /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); } /** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() { prepare(); setMainLooper(myLooper()); myLooper().mQueue.mQuitAllowed = false; } private synchronized static void setMainLooper(Looper looper) { mMainLooper = looper; } /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); }
我們看到myLooper()方法是通過sThreadLocal.get()來獲取的,關於ThreadLocal的資料請參考ThreadLocal多執行緒例項詳解。那麼Looper物件又是什麼時候儲存在sThreadLocal中的呢? 眼尖的朋友可能看到了,上面貼出的程式碼中給出了一個熟悉的方法,prepareMainLooper(),在這個方法中呼叫了prepare()方法,在這個方法中建立了一個Looper物件,並且將該物件設定給了sThreadLocal。這樣,佇列就與執行緒關聯上了!!!不同的執行緒是不能訪問對方的訊息佇列的。再回到Handler中來,訊息佇列通過Looper與執行緒關聯上,而Handler又與Looper關聯,因此Handler最終就和執行緒、執行緒的訊息佇列關聯上了。這就能解釋上面提到的問題了,“為什麼要更新UI的Handler必須要在主執行緒中建立?”。就是因為Handler要與主執行緒的訊息佇列關聯上,這樣handleMessage才會執行在UI執行緒,此時更新UI才是執行緒安全的!!!Looper與MessageQueue
建立了Looper後,如何執行訊息迴圈呢?通過Handler來post訊息給訊息佇列( 連結串列 ),那麼訊息是如何被處理的呢?答案就是在訊息迴圈中,訊息迴圈的建立就是通過Looper.loop()方法。原始碼如下 :
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } MessageQueue queue = me.mQueue; // 1、獲取訊息佇列 // 程式碼省略 while (true) { // 2、死迴圈,即訊息迴圈 Message msg = queue.next(); // 3、獲取訊息 (might block ) if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } long wallStart = 0; long threadStart = 0; // 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); wallStart = SystemClock.currentTimeMicro(); threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); // 4、處理訊息 // 程式碼省略 msg.recycle(); } } }
可以看到,loop方法中實質上就是建立一個死迴圈,然後通過從訊息佇列中挨個取出訊息,最後處理訊息的過程。對於Looper我們總結一下 : 通過Looper.prepare()來建立Looper物件(訊息佇列封裝在Looper物件中),並且儲存在sThreadLoal中,然後通過Looper.loop()來執行訊息迴圈,這兩步通常是成對出現的!! 最後我們看看訊息處理機制,我們看到程式碼中第4步通過msg.target.dispatchMessage(msg)來處理訊息。其中msg是Message型別,我們看原始碼 :
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; int flags; long when; Bundle data; Handler target; // target處理 Runnable callback; // Runnable型別的callback // sometimes we store linked lists of these things Message next; // 下一條訊息,訊息佇列是鏈式儲存的 // 程式碼省略 .... }
從原始碼中可以看到,target是Handler型別。實際上就是轉了一圈,通過Handler將訊息投遞給訊息佇列,訊息佇列又將訊息分發給Handler來處理。我們繼續看 /** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { } private final void handleCallback(Message message) { message.callback.run(); } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
可以看到,dispatchMessage只是一個分發的方法,如果Runnable型別的callback為空則執行handlerMessage來處理訊息,該方法為空,我們會將更新UI的程式碼寫在該函式中;如果callback不為空,則執行handleCallback來處理,該方法會呼叫callback的run方法。其實這是Handler分發的兩種型別,比如我們post(Runnable callback)則callback就不為空,當我們使用Handler來sendMessage時通常不會設定callback,因此也就執行handlerMessage這個分支。我們看看兩種實現 : public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private final 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) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; // 設定訊息的target為當前Handler物件 sent = queue.enqueueMessage(msg, uptimeMillis); // 將訊息插入到訊息佇列 } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }
可以看到,在post(Runnable r)時,會將Runnable包裝成Message物件,並且將Runnable物件設定給Message物件的callback欄位,最後會將該Message物件插入訊息佇列。sendMessage也是類似實現 : public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
不管是post一個Runnbale還是Message,都會呼叫sendMessageDelayed(msg, time)方法。子執行緒中建立Handler為何會丟擲異常 ?
我們看如下程式碼 : new Thread(){ Handler handler = null; public void run() { handler = new Handler(); }; }.start();
上面的程式碼有問題嗎 ?如果你能夠發現並且解釋上述程式碼的問題,那麼應該說您對Handler、Looper、Thread這幾個概念已經很瞭解了。如果您還不太清楚,那麼我們一起往下學習。前面說過,Looper物件是ThreadLocal的,即每個執行緒都有自己的Looper,這個Looper可以為空。但是當你要在子執行緒中建立Handler物件時,如果Looper為空,那麼就會丟擲“Can't create handler inside thread that has not called Looper.prepare()”異常,為什麼會這樣呢?我們一起看原始碼吧。 /** * Default constructor associates this handler with the queue for the * current thread. * * If there isn't one, this handler won't be able to receive messages. */ public Handler() { // 程式碼省略 mLooper = Looper.myLooper(); // 獲取myLooper if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");// 丟擲異常 } mQueue = mLooper.mQueue; mCallback = null; }
我們可以看到,當mLooper物件為空時,丟擲了該異常。這是因為該執行緒中的Looper物件還沒有建立,因此sThreadLocal.get()會返回null。解決方法如下 : new Thread(){ Handler handler = null; public void run() { Looper.prepare(); // 1、建立Looper,並且會繫結到ThreadLocal中 handler = new Handler(); Looper.loop(); // 2、啟動訊息迴圈 }; }.start();
在程式碼中我們加了2處,第一是通過Looper.prepare()來建立Looper,第二是通過Looper.loop()來啟動訊息迴圈。這樣該執行緒就有了自己的Looper,也就是有了自己的訊息佇列。如果之建立Looper,而不啟動訊息迴圈,雖然不會丟擲異常,但是你通過handler來post或者sendMessage也不會有效,因為雖然訊息被追加到訊息隊列了,但是並沒有啟動訊息迴圈,也就不會從訊息佇列中獲取訊息並且執行了!