在分析Android訊息機制之前,我們先來看一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class MainActivity extends Activity implements View.OnClickListener { private TextView stateText; private Button btn; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); stateText = (TextView) findViewById(R.id.tv); btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(this); } @Override public void onClick(View v) { new WorkThread().start(); } //工作執行緒 private class WorkThread extends Thread { @Override public void run() { //......處理比較耗時的操作 //處理完成後改變狀態 stateText.setText("completed"); } } } |
這段程式碼似乎看上去很正常,但是當你執行時就會發現,它會報一個致命性的異常:
1 2 3 |
ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8 ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. |
到底是怎麼回事呢?原因在於,Android系統中的檢視元件並不是執行緒安全的,如果要更新檢視,必須在主執行緒中更新,不可以在子執行緒中執行更新的操作。
既然這樣,我們就在子執行緒中通知主執行緒,讓主執行緒做更新操作吧。那麼,我們如何通知主執行緒呢?我們需要使用到Handler物件。
我們稍微修改一下上面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class MainActivity extends Activity implements View.OnClickListener { private static final int COMPLETED = 0; private TextView stateText; private Button btn; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == COMPLETED) { stateText.setText("completed"); } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); stateText = (TextView) findViewById(R.id.tv); btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(this); } @Override public void onClick(View v) { new WorkThread().start(); } //工作執行緒 private class WorkThread extends Thread { @Override public void run() { //......處理比較耗時的操作 //處理完成後給handler傳送訊息 Message msg = new Message(); msg.what = COMPLETED; handler.sendMessage(msg); } } } |
通過上面這種方式,我們就可以解決執行緒安全的問題,把複雜的任務處理工作交給子執行緒去完成,然後子執行緒通過handler物件告知主執行緒,由主執行緒更新檢視,這個過程中訊息機制起著重要的作用。
下面,我們就來分析一下Android中的訊息機制。
熟悉Windows程式設計的朋友知道Windows程式是訊息驅動的,並且有全域性的訊息迴圈系統。Google參考了Windows的訊息迴圈機制,也在Android系統中實現了訊息迴圈機制。Android通過Looper、Handler來實現訊息迴圈機制。Android的訊息迴圈是針對執行緒的,每個執行緒都可以有自己的訊息佇列和訊息迴圈。
Android系統中的Looper負責管理執行緒的訊息佇列和訊息迴圈。通過Looper.myLooper()得到當前執行緒的Looper物件,通過Looper.getMainLooper()得到當前程式的主執行緒的Looper物件。
前面提到,Android的訊息佇列和訊息迴圈都是針對具體執行緒的,一個執行緒可以存在一個訊息佇列和訊息迴圈,特定執行緒的訊息只能分發給本執行緒,不能跨執行緒和跨程式通訊。但是建立的工作執行緒預設是沒有訊息佇列和訊息迴圈的,如果想讓工作執行緒具有訊息佇列和訊息迴圈,就需要線上程中先呼叫Looper.prepare()來建立訊息佇列,然後呼叫Looper.loop()進入訊息迴圈。下面是我們建立的工作執行緒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class WorkThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // 處理收到的訊息 } }; Looper.loop(); } } |
這樣一來,我們建立的工作執行緒就具有了訊息處理機制了。
那麼,為什麼前邊的示例中,我們怎麼沒有看到Looper.prepare()和Looper.loop()的呼叫呢?原因在於,我們的Activity是一個UI執行緒,執行在主執行緒中,Android系統會在Activity啟動時為其建立一個訊息佇列和訊息迴圈。
前面提到最多的是訊息佇列(MessageQueue)和訊息迴圈(Looper),但是我們看到每個訊息處理的地方都有Handler的存在,它是做什麼的呢?Handler的作用是把訊息加入特定的Looper所管理的訊息佇列中,並分發和處理該訊息佇列中的訊息。構造Handler的時候可以指定一個Looper物件,如果不指定則利用當前執行緒的Looper物件建立。下面是Handler的兩個構造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * 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() { 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(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; } /** * Use the provided queue instead of the default one. */ public Handler(Looper looper) { mLooper = looper; mQueue = looper.mQueue; mCallback = null; } |
下面是訊息機制中幾個重要成員的關係圖:
一個Activity中可以建立出多個工作執行緒,如果這些執行緒把他們訊息放入Activity主執行緒的訊息佇列中,那麼訊息就會在主執行緒中處理了。因為主執行緒一般負責檢視元件的更新操作,對於不是執行緒安全的檢視元件來說,這種方式能夠很好的實現檢視的更新。
那麼,子執行緒如何把訊息放入主執行緒的訊息佇列中呢?只要Handler物件以主執行緒的Looper建立,那麼當呼叫Handler的sendMessage方法,系統就會把訊息主執行緒的訊息佇列,並且將會在呼叫handleMessage方法時處理主執行緒訊息佇列中的訊息。
對於子執行緒訪問主執行緒的Handler物件,你可能會問,多個子執行緒都訪問主執行緒的Handler物件,傳送訊息和處理訊息的過程中會不會出現資料的不一致呢?答案是Handler物件不會出現問題,因為Handler物件管理的Looper物件是執行緒安全的,不管是新增訊息到訊息佇列還是從訊息佇列中讀取訊息都是同步保護的,所以不會出現資料不一致現象。
深入理解Android訊息處理機制對於應用程式開發非常重要,也可以讓我們對執行緒同步有更加深刻的認識,希望這篇文章可以對朋友們有所幫助。