Android中的訊息佇列和執行緒佇列機制
下面是訊息機制中幾個重要成員的關係圖:
一個Activity中可以創建出多個工作執行緒,如果這些執行緒把他們訊息放入Activity主執行緒的訊息佇列中,那麼訊息就會在主執行緒中處理了。因為主執行緒一般負責檢視元件的更新操作,對於不是執行緒安全的檢視元件來說,這種方式能夠很好的實現檢視的更新 。
那麼,子執行緒如何把訊息放入主執行緒的訊息佇列中呢?只要Handler物件以主執行緒的Looper建立,那麼當呼叫Handler的sendMessage方法,系統就會把訊息主執行緒的訊息佇列,並且將會在呼叫handleMessage方法時處理主執行緒訊息佇列中的訊息 。
對於子執行緒訪問主執行緒的Handler物件,你可能會問,多個子執行緒都訪問主執行緒的Handler物件,傳送訊息和處理訊息的過程中會不會出現資料的不一致呢?答案是Handler物件不會出現問題,因為Handler物件管理的Looper物件是執行緒安全的,不管是新增訊息到訊息佇列還是從訊息佇列中讀取訊息都是同步保護的,所以不會出現資料不一致現象。
Android中的Looper類,是用來封裝訊息迴圈和訊息佇列的一個類,用於在android執行緒中進行訊息處理。handler其實可以看做是一個工具類,用來向訊息佇列中插入訊息的。
(1) Looper類用來為一個執行緒開啟一個訊息迴圈。 預設情況下android中新誕生的執行緒是沒有開啟訊息迴圈的。(主執行緒除外,主執行緒系統會自動為其建立Looper物件,開啟訊息迴圈。) Looper物件通過MessageQueue來存放訊息和事件。一個執行緒只能有一個Looper,對應一個MessageQueue。
(2) 通常是通過Handler物件來與Looper進行互動的。Handler可看做是Looper的一個介面,用來向指定的Looper傳送訊息及定義處理方法。 預設情況下Handler會與其被定義時所線上程的Looper繫結,比如,Handler在主執行緒中定義,那麼它是與主執行緒的Looper繫結。 mainHandler = new Handler() 等價於new Handler(Looper.myLooper()). Looper.myLooper():獲取當前程序的looper物件,類似的 Looper.getMainLooper() 用於獲取主執行緒的Looper物件。
(3) 在非主執行緒中直接new Handler() 會報如下的錯誤: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 原因是非主執行緒中預設沒有建立Looper物件,需要先呼叫Looper.prepare()啟用Looper。
(4) Looper.loop(); 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。
注意:寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。把下面例子中的mHandler宣告成類成員,在主執行緒通過mHandler傳送訊息即可。 Android官方文件中Looper的介紹: Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.
This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.
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();
}
3、執行緒佇列
Handler 就是實現佇列的形式,一個 Handler 共有兩個佇列:一個是執行緒佇列,另一個是訊息佇列 。
要應用 Handler 進行執行緒佇列,其流程主要是:
1. 要定義一個 Handler 物件;
2. 定義一個執行緒,並覆寫其 run 方法;
3. 通過 Handler 物件把第二的執行緒物件壓到佇列中等待執行;
4. Handler 物件把第二的執行緒物件從佇列中去除(當不想執行執行緒時 )。
注:如果需要迴圈執行執行緒,即可在處理執行緒後,再次利用 Handler 物件把執行緒物件壓到佇列中。
定義一個 Handler 物件的程式碼如下:
Handler myFirstHandler= new Handler
定義一個執行緒的程式碼如下:
Runnable myThread = new Runnable() {
public void run() {
// TODO Auto-generated method stub
}
};
Handler物件把執行緒壓入佇列的方法是:
myFirstHandler.post(myThread); //把一個執行緒放到佇列裡面去
Handler物件把執行緒從佇列中去除的方法是:
myFirstHandler.removeCallbacks(myThread);//把執行緒從佇列中去除
通過上面我們的處理,雖然通過 Handler 物件往佇列裡面加入了一個新的執行緒,但實際上Handler 和它所屬的 Activity 是處於同一個執行緒中。因為我們通過 Handler 把執行緒加到佇列中,實際是直接執行了 Runable 裡面的 run 方法,而且沒有像 JAVA 經典多執行緒程式設計一樣呼叫執行緒的start 方法,所以只是把執行緒物件加到 Activity 的執行緒中一起執行,而不是新開一個執行緒,即不是多真正的多執行緒。
4、runOnUiThread更新主執行緒
更新UI採用Handle+Thread,需要傳送訊息,接受處理訊息(在回撥方法中處理),比較繁瑣。除此之外,還可以使用runOnUiThread方法。
利用Activity.runOnUiThread(Runnable)把更新ui的程式碼建立在Runnable中,然後在需要更新ui時,把這個Runnable物件傳給Activity.runOnUiThread(Runnable)。
Runnable對像就能在ui程式中被呼叫。如果當前執行緒是UI執行緒,那麼行動是立即執行。如果當前執行緒不是UI執行緒,操作是釋出到事件佇列的UI執行緒。
public class TestActivity extends Activity {
Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler_msg);
btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
// 模擬耗時的操作。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 更新主執行緒UI
TestActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
btn.setText("更新完畢!");
}
});
}
}).start();
}
});
}
5、不是所有的Handler都能更新UI
Handler處理訊息總是在建立Handler的執行緒裡執行。而我們的訊息處理中,不乏更新UI的操作,不正確的執行緒直接更新UI將引發異常。因此,需要時刻關心Handler在哪個執行緒裡建立的。如何更新UI才能不出異常呢?SDK告訴我們,有以下4種方式可以從其它執行緒訪問UI執行緒(也即執行緒間通訊):
· Activity.runOnUiThread(Runnable)
· View.post(Runnable)
· View.postDelayed(Runnable, long)
·在UI執行緒中建立的Handler
其中,重點說一下的是View.post(Runnable)方法。在post(Runnableaction)方法裡,View獲得當前執行緒(即UI執行緒)的Handler,然後將action物件post到Handler裡。在Handler裡,它將傳遞過來的action物件包裝成一個Message(Message的callback為action),然後將其投入UI執行緒的訊息迴圈中。在 Handler再次處理該Message時,有一條分支(未解釋的那條)就是為它所設,直接呼叫runnable的run方法。而此時,已經路由到UI執行緒裡,因此,我們可以毫無顧慮的來更新UI。
幾點小結
· Handler的處理過程執行在建立Handler的執行緒裡
·一個Looper對應一個MessageQueue,一個執行緒對應一個Looper,一個Looper可以對應多個Handler
·不確定當前執行緒時,更新UI時儘量呼叫View.post方法
· handler應該由處理訊息的執行緒建立。
·handler與建立它的執行緒相關聯,而且也只與建立它的執行緒相關聯。handler執行在建立它的執行緒中,所以,如果在handler中進行耗時的操作,會阻塞建立它的執行緒。
· Android的執行緒分為有訊息迴圈的執行緒和沒有訊息迴圈的執行緒,有訊息迴圈的執行緒一般都會有一個Looper。主執行緒(UI執行緒)就是一個訊息迴圈的執行緒。
· Looper.myLooper(); //獲得當前的Looper
Looper.getMainLooper() //獲得UI執行緒的Lopper
· Handle的初始化函式(建構函式),如果沒有引數,那麼他就預設使用的是當前的Looper,如果有Looper引數,就是用對應的執行緒的Looper。
·如果一個執行緒中呼叫Looper.prepare(),那麼系統就會自動的為該執行緒建立一個訊息佇列,然後呼叫Looper.loop();之後就進入了訊息迴圈,這個之後就可以發訊息、取訊息、和處理訊息。
6、HandlerThread
在上面的總結中指出,Android的執行緒分為有訊息迴圈的執行緒和沒有訊息迴圈的執行緒,有訊息迴圈的執行緒一般都會有一個Looper。事實上Android提供了一個封裝好的帶有looper的執行緒類,即為HandlerThread,具體可參見下面的程式碼:
public class HandlerThreadActivity extends Activity {
private static final String TAG = "HandlerThreadActivity";
private HandlerThreadmHandlerThread;
private MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generatedmethod stub
super.onCreate(savedInstanceState);
TextView text = new TextView(this);
text.setText("HandlerThreadActivity");
setContentView(text);
Log.d(TAG, "The mainthread id = " + Thread.currentThread().getId());
//生成一個HandlerThread物件,實現了使用Looper來處理訊息佇列的功能,
//這個類由Android應用程式框架提供
mHandlerThread = new HandlerThread("handler_thread");
//在使用HandlerThread的getLooper()方法之前,必須先呼叫該類的start();
mHandlerThread.start();
//即這個Handler是執行在mHandlerThread這個執行緒中
mMyHandler = new MyHandler(mHandlerThread.getLooper());
mMyHandler.sendEmptyMessage(1);
}
private class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "MyHandler-->handleMessage-->threadid = " +Thread.currentThread().getId());
super.handleMessage(msg);
}
}
}