Android開發之訊息處理機制(二)——訊息迴圈
/*
* Android開發之訊息處理機制(二)——訊息迴圈
* 北京Android俱樂部群:167839253
* Created on: 2011-9-1
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
先介紹一下如何在Android應用程式中使用後台線程和UI線程。
建立Android友好的後台線程時,最靈活的方式就是建立Handler子類的一個執行個體。每個Activity僅需要一個Handler對象,而且不需要手動註冊他。
後台線程可以與Handler通訊,Handler將在Activity的UI線程上執行自己所有的工作。這一點非常重要。因為UI的更改,只能發生在Activity的UI線程上。
有兩種與Handler通訊的方式:訊息和Runnable對象。
關於訊息:
要向Handler發送一個Message,首先調用obtainMessage()從池中擷取Message對象。obtainMessage()對象有許多特點,允許建立空的Message對象或填充了訊息標示符和參數的對象。需要的Handler處理過程越複雜,就越需要將資料放在Message中,以協助Handler區分不同的事件。
然後可以通過訊息佇列將Message發送給Handler,此時需要使用sendMessage…()系列方法中的一個方法。
sendMessage();立即將訊息放在隊列中。
sendMessageAtFrontOfQueue();立即將訊息放在隊列中,並將其放在訊息佇列的最前面,這樣該訊息就會具有比所有其他訊息更高的優先順序。
sendMessageAtTime();在規定的時間將訊息放在隊列中,這個時間用ms表示,基於系統正常工作時間。(SystemClock.uptimeMillis())
sendMessageDelayed();在一段延遲之後將訊息放在隊列中,延遲用ms表示。
要處理這些訊息,Handler需要實現handleMessage(),將使用出現在訊息佇列中的每個訊息來調用handleMessage()。此處Handler可以根據需要更新UI。但是,它仍然應該迅速完成此工作,因為在它完成之前,其他UI工作會暫停。
關於Runnable :
如果不願意使用Message對象,也可以將Runnable對象傳遞給Handler,Handler將在ActivityUI線程上運行這些Runnable對象。Handler提供了一組post…()方法來傳入Runnable對象供最終處理。
對Android的訊息迴圈機制的理解,可以仿照Windows訊息迴圈的機制進行。
每個Handler都會和一個線程和線程的message queue關聯起來,此時你可以傳遞messages 和 runnable對象到message queue中。後面可以從message queue中拿出該對象並且執行。Handler, Message queue這兩個對象之間的互動,就涉及到了Looper這個東西。
關於Handler,Looper,Message Queue三者之間的關係,如所示:
Handler有很多建構函式
Handler在無參數的構造方法中調用Looper.myLooper()方法,裡面就是從當前線程裡面擷取一個Looper的同時一併初始化MessageQueue,並且從中得到looper的MessageQueue。可以看出Handler就是Looper和MessageQueue的管理者和調度者。
其中最重要的是sendMessageAtTime方法,當你往Handler中發送Message訊息的時候,從代碼看出他自己並不去處理Message,而是交給了MessageQueue,由queue.enqueueMessage來處理。
Looper代碼中有一個prepare()方法,通過ThreadLocal實現一個線程只有一個Looper。
Handler的訊息發送
Handler是一個核心,負責message的建立,獲得,處理。Handler的sendMessage()方法最終其實就是調用了queue.enqueueMessage(msg, uptimeMillis);把訊息放入message queue中。Looper通過Looper.loop()檢測到有訊息並將訊息廣播後,Handler又負責接收到此訊息並調用handleMessage進行處理接下來的事情。Message的建立一般都是通過如下代碼建立的,把Handler傳進去。
public final Message obtainMessage()
{
return Message.obtain(this);
}
這種工作方式如所示:
Message在裡面是一個鏈表的結構。在這個方法中,會首先去MessagePool(訊息資源回收筒)去找Message,如果有被回收的Message,則會將這個Message取出來進行再利用。如果沒有被回收的,這時系統才會真正new一個Message對象出來當然MessagePool不是無限大的,它最多隻能儲存十條回收的訊息,多出來的就直接刪除了。
至此,我們看到,一個Message經由Handler建立、發送,MessageQueue的入隊,Looper的抽取,又再一次地回到Handler進行處理。而繞的這一圈,也正好協助我們將同步操作變成了非同步作業。
在主線程(UI線程)裡,如果建立Handler時不傳入Looper對象,那麼將直接使用主線程(UI線程)的Looper對象(系統已經幫我們建立了);在其它線程裡,如果建立Handler時不傳入Looper對象,那麼,這個Handler將不能接收處理訊息。在建立Handler之前,為該線程準備好一個Looper(Looper.prepare),然後讓這個Looper跑起來(Looper.loop),抽取Message,這樣,Handler才能正常工作。
因此,Handler處理訊息總是在建立Handler的線程裡運行。
下面根據上述理論知識,來舉幾個例子:
例1:
在UI線程中調用Handler的Post方法。代碼如下:
/* * Android開發之訊息處理機制(二)——訊息迴圈 * MessageCircle.java * Created on: 2011-9-2 * Author: blueeagle * Email: liujiaxiang@gmail.com */package blueeagle.com;import android.app.Activity;import android.os.Bundle;import android.os.Handler;public class MessageCircle extends Activity { /** Called when the activity is first created. */private Handler myHandler = new Handler();private Runnable myRunnable = new Runnable(){@Overridepublic void run(){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println("Current Runnable Thread ID:"+Thread.currentThread().getId());}}; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("Current Activity Thread ID:"+Thread.currentThread().getId()); myHandler.post(myRunnable); } }
程式運行結果如:
在這個程式中,我們New了一個沒有參數的Handler對象myHandler,這個myHandler自動與當前運行程式相關聯,也就是說,這個myHandler將與當前啟動並執行線程使用同一個訊息佇列,並且可以處理該隊列中的訊息。
在程式碼中,這個myHandler向訊息佇列中post了一個myRunnable對象。在myRunnable的run方法中,列印當前線程的ID。由結果得知,我們New出來的這個myHandler是把訊息或者Runnable對象送到當前的線程中的。這裡我們沒有調用線程的start()函數,因此也就不會去建立一個新的線程。而是在原有線程的內部,直接調用run()方法。因此輸出的ID是相同的。
那麼如何建立新的線程呢?在Android開發之訊息處理機制(一)中已經知道如何去建立一個新的線程,這裡繼續舉一個例子來進行比較說明。
例2:
首先需要建立一個線程,這個線程可以是自己建立的線程,也可以是HandlerThread,這個線程需要繼承於Thread類或者實現Runnable介面。其次需要有一個Handler,這個Handler可以是Handler也可以繼承於Handler並且需要有一個有Looper參數的建構函式。然後就可以進行訊息的分發執行了。
具體範例程式碼如下:
/* * Android開發之訊息處理機制(二)——訊息迴圈 * MessageCircle.java * Created on: 2011-9-2 * Author: blueeagle * Email: liujiaxiang@gmail.com */package blueeagle.com;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.HandlerThread;import android.os.Looper;import android.os.Message;import android.widget.TextView;public class MessageCircle extends Activity { public class MyOwnThread extends Thread{ int sleepSpan = 2000;//休眠時間 boolean flag;// 線程執行標誌位 //----------------------------------// //構造方法,初始化類的主要成員變數 public MyOwnThread(){ this.flag = true; } //----------------------------------// //方法,線程執行方法 @Override public void run(){ TextView myTextView = (TextView)findViewById(R.id.mytextview); Looper.prepare(); int i = 1; while(flag){ try{ Thread.sleep(sleepSpan);//休眠一段時間 System.out.println("Current MyOwnThread Thread ID: "+Thread.currentThread().getId()); myTextView.setText(i); i++; } catch(Exception e){ e.printStackTrace();//捕獲異常並列印 } } Looper.loop(); }}public class MyHandler extends Handler{ public MyHandler(){} public MyHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg){ System.out.println("Current MyHandle Thread ID: "+Thread.currentThread().getId()); TextView myTextView = (TextView)findViewById(R.id.mytextview); int i = 1; while(true){ try{ Thread.sleep(1000);//休眠一段時間 System.out.println("Current MyHandle Thread ID: "+Thread.currentThread().getId()); myTextView.setText("i"); i++; } catch(Exception e){ e.printStackTrace();//捕獲異常並列印 } } }}/** Called when the activity is first created. */private MyHandler myHandler = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("Current Activity Thread ID:"+Thread.currentThread().getId()); //myHandler.post(myRunnable); HandlerThread myThread = new HandlerThread("myThread");//產生一個HandlerThread對象,使用Looper來處理訊息佇列。 myThread.start();//啟動這個線程 myHandler = new MyHandler(myThread.getLooper());//將一個線程綁定到myHandler這個對象上。 Message msg = myHandler.obtainMessage();//從myHandler這個對象中擷取訊息 msg.sendToTarget();//將msg對象發送給目標的Handler //下面製造一個自己的線程 MyOwnThread myOwnThread = new MyOwnThread(); myOwnThread.start(); } }
關於線程更新UI的方法,在(一)中已經說過。下面利用一個樣本將訊息迴圈過程進行展示,進而實現音樂播放,UI線程中更新歌詞的例子來說明訊息處理機制。例子較簡單,沒有實現歌詞同步等內容。只是為了更深刻的理解線程運行情況。
按照上述理論:代碼如下:
/* * Android開發之訊息處理機制(二)——訊息迴圈 * MusicPlayer.java * Created on: 2011-9-3 * Author: blueeagle * Email: liujiaxiang@gmail.com */package blueeagle.com;import android.app.Activity;import android.media.MediaPlayer;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MusicPlayer extends Activity { /** Called when the activity is first created. */Button myButton;TextView myTextView;private UIUpdateThread myUpdateThread = new UIUpdateThread(); //定義一個自己的UI更新的線程類MyHandler mHandler = new MyHandler();//定義自己的一個Handler類@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); myButton = (Button)findViewById(R.id.myButton); myTextView = (TextView)findViewById(R.id.myTextView); myButton.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {// TODO Auto-generated method stub System.out.println("主線程運行ID:"+Thread.currentThread().getId());new Thread(myUpdateThread).start();//起一個自己定義的線程} }); } class MyHandler extends Handler{//繼承Handler類時,必須重寫handleMessage方法 public MyHandler(){}public MyHandler(Looper l){super(l);}@Overridepublic void handleMessage(Message msg) {//執行接收到的通知,此時執行的順序是按照隊列進行,即先進先出 myTextView.setText(msg.toString());System.out.println("Current Handler Thread ID:"+Thread.currentThread().getId());} }//該線程將會在單獨的線程中運行class UIUpdateThread implements Runnable { int i=1; public void run() { while (i<11) { Message msg = mHandler.obtainMessage(); mHandler.sendMessage(msg); System.out.println("新線程運行ID:"+Thread.currentThread().getId());i++; try { Thread.sleep(4000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}
關於簡單的音樂播放的源碼如下:
/* * Android開發之訊息處理機制(二)——訊息迴圈 * MusicPlayer.java * Created on: 2011-9-3 * Author: blueeagle * Email: liujiaxiang@gmail.com */package blueeagle.com;import android.app.Activity;import android.media.MediaPlayer;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MusicPlayer extends Activity { /** Called when the activity is first created. */private MediaPlayer mp;Button myButton;TextView myTextView;private UIUpdateThread myUpdateThread = new UIUpdateThread(); //定義一個自己的UI更新的線程類MyHandler mHandler = new MyHandler();//定義自己的一個Handler類@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); myButton = (Button)findViewById(R.id.myButton); myTextView = (TextView)findViewById(R.id.myTextView); myButton.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {// TODO Auto-generated method stubmp = MediaPlayer.create(MusicPlayer.this, R.raw.yinweiaiqing);mp.start(); System.out.println("主線程運行ID:"+Thread.currentThread().getId());new Thread(myUpdateThread).start();} }); } class MyHandler extends Handler{//繼承Handler類時,必須重寫handleMessage方法 public MyHandler(){}public MyHandler(Looper l){super(l);}@Overridepublic void handleMessage(Message msg) {//執行接收到的通知,此時執行的順序是按照隊列進行,即先進先出 myTextView.setText(msg.obj.toString());System.out.println("Current Handler Thread ID:"+Thread.currentThread().getId());} }//該線程將會在單獨的線程中運行 class UIUpdateThread implements Runnable { int i = 0; String myString[] = { "這是一個即時播放的線程操作... ...", "[00:08.79]《因為愛情》", "[00:19.46]E 給你一張過去的CD", "[00:28.68]聽聽那時我們的愛情", "[00:34.12]有時會突然忘了", "[00:37.48]我還在愛著你", "[00:44.98]F 再唱不出那樣的歌曲", "[00:50.48]聽到都會紅著臉躲避", "[00:55.83]雖然會經常忘了", "[00:59.33]我依然愛著你", "[01:05.49]F 因為愛情 不會輕易悲傷", "[01:05.49]F 因為愛情 不會輕易悲傷", "[01:12.09]E 所以一切都是幸福的模樣", "[01:17.24]F 因為愛情 簡單的生長", "[01:22.24]E 依然隨時可以為你瘋狂", "[01:27.21]F 因為愛情 怎麼會有滄桑", "[01:34.30]E 所以我們還是年輕的模樣", "[01:38.90]F 因為愛情 在那個地方", "[01:44.32]E 依然還有人在那裡遊盪", "[01:48.91]E&F 人來人往", "[02:11.57]F 再唱不出那樣的歌曲", "[02:17.70]聽到都會紅著臉躲避", "[02:23.14]雖然會經常忘了", "[02:26.26]E&F 我依然愛著你", "[02:32.60]F 因為愛情 不會輕易悲傷", "[02:39.22]E 所以一切都是幸福的模樣", "[02:44.98]F 因為愛情 簡單的生長", "[02:49.36]E 依然隨時可以為你瘋狂", "[02:54.38]F 因為愛情 怎麼會有滄桑", "[03:00.94]E 所以我們還是年輕的模樣", "[03:06.04]F 因為愛情 在那個地方", "[03:11.63]E 依然還有人在那裡遊盪", "[03:17.04]E&F 人來人往", "[03:21.98]E 給你一張過去的CD", "[03:28.58]聽聽那時我們的愛情", "[03:33.48]F 有時會突然忘了", "[03:36.94]E&F 我還在愛著你"}; public void run() { while (mp.isPlaying()) { Message msg = mHandler.obtainMessage(1, 1, 1, myString[i]); mHandler.sendMessage(msg); System.out.println("新線程運行ID:"+Thread.currentThread().getId()); try { Thread.sleep(4000); i++; if(i == myString.length){ i=myString.length-1; } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}
綜上,基本瞭解了Android下的關於線程,訊息迴圈的一些概念,還需要深入瞭解Looper如何運用在程式中等。