1. 程式人生 > >終於明白了Handler的執行機制

終於明白了Handler的執行機制

前言

Handler是一個Android SDK 提供給開發者方便進行非同步訊息處理的類。

我們都知道在UI執行緒中不能進行耗時操作,例如資料讀寫、網路請求。Android 4.0開始,在主執行緒中進行網路請求甚至會丟擲Android.os.NetworkOnMainThreadException。這個時候,我們就會開始依賴Handler。我們在子執行緒進行耗時操作後,將請求結果通過Handler的sendMessge**() 方法傳送出去,在主執行緒中通過Handler的handleMessage 方法處理請求結果,進行UI的更新。

後來隨著AsyncTask、EventBus、Volley以及Retrofit 的出現,Handler的作用似乎被弱化,逐漸被大家遺忘。其實不然,AsyncTask其實是基於Handler進行了非常巧妙的封裝,Handler的使用依然是其核心。Volley同樣也是使用到了Handler。因此,我們有必要了解一下Handler的實現機制。

神奇的Handler

記得很久之前的一天,我在閱讀別人的程式碼時,看到了這樣一段:

 
  1. new Handler().postDelayed(new Runnable() {

  2. @Override

  3. public void run() {

  4. Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();

  5. }

  6. }, 1000);

第一印象就是,這不是在子執行緒中進行UI操作嗎?這程式碼有問題吧,於是乎立刻在自己電腦上寫了個demo試了一下,結果發現真的沒有問題。在一陣懵逼過後,我又寫出下面的程式碼,測試一下子執行緒中到底能不能進行UI操作。

 
  1. new Thread(new Runnable() {

  2. @Override

  3. public void run() {

  4.  
  5. try {

  6. Thread.sleep(2000);

  7. } catch (InterruptedException e) {

  8. e.printStackTrace();

  9. }

  10.  
  11. Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

  12.  
  13. }

  14. }).start();

結果很明顯,程式一啟動立刻就奔潰了。並丟擲異常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
於是乎我又在try block 之前添加了Looper.prepare()這行程式碼。再次執行程式雖然沒有奔潰,但也沒有任何反應,Toast也沒顯示。

那麼Handler到底是什麼呢?他怎麼就這麼神奇。

實現機制解析

首先,我們從整體上了解一下,在整個Handler機制中所有使用到的類,主要包括Message,MessageQueue,Looper以及Handler。

 

 

好了,為了方便後面的敘述,我們就首先了解一下這個類圖中使用到幾個類,及其關鍵方法。

Message

首先看一下Message這個類的定義(擷取部分)

 
  1. public final class Message implements Parcelable {

  2.  
  3. public int what;

  4. public int arg1;

  5. public int arg2;

  6. public Object obj;

  7. /*package*/ Handler target;

  8. /*package*/ Runnable callback;

  9.  
  10. /**

  11. * Return a new Message instance from the global pool. Allows us to

  12. * avoid allocating new objects in many cases.

  13. */

  14. public static Message obtain() {

  15. synchronized (sPoolSync) {

  16. if (sPool != null) {

  17. Message m = sPool;

  18. sPool = m.next;

  19. m.next = null;

  20. m.flags = 0; // clear in-use flag

  21. sPoolSize--;

  22. return m;

  23. }

  24. }

  25. return new Message();

  26. }

  27.  
  28. /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).

  29. */

  30. public Message() {

  31. }

  32. }

看到這個類的前四個屬性,大家應該很熟悉,就是我們使用Handler時經常用到的那幾個屬性。用來在傳遞我們特定的資訊。其次我們還可以總結出以下資訊:

  • Message 實現了Parcelable 介面,也就是說實現了序列化,這就說明Message可以在不同程序之間傳遞。
  • 包含一個名為target的Handler 物件
  • 包含一個名為callback的Runnable 物件
  • 使用obtain 方法可以從訊息池中獲取Message的例項,也是推薦大家使用的方法,而不是直接呼叫構造方法。

MessageQueue

MessageQueue顧名思義,就是上面所說的Message所組成的queue。

首先看一下構造方法:

 
  1. MessageQueue(boolean quitAllowed) {

  2. mQuitAllowed = quitAllowed;

  3. mPtr = nativeInit();

  4. }

接收一個引數,決定當前佇列是否允許被終止。同時呼叫 一個native方法,初始化了一個long型別的變數mPtr。

同時,在這個類當中,還定義了一個next 方法,用於返回一個Message 。

 
  1. Message next() {

  2. // Return here if the message loop has already quit and been disposed.

  3. // This can happen if the application tries to restart a looper after quit

  4. // which is not supported.

  5. final long ptr = mPtr;

  6. if (ptr == 0) {

  7. return null;

  8. }

  9. ……

  10. }

由於這個方法中有一些native呼叫,未能完全理解,只知道會返回一個Message物件。

這個next方法相當於是隊列出棧,有出棧必然有進棧,enqueueMessage 方法就是完成這個操作;這個我們後面再說。

Looper

上面說到了MessageQueue,那麼這個Queue又是由誰建立的呢?其實就是Looper。關於Looper有兩個關鍵方法:

prepare() 和 loop()

Looper-prepare()

 
  1. public static void prepare() {

  2. prepare(true);

  3. }

  4.  
  5. private static void prepare(boolean quitAllowed) {

  6. if (sThreadLocal.get() != null) {

  7. throw new RuntimeException("Only one Looper may be created per thread");

  8. }

  9. sThreadLocal.set(new Looper(quitAllowed));

  10. }

可以看到,對於每一個執行緒只能有一個Looper。也就是說執行prepare方法時,必然執行最後一行程式碼
sThreadLocal.set(new Looper(quitAllowed));

我們再看Looper(quitAllowed)方法:

 
  1. private Looper(boolean quitAllowed) {

  2. mQueue = new MessageQueue(quitAllowed);

  3. mThread = Thread.currentThread();

  4. }

這樣,MessageQueue 就被建立了。這裡也可以看到,預設情況下,一個MessageQueue的quiteAllow=true。

這裡使用到的sThreadLocal 是一個ThreadLocal物件。簡單來說,使用它可以用來解決多執行緒程式的併發問題。使用set方法,將此執行緒區域性變數的當前執行緒副本中的值設定為指定值;使用get方法,返回此執行緒區域性變數的當前執行緒副本中的值。

Looper-loop()

再看一下loop方法(擷取主要邏輯)

 
  1. public static void loop() {

  2. final Looper me = myLooper();

  3. if (me == null) {

  4. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

  5. }

  6. final MessageQueue queue = me.mQueue;

  7. for (;;) {

  8. Message msg = queue.next(); // might block

  9. if (msg == null) {

  10. // No message indicates that the message queue is quitting.

  11. return;

  12. }

  13. msg.target.dispatchMessage(msg);

  14. msg.recycleUnchecked();

  15. }

  16. }

首先看第一句程式碼執行的方法:

 
  1. /**

  2. * Return the Looper object associated with the current thread. Returns

  3. * null if the calling thread is not associated with a Looper.

  4. */

  5. public static @Nullable Looper myLooper() {

  6. return sThreadLocal.get();

  7. }

很明顯,這樣返回的Looper就是剛才prepare時set進去的那個,因為都是在同一執行緒。再明確一下,一個執行緒對應一個Looper。

這樣就確保我們可以在不同的執行緒中建立各自的Handler,進行各自的通訊而不會互相干擾

回到程式碼,後面邏輯就很簡單了,在一個死迴圈中,通過隊列出棧的形式,不斷從MessageQueue 中取出新的Message,然後執行msg.target.dispatchMessage(msg) 方法,還記的前面Message類的定義嗎,這個target屬性其實就是一個Handler 物件,因此在這裡就會不斷去執行Handler 的dispatchMessage 方法。如果取出的Message物件為null,就會跳出死迴圈,一次Handler的工作整個就結束了。

Handler

上面說了這麼多終於輪到Handler,那麼就看看在Handler中到底發生了什麼。回到我們一開始的程式碼。

 
  1. new Handler().postDelayed(new Runnable() {

  2. @Override

  3. public void run() {

  4. String currentName=Thread.currentThread().getName();

  5. Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();

  6. }

  7. }, 4000);

這裡我們用Toast彈出了當前執行緒的name,結果發現這個執行緒的名字居然是main,這也是必然結果

讓我們一步一步看看,神奇的Handler到底是怎樣工作的。就從這個程式碼開始解讀。首先看一下Handler的構造方法。

 
  1. public Handler() {

  2. this(null, false);

  3. }

  4.  
  5. ---------------

  6.  
  7. public Handler(Callback callback, boolean async) {

  8. if (FIND_POTENTIAL_LEAKS) {

  9. final Class<? extends Handler> klass = getClass();

  10. if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&

  11. (klass.getModifiers() & Modifier.STATIC) == 0) {

  12. Log.w(TAG, "The following Handler class should be static or leaks might occur: " +

  13. klass.getCanonicalName());

  14. }

  15. }

  16.  
  17. mLooper = Looper.myLooper();

  18. if (mLooper == null) {

  19. throw new RuntimeException(

  20. "Can't create handler inside thread that has not called Looper.prepare()");

  21. }

  22. mQueue = mLooper.mQueue;

  23. mCallback = callback;

  24. mAsynchronous = async;

  25. }

這裡做的事情很簡單,就是完成了一些初始化的工作,呼叫Looper.myLooper()賦值給當前mLooper,關聯MessageQueue;這裡由於程式碼中呼叫的是不帶任何引數的建構函式,因此會建立一個mCallback=null且非非同步執行的Handler 。

接下看postDelayed 方法。

 
  1. public final boolean postDelayed(Runnable r, long delayMillis)

  2. {

  3. return sendMessageDelayed(getPostMessage(r), delayMillis);

  4. }

  5.  
  6. private static Message getPostMessage(Runnable r) {

  7. Message m = Message.obtain();

  8. m.callback = r;

  9. return m;

  10. }

這裡通過getPostMessage(Runnable r) 方法,把我們在Activity裡寫的Runnable 這個執行緒賦給了Message 的callback這個屬性。

平時大家使用Handler也發現了,他為我們提供了很多方法

 

 

因此,上面的postDelayed經過了各種輾轉反側,最終來到了這裡:

 
  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

  2. MessageQueue queue = mQueue;

  3. if (queue == null) {

  4. RuntimeException e = new RuntimeException(

  5. this + " sendMessageAtTime() called with no mQueue");

  6. Log.w("Looper", e.getMessage(), e);

  7. return false;

  8. }

  9. return enqueueMessage(queue, msg, uptimeMillis);

  10. }

經過之前的構造方法,mQueue顯然不為null,繼續往下看

 
  1. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

  2. msg.target = this;

  3. if (mAsynchronous) {

  4. msg.setAsynchronous(true);

  5. }

  6. return queue.enqueueMessage(msg, uptimeMillis);

  7. }

注意,注意,注意 這裡進行了一次賦值:

msg.target = this;

前面提到,這個target就是一個Handler物件,因此這裡Message就和當前Handler關聯起來了。enqueueMessage,哈哈,這就是我們之前在MessageQueue中提到的進棧操作的方法,我們看一下:

 
  1. boolean enqueueMessage(Message msg, long when) {

  2. if (msg.target == null) {

  3. throw new IllegalArgumentException("Message must have a target.");

  4. }

  5. if (msg.isInUse()) {

  6. throw new IllegalStateException(msg + " This message is already in use.");

  7. }

  8.  
  9. synchronized (this) {

  10. if (mQuitting) {

  11. IllegalStateException e = new IllegalStateException(

  12. msg.target + " sending message to a Handler on a dead thread");

  13. Log.w(TAG, e.getMessage(), e);

  14. msg.recycle();

  15. return false;

  16. }

  17.  
  18. msg.markInUse();

  19. msg.when = when;

  20. Message p = mMessages;

  21. boolean needWake;

  22. if (p == null || when == 0 || when < p.when) {

  23. // New head, wake up the event queue if blocked.

  24. msg.next = p;

  25. mMessages = msg;

  26. needWake = mBlocked;

  27. } else {

  28. // Inserted within the middle of the queue. Usually we don't have to wake

  29. // up the event queue unless there is a barrier at the head of the queue

  30. // and the message is the earliest asynchronous message in the queue.

  31. needWake = mBlocked && p.target == null && msg.isAsynchronous();

  32. Message prev;

  33. for (;;) {

  34. prev = p;

  35. p = p.next;

  36. if (p == null || when < p.when) {

  37. break;

  38. }

  39. if (needWake && p.isAsynchronous()) {

  40. needWake = false;

  41. }

  42. }

  43. msg.next = p; // invariant: p == prev.next

  44. prev.next = msg;

  45. }

  46.  
  47. // We can assume mPtr != 0 because mQuitting is false.

  48. if (needWake) {

  49. nativeWake(mPtr);

  50. }

  51. }

  52. return true;

  53. }

這個方法就是典型的佇列入隊操作,只不過會根據Message這個物件特有的一些屬性,以及當前的狀態是否inUse,是否已經被quit等進行一些額外的判斷。

這樣,我們就完成訊息入隊的操作。還記得我們在Looper中說過,在loop方法中,會從MessageQueue中取出Message 並執行他的dispatchMessage 方法。

dispatchMessage

 
  1. public void dispatchMessage(Message msg) {

  2. if (msg.callback != null) {

  3. handleCallback(msg);

  4. } else {

  5. if (mCallback != null) {

  6. if (mCallback.handleMessage(msg)) {

  7. return;

  8. }

  9. }

  10. handleMessage(msg);

  11. }

  12. }

到這裡,就很明確了,在之前的postDelayed 方法中,已經通過getPostMessage,實現了 m.callback = r;這樣這裡就會執行第一個if語句:

 
  1. private static void handleCallback(Message message) {

  2. message.callback.run();

  3. }

這樣,就會執行我們在Activity 的Runnable 中的run 方法了,也就是顯示Toast。

到了這裡,我們終於明白了,使用Handler 的postDelay 方法時,其Runnable中的run方法並不是在子執行緒中執行,而是把這個Runnable賦值給了一個Message物件的callback屬性,而這個Message會被傳遞到建立Handler所在的執行緒,也就是這裡的主執行緒,所以這個Toast的顯示依舊是在主執行緒中。這也和postDelay API 中所宣告的內容是一致的。

/**

 
  1. * Causes the Runnable r to be added to the message queue, to be run

  2. * after the specified amount of time elapses.

  3. * The runnable will be run on the thread to which this handler

  4. * is attached.

  5. */

到這裡,一開始所說的第一個程式碼塊所執行的邏輯已經理清楚了,但是還是有一點疑問,我們並沒有在Handler的構造方法中看到Looper 的prepare()方法和loop() 方法被執行,那麼他們到底是在哪裡執行的呢?這個問題我也是疑惑了很久,最終才明白是在
ActivityThread的main方法中執行。簡單來說,ActivityThread是Java層面一個Android程式真正的入口。關於ActivityThread更多的內容可以看看這篇文章

ActivityThread-main方法(擷取主要部分)

 
  1. public static void main(String[] args) {

  2.  
  3.  
  4. Looper.prepareMainLooper();

  5.  
  6. ActivityThread thread = new ActivityThread();

  7. thread.attach(false);

  8.  
  9. if (sMainThreadHandler == null) {

  10. sMainThreadHandler = thread.getHandler();

  11. }

  12.  
  13. if (false) {

  14. Looper.myLooper().setMessageLogging(new

  15. LogPrinter(Log.DEBUG, "ActivityThread"));

  16. }

  17.  
  18. // End of event ActivityThreadMain.

  19. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

  20. Looper.loop();

  21.  
  22. throw new RuntimeException("Main thread loop unexpectedly exited");

  23. }

這個類藏的比較深,你可以在Android-SDK\sources\android-24\android\app 這個目錄中找到。

也就是說,在一個Android 程式啟動之初,系統會幫我們為這個主執行緒建立好Looper。只不過這個方法名字比較特殊,叫做prepareMainLooper。

 
  1. public static void prepareMainLooper() {

  2. prepare(false);

  3. synchronized (Looper.class) {

  4. if (sMainLooper != null) {

  5. throw new IllegalStateException("The main Looper has already been prepared.");

  6. }

  7. sMainLooper = myLooper();

  8. }

  9. }

注意這裡呼叫prepare時傳遞的引數值為false,和我們之前建立普通Looper時是不同的,這也 可以理解,因為這是主執行緒,怎麼可以被允許被外部程式碼終止呢。

到這裡,我們終於完整的理解了開頭我們提到的第一個程式碼塊的內容了。

至於第二種使用寫法出錯的原因也在明顯不過了,主執行緒會在程式啟動時在main方法中幫我們主動建立Looper,呼叫loop方法;而我們自己建立的執行緒就得我們主動去呼叫Looper.prepare(),這樣才能保證MessageQueue被建立,程式不會奔潰;但是我們所期望的Toast依然沒有顯示出來,這是為什麼呢?因為,我們沒有呼叫loop方法。訊息被加入隊列了,但是沒有辦法彈出。因此我們將程式碼修改如下:

 
  1. new Thread(new Runnable() {

  2. @Override

  3. public void run() {

  4.  
  5. Looper.prepare();

  6.  
  7. try {

  8. Thread.sleep(2000);

  9. } catch (InterruptedException e) {

  10. e.printStackTrace();

  11. }

  12.  
  13. Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

  14.  
  15. Looper.loop();

  16.  
  17. }

  18. }).start();

這樣就沒問題了,Toast就可以顯示出來了。實際上,平時寫程式碼肯定不會這麼寫,這裡只是為了說明問題。

handleMessage

回想一下,我們使用Handler最常見的場景:

 
  1. handler = new MyHandler();

  2.  
  3. private class MyCallback implements Callback {

  4.  
  5. @Override

  6. public void onFailure(Call call, IOException e) {

  7. Message msg = new Message();

  8. msg.what = 100;

  9. msg.obj = e;

  10. handler.sendMessage(msg);

  11. }

  12.  
  13. @Override

  14. public void onResponse(Call call, Response response) throws IOException {

  15. Message msg = new Message();

  16. msg.what = 200;

  17. msg.obj = response.body().string();

  18. handler.sendMessage(msg);

  19. }

  20. }

上面的程式碼是OKHttp的回撥方法,由於其回撥方法不處於UI 執行緒,因此需要我們通過Handler將結果傳送到主線中取執行。
那麼這又是怎樣實現的呢?

前面我們截圖說過,Handler為我們提供許多sendMessage 相關的方法,因此這裡我們在onResponse 中執行的sendMessage 經過層層傳遞,殊途同歸依然會回到MessageQueue的enqueueMessage方法,也就是說所有的sendMessageXXX方法完成的工作無非就是佇列入棧的工作,就是將包含特定資訊的Message加入到MessageQueue中。而我們也知道,通過loop方法,會從MessageQueue中取出Message,執行每一個Message 所對應Handler的dispatchMessage方法,我們再看一次這個方法:

 
  1.  
  2.  
  3. public void dispatchMessage(Message msg) {

  4. if (msg.callback != null) {

  5. handleCallback(msg);

  6. } else {

  7. if (mCallback != null) {

  8. if (mCallback.handleMessage(msg)) {

  9. return;

  10. }

  11. }

  12. handleMessage(msg);

  13. }

  14. }

這一次,我們建立的Message很簡單,其callback屬性必然是空的,而且在例項化Handler時,呼叫的是其無參建構函式 ,因此這個時候,就會執行最後一行程式碼handleMessage(msg) ;

 
  1. /**

  2. * Subclasses must implement this to receive messages.

  3. */

  4. public void handleMessage(Message msg) {

  5. }

空的 ! 沒錯,這個方法就是空的,因為這是需要我們在Handler的繼承類中自己實現的方法呀。比如下面這樣;

 
  1. class MyHandler extends Handler {

  2. @Override

  3. public void handleMessage(Message msg) {

  4. super.handleMessage(msg);

  5. loading.setVisibility(View.GONE);

  6. switch (msg.what) {

  7. case 100:

  8. Object e = msg.obj;

  9. Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();

  10. break;

  11. case 200:

  12. String response = (String) msg.obj;

  13. tv.setText(response);

  14. break;

  15. default:

  16. break;

  17. }

  18. }

  19. }

我們在handleMessage方法中,實現了自己的處理邏輯。

總結

好了,這就是Handler的實現機制,這裡再做一次總結稱述。

  • 通過Looper的prepare方法建立MessageQueue
  • 通過loop方法找到和當前執行緒匹配的Looper物件me
  • 從me中取出訊息佇列物件mQueue
  • 在一個死迴圈中,從mQueue中取出Message物件
  • 呼叫每個Message物件的msg.target.dispatchMesssge方法
  • 也就是Handler的dispatchMessage 方法
  • 在dispatchMessage 根據Message物件的特點執行特定的方法。

至此,終於弄明白了Handler的執行機制。