Android異步消息處理機制 handler

我們都知道,Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程序就有可能會崩潰。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟於心,即創建一個Message對象,然後藉助Handler發送出去,之後在Handler的handleMessage()方法中獲得剛纔發送的Message對象,然後在這裏進行UI操作就不會再出現崩潰了。
這種處理方式被稱爲異步消息處理線程,雖然我相信大家都會用,可是你知道它背後的原理是什麼樣的嗎?今天我們就來一起深入探究一下Handler和Message背後的祕密。

首先來看一下如何創建Handler對象。你可能會覺得挺納悶的,創建Handler有什麼好看的呢,直接new一下不就行了?確實,不過即使只是簡單new一下,還是有不少地方需要注意的,我們嘗試在程序中創建兩個Handler對象,一個在主線程中創建,一個在子線程中創建,代碼如下所示:

  1. public class MainActivity extends Activity {  
  2.       
  3.     private Handler handler1;  
  4.       
  5.     private Handler handler2;  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         handler1 = new Handler();  
  12.         new Thread(new Runnable() {  
  13.             @Override  
  14.             public void run() {  
  15.                 handler2 = new Handler();  
  16.             }  
  17.         }).start();  
  18.     }  
  19.   
  20. }  
如果現在運行一下程序,你會發現,在子線程中創建的Handler是會導致程序崩潰的,提示的錯誤信息爲 Can't create handler inside thread that has not called Looper.prepare() 。說是不能在沒有調用Looper.prepare() 的線程中創建Handler,那我們嘗試在子線程中先調用一下Looper.prepare()呢,代碼如下所示:
  1. new Thread(new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Looper.prepare();  
  5.         handler2 = new Handler();  
  6.     }  
  7. }).start();  
果然這樣就不會崩潰了,不過只滿足於此顯然是不夠的,我們來看下Handler的源碼,搞清楚爲什麼不調用Looper.prepare()就不行呢。Handler的無參構造函數如下所示:
  1. public Handler() {  
  2.     if (FIND_POTENTIAL_LEAKS) {  
  3.         final Class<? extends Handler> klass = getClass();  
  4.         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  5.                 (klass.getModifiers() & Modifier.STATIC) == 0) {  
  6.             Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  7.                 klass.getCanonicalName());  
  8.         }  
  9.     }  
  10.     mLooper = Looper.myLooper();  
  11.     if (mLooper == null) {  
  12.         throw new RuntimeException(  
  13.             "Can't create handler inside thread that has not called Looper.prepare()");  
  14.     }  
  15.     mQueue = mLooper.mQueue;  
  16.     mCallback = null;  
  17. }  
可以看到,在第10行調用了Looper.myLooper()方法獲取了一個Looper對象,如果Looper對象爲空,則會拋出一個運行時異常,提示的錯誤正是 Can't create handler inside thread that has not called Looper.prepare()!那什麼時候Looper對象纔可能爲空呢?這就要看看Looper.myLooper()中的代碼了,如下所示:
  1. public static final Looper myLooper() {  
  2.     return (Looper)sThreadLocal.get();  
  3. }  
這個方法非常簡單,就是從sThreadLocal對象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果沒有Looper存在自然就返回空了。因此你可以想象得到是在哪裏給sThreadLocal設置Looper了吧,當然是Looper.prepare()方法!我們來看下它的源碼:
  1. public static final void prepare() {  
  2.     if (sThreadLocal.get() != null) {  
  3.         throw new RuntimeException("Only one Looper may be created per thread");  
  4.     }  
  5.     sThreadLocal.set(new Looper());  
  6. }  

可以看到,首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則創建一個新的Looper設置進去。這樣也就完全解釋了爲什麼我們要先調用Looper.prepare()方法,才能創建Handler對象。同時也可以看出每個線程中最多隻會有一個Looper對象。

咦?不對呀!主線程中的Handler也沒有調用Looper.prepare()方法,爲什麼就沒有崩潰呢?細心的朋友我相信都已經發現了這一點,這是由於在程序啓動的時候,系統已經幫我們自動調用了Looper.prepare()方法。查看ActivityThread中的main()方法,代碼如下所示:

  1. public static void main(String[] args) {  
  2.     SamplingProfilerIntegration.start();  
  3.     CloseGuard.setEnabled(false);  
  4.     Environment.initForCurrentUser();  
  5.     EventLogger.setReporter(new EventLoggingReporter());  
  6.     Process.setArgV0("<pre-initialized>");  
  7.     Looper.prepareMainLooper();  
  8.     ActivityThread thread = new ActivityThread();  
  9.     thread.attach(false);  
  10.     if (sMainThreadHandler == null) {  
  11.         sMainThreadHandler = thread.getHandler();  
  12.     }  
  13.     AsyncTask.init();  
  14.     if (false) {  
  15.         Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
  16.     }  
  17.     Looper.loop();  
  18.     throw new RuntimeException("Main thread loop unexpectedly exited");  
  19. }  
可以看到,在第7行調用了Looper.prepareMainLooper()方法,而這個方法又會再去調用Looper.prepare()方法,代碼如下所示:
  1. public static final void prepareMainLooper() {  
  2.     prepare();  
  3.     setMainLooper(myLooper());  
  4.     if (Process.supportsProcesses()) {  
  5.         myLooper().mQueue.mQuitAllowed = false;  
  6.     }  
  7. }  

因此我們應用程序的主線程中會始終存在一個Looper對象,從而不需要再手動去調用Looper.prepare()方法了。

這樣基本就將Handler的創建過程完全搞明白了,總結一下就是在主線程中可以直接創建Handler對象,而在子線程中需要先調用Looper.prepare()才能創建Handler對象。

看完了如何創建Handler之後,接下來我們看一下如何發送消息,這個流程相信大家也已經非常熟悉了,new出一個Message對象,然後可以使用setData()方法或arg參數等方式爲消息攜帶一些數據,再借助Handler將消息發送出去就可以了,示例代碼如下:

  1. new Thread(new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Message message = new Message();  
  5.         message.arg1 = 1;  
  6.         Bundle bundle = new Bundle();  
  7.         bundle.putString("data""data");  
  8.         message.setData(bundle);  
  9.         handler.sendMessage(message);  
  10.     }  
  11. }).start();  

可是這裏Handler到底是把Message發送到哪裏去了呢?爲什麼之後又可以在Handler的handleMessage()方法中重新得到這條Message呢?看來又需要通過閱讀源碼才能解除我們心中的疑惑了,Handler中提供了很多個發送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的發送消息方法最終都會輾轉調用到sendMessageAtTime()方法中,這個方法的源碼如下所示:

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  2. {  
  3.     boolean sent = false;  
  4.     MessageQueue queue = mQueue;  
  5.     if (queue != null) {  
  6.         msg.target = this;  
  7.         sent = queue.enqueueMessage(msg, uptimeMillis);  
  8.     }  
  9.     else {  
  10.         RuntimeException e = new RuntimeException(  
  11.             this + " sendMessageAtTime() called with no mQueue");  
  12.         Log.w("Looper", e.getMessage(), e);  
  13.     }  
  14.     return sent;  
  15. }  
sendMessageAtTime()方法接收兩個參數,其中msg參數就是我們發送的Message對象,而uptimeMillis參數則表示發送消息的時間,它的值等於自系統開機到當前時間的毫秒數再加上延遲時間,如果你調用的不是sendMessageDelayed()方法,延遲時間就爲0,然後將這兩個參數都傳遞到MessageQueue的enqueueMessage()方法中。這個MessageQueue又是什麼東西呢?其實從名字上就可以看出了,它是一個消息隊列,用於將所有收到的消息以隊列的形式進行排列,並提供入隊和出隊的方法。這個類是在Looper的構造函數中創建的,因此一個Looper也就對應了一個MessageQueue。

那麼enqueueMessage()方法毫無疑問就是入隊的方法了,我們來看下這個方法的源碼:

  1. final boolean enqueueMessage(Message msg, long when) {  
  2.     if (msg.when != 0) {  
  3.         throw new AndroidRuntimeException(msg + " This message is already in use.");  
  4.     }  
  5.     if (msg.target == null && !mQuitAllowed) {  
  6.         throw new RuntimeException("Main thread not allowed to quit");  
  7.     }  
  8.     synchronized (this) {  
  9.         if (mQuiting) {  
  10.             RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
  11.             Log.w("MessageQueue", e.getMessage(), e);  
  12.             return false;  
  13.         } else if (msg.target == null) {  
  14.             mQuiting = true;  
  15.         }  
  16.         msg.when = when;  
  17.         Message p = mMessages;  
  18.         if (p == null || when == 0 || when < p.when) {  
  19.             msg.next = p;  
  20.             mMessages = msg;  
  21.             this.notify();  
  22.         } else {  
  23.             Message prev = null;  
  24.             while (p != null && p.when <= when) {  
  25.                 prev = p;  
  26.                 p = p.next;  
  27.             }  
  28.             msg.next = prev.next;  
  29.             prev.next = msg;  
  30.             this.notify();  
  31.         }  
  32.     }  
  33.     return true;  
  34. }  
首先你要知道,MessageQueue並沒有使用一個集合把所有的消息都保存起來,它只使用了一個mMessages對象表示當前待處理的消息。然後觀察上面的代碼的16~31行我們就可以看出,所謂的入隊其實就是將所有的消息按時間來進行排序,這個時間當然就是我們剛纔介紹的uptimeMillis參數。具體的操作方法就根據時間的順序調用msg.next,從而爲每一個消息指定它的下一個消息是什麼。當然如果你是通過sendMessageAtFrontOfQueue()方法來發送消息的,它也會調用enqueueMessage()來讓消息入隊,只不過時間爲0,這時會把mMessages賦值爲新入隊的這條消息,然後將這條消息的next指定爲剛纔的mMessages,這樣也就完成了添加消息到隊列頭部的操作。
現在入隊操作我們就已經看明白了,那出隊操作是在哪裏進行的呢?這個就需要看一看Looper.loop()方法的源碼了,如下所示:
  1. public static final void loop() {  
  2.     Looper me = myLooper();  
  3.     MessageQueue queue = me.mQueue;  
  4.     while (true) {  
  5.         Message msg = queue.next(); // might block  
  6.         if (msg != null) {  
  7.             if (msg.target == null) {  
  8.                 return;  
  9.             }  
  10.             if (me.mLogging!= null) me.mLogging.println(  
  11.                     ">>>>> Dispatching to " + msg.target + " "  
  12.                     + msg.callback + ": " + msg.what  
  13.                     );  
  14.             msg.target.dispatchMessage(msg);  
  15.             if (me.mLogging!= null) me.mLogging.println(  
  16.                     "<<<<< Finished to    " + msg.target + " "  
  17.                     + msg.callback);  
  18.             msg.recycle();  
  19.         }  
  20.     }  
  21. }  
可以看到,這個方法從第4行開始,進入了一個死循環,然後不斷地調用的MessageQueue的next()方法,我想你已經猜到了,這個next()方法就是消息隊列的出隊方法。不過由於這個方法的代碼稍微有點長,我就不貼出來了,它的簡單邏輯就是如果當前MessageQueue中存在mMessages(即待處理消息),就將這個消息出隊,然後讓下一條消息成爲mMessages,否則就進入一個阻塞狀態,一直等到有新的消息入隊。繼續看loop()方法的第14行,每當有一個消息出隊,就將它傳遞到msg.target的dispatchMessage()方法中,那這裏msg.target又是什麼呢?其實就是Handler啦,你觀察一下上面sendMessageAtTime()方法的第6行就可以看出來了。接下來當然就要看一看Handler中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. }  
在第5行進行判斷,如果mCallback不爲空,則調用mCallback的handleMessage()方法,否則直接調用Handler的handleMessage()方法,並將消息對象作爲參數傳遞過去。這樣我相信大家就都明白了爲什麼handleMessage()方法中可以獲取到之前發送的消息了吧!

因此,一個最標準的異步消息處理線程的寫法應該是這樣:

  1. class LooperThread extends Thread {  
  2.       public Handler mHandler;  
  3.   
  4.       public void run() {  
  5.           Looper.prepare();  
  6.   
  7.           mHandler = new Handler() {  
  8.               public void handleMessage(Message msg) {  
  9.                   // process incoming messages here  
  10.               }  
  11.           };  
  12.   
  13.           Looper.loop();  
  14.       }  
  15.   }  
當然,這段代碼是從Android官方文檔上覆制的,不過大家現在再來看這段代碼,是不是理解的更加深刻了?

那麼我們還是要來繼續分析一下,爲什麼使用異步消息處理的方式就可以對UI進行操作了呢?這是由於Handler總是依附於創建時所在的線程,比如我們的Handler是在主線程中創建的,而在子線程中又無法直接對UI進行操作,於是我們就通過一系列的發送消息、入隊、出隊等環節,最後調用到了Handler的handleMessage()方法中,這時的handleMessage()方法已經是在主線程中運行的,因而我們當然可以在這裏進行UI操作了。整個異步消息處理流程的示意圖如下圖所示:


另外除了發送消息之外,我們還有以下幾種方法可以在子線程中進行UI操作:

1. Handler的post()方法

2. View的post()方法

3. Activity的runOnUiThread()方法

我們先來看下Handler中的post()方法,代碼如下所示:

  1. public final boolean post(Runnable r)  
  2. {  
  3.    return  sendMessageDelayed(getPostMessage(r), 0);  
  4. }  
原來這裏還是調用了sendMessageDelayed()方法去發送一條消息啊,並且還使用了getPostMessage()方法將Runnable對象轉換成了一條消息,我們來看下這個方法的源碼:
  1. private final Message getPostMessage(Runnable r) {  
  2.     Message m = Message.obtain();  
  3.     m.callback = r;  
  4.     return m;  
  5. }  
在這個方法中將消息的callback字段的值指定爲傳入的Runnable對象。咦?這個callback字段看起來有些眼熟啊,喔!在Handler的dispatchMessage()方法中原來有做一個檢查,如果Message的callback等於null纔會去調用handleMessage()方法,否則就調用handleCallback()方法。那我們快來看下handleCallback()方法中的代碼吧:
  1. private final void handleCallback(Message message) {  
  2.     message.callback.run();  
  3. }  
也太簡單了!竟然就是直接調用了一開始傳入的Runnable對象的run()方法。因此在子線程中通過Handler的post()方法進行UI操作就可以這麼寫:
  1. public class MainActivity extends Activity {  
  2.   
  3.     private Handler handler;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         handler = new Handler();  
  10.         new Thread(new Runnable() {  
  11.             @Override  
  12.             public void run() {  
  13.                 handler.post(new Runnable() {  
  14.                     @Override  
  15.                     public void run() {  
  16.                         // 在這裏進行UI操作  
  17.                     }  
  18.                 });  
  19.             }  
  20.         }).start();  
  21.     }  
  22. }  
雖然寫法上相差很多,但是原理是完全一樣的,我們在Runnable對象的run()方法裏更新UI,效果完全等同於在handleMessage()方法中更新UI。

然後再來看一下View中的post()方法,代碼如下所示:

  1. public boolean post(Runnable action) {  
  2.     Handler handler;  
  3.     if (mAttachInfo != null) {  
  4.         handler = mAttachInfo.mHandler;  
  5.     } else {  
  6.         ViewRoot.getRunQueue().post(action);  
  7.         return true;  
  8.     }  
  9.     return handler.post(action);  
  10. }  
原來就是調用了Handler中的post()方法,我相信已經沒有什麼必要再做解釋了。

最後再來看一下Activity中的runOnUiThread()方法,代碼如下所示:

  1. public final void runOnUiThread(Runnable action) {  
  2.     if (Thread.currentThread() != mUiThread) {  
  3.         mHandler.post(action);  
  4.     } else {  
  5.         action.run();  
  6.     }  
  7. }  
如果當前的線程不等於UI線程(主線程),就去調用Handler的post()方法,否則就直接調用Runnable對象的run()方法。還有什麼會比這更清晰明瞭的嗎?

通過以上所有源碼的分析,我們已經發現了,不管是使用哪種方法在子線程中更新UI,其實背後的原理都是相同的,必須都要藉助異步消息處理的機制來實現,而我們又已經將這個機制的流程完全搞明白了,真是一件一本萬利的事情啊。

發佈了15 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章