深入理解Android訊息處理系統——Looper、Handler、Thread
熟悉Windows程式設計的朋友可能知道Windows程式是訊息驅動的,並且有全域性的訊息迴圈系統。而Android應用程式也是訊息驅動的,按道理來說也應該提供訊息迴圈機制。實際上谷歌參考了Windows的訊息迴圈機制,也在Android系統中實現了訊息迴圈機制。Android通過Looper、Handler來實現訊息迴圈機制,Android訊息迴圈是針對執行緒的(每個執行緒都可以有自己的訊息佇列和訊息迴圈)。本文深入介紹一下Android訊息處理系統原理。
Android系統中Looper負責管理執行緒的訊息佇列和訊息迴圈,具體實現請參考Looper的原始碼。 可以通過Loop.myLooper()得到當前執行緒的Looper物件,通過Loop.getMainLooper()可以獲得當前程序的主執行緒的Looper物件。
前面提到Android系統的訊息佇列和訊息迴圈都是針對具體執行緒的,一個執行緒可以存在(當然也可以不存在)一個訊息佇列和一個訊息迴圈(Looper),特定執行緒的訊息只能分發給本執行緒,不能進行跨執行緒,跨程序通訊。但是建立的工作執行緒預設是沒有訊息迴圈和訊息佇列的,如果想讓該執行緒具有訊息佇列和訊息迴圈,需要線上程中首先呼叫Looper.prepare()來建立訊息佇列,然後呼叫Looper.loop()進入訊息迴圈。如下例所示:
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(); } }
這樣你的執行緒就具有了訊息處理機制了,在Handler中進行訊息處理。
Activity是一個UI執行緒,運行於主執行緒中,Android系統在啟動的時候會為Activity建立一個訊息佇列和訊息迴圈(Looper)。詳細實現請參考ActivityThread.java檔案。
Handler的作用是把訊息加入特定的(Looper)訊息佇列中,並分發和處理該訊息佇列中的訊息。構造Handler的時候可以指定一個Looper物件,如果不指定則利用當前執行緒的Looper建立。詳細實現請參考Looper的原始碼。
Activity、Looper、Handler的關係如下圖所示:
一個Activity中可以建立多個工作執行緒或者其他的元件,如果這些執行緒或者元件把他們的訊息放入Activity的主執行緒訊息佇列,那麼該訊息就會在主執行緒中處理了。因為主執行緒一般負責介面的更新操作,並且Android系統中的weget不是執行緒安全的,所以這種方式可以很好的實現Android介面更新。在Android系統中這種方式有著廣泛的運用。
那麼另外一個執行緒怎樣把訊息放入主執行緒的訊息佇列呢?答案是通過Handle物件,只要Handler物件以主執行緒的Looper建立,那麼呼叫Handler的sendMessage等介面,將會把訊息放入佇列都將是放入主執行緒的訊息佇列。並且將會在Handler主執行緒中呼叫該handler的handleMessage介面來處理訊息。
這裡面涉及到執行緒同步問題,請先參考如下例子來理解Handler物件的執行緒模型:
1、首先建立MyHandler工程。
2、在MyHandler.java中加入如下的程式碼:
package com.simon;
import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.os.Handler;
public class MyHandler extends Activity {
static final String TAG = "Handler";
Handler h = new Handler(){
public void handleMessage (Message msg)
{
switch(msg.what)
{
case HANDLER_TEST:
Log.d(TAG, "The handler thread id = " + Thread.currentThread().getId() + "\n");
break;
}
}
};
static final int HANDLER_TEST = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "The main thread id = " + Thread.currentThread().getId() + "\n");
new myThread().start();
setContentView(R.layout.main);
}
class myThread extends Thread
{
public void run()
{
Message msg = new Message();
msg.what = HANDLER_TEST;
h.sendMessage(msg);
Log.d(TAG, "The worker thread id = " + Thread.currentThread().getId() + "\n");
}
}
}
在這個例子中我們主要是列印,這種處理機制各個模組的所處的執行緒情況。如下是我的機器執行結果:
09-10 23:40:51.478: DEBUG/Handler(302): The main thread id = 1 09-10 23:40:51.569: DEBUG/Handler(302): The worker thread id = 8 09-10 23:40:52.128: DEBUG/Handler(302): The handler thread id = 1
我們可以看出訊息處理是在主執行緒中處理的,在訊息處理函式中可以安全的呼叫主執行緒中的任何資源,包括重新整理介面。工作執行緒和主執行緒執行在不同的執行緒中,所以必須要注意這兩個執行緒間的競爭關係。
上例中,你可能注意到在工作執行緒中訪問了主執行緒handler物件,並在呼叫handler的物件向訊息佇列加入了一個訊息。這個過程中會不會出現訊息佇列資料不一致問題呢?答案是handler物件不會出問題,因為handler物件管理的Looper物件是執行緒安全的,不管是加入訊息到訊息佇列和從佇列讀出訊息都是有同步物件保護的,具體請參考Looper.java檔案。上例中沒有修改handler物件,所以handler物件不可能會出現資料不一致的問題。
通過上面的分析,我們可以得出如下結論:
1、如果通過工作執行緒重新整理介面,推薦使用handler物件來實現。
2、注意工作執行緒和主執行緒之間的競爭關係。推薦handler物件在主執行緒中構造完成(並且啟動工作執行緒之後不要再修改之,否則會出現資料不一致),然後在工作執行緒中可以放心的呼叫傳送訊息SendMessage等介面。
3、除了2所述的hanlder物件之外的任何主執行緒的成員變數如果在工作執行緒中呼叫,仔細考慮執行緒同步問題。如果有必要需要加入同步物件保護該變數。
4、handler物件的handleMessage介面將會在主執行緒中呼叫。在這個函式可以放心的呼叫主執行緒中任何變數和函式,進而完成更新UI的任務。
5、Android很多API也利用Handler這種執行緒特性,作為一種回撥函式的變種,來通知呼叫者。這樣Android框架就可以在其執行緒中將訊息傳送到呼叫者的執行緒訊息佇列之中,不用擔心執行緒同步的問題。
深入理解Android訊息處理機制對於應用程式開發非常重要,也可以讓你對執行緒同步有更加深刻的認識。以上是最近Simon學習Android訊息處理機制的一點兒總結,如有錯誤之處請不吝指教。
參考資料: