1. 程式人生 > >Handler,Looper,Message,MessageQueue,HandlerThread使用總結(上)

Handler,Looper,Message,MessageQueue,HandlerThread使用總結(上)

在安卓程式中,經常會有一些耗時的操作例如下載,網路訪問等,如果將這些放在主執行緒執行,會很耗時,這樣可能會導致一個異常 叫ANR異常(Application Not Responding)將會阻塞UI執行緒,從而會導致程式無響應。因此我們會將一些耗時操作放在子執行緒進行,但是由於android的UI操作並不是執行緒安全的,因此如果多個執行緒同時操作UI的話,會導致執行緒安全問題,因此android制訂了一條規則,只允許UI執行緒(即主執行緒)進行UI操作,那麼我們如何知道子執行緒何時操作完成呢?例如子執行緒下載好圖片以後通知主執行緒進行檢視更新。

Message簡介

子執行緒任務完成以後需要傳送訊息給主執行緒,這個訊息就是Message

的一個例項,定義Message時需要定義三個例項變數

1、what   int型訊息程式碼,用來描述訊息

2、obj      隨訊息傳送的使用者指定物件

3、target  處理該訊息的Handler

建立Message的方法

1、new一個就行

2、使用Handler.obtainMessage(...)該方法可以使我們從公共迴圈池中獲取到一個Message例項,效率會提高



因此我們使用該方法會好些

MessageQueue簡介

MessageQueue(訊息佇列)用來存放Message,採用先進先出的方式發出Message,

Looper簡介

Looper叫做訊息迴圈,他會不斷的檢查 MessageQueue上是否有新訊息,然後抓取訊息,完成指定的任務。每個執行緒有且只有一個Looper物件,用來管理MessageQueue。主執行緒也有Looper,它會自動建立,主執行緒的所有工作都是由他的Looper完成的。Looper主要有兩個方法

1、Looper.prepare     用以啟用Looper

2、Looper.loop           讓Looper開始工作,從訊息佇列中抓取處理訊息

Handler簡介

要處理訊息以及訊息指定的任務時就需要用到Handler,她可以發出新訊息到MessageQueue上,也可以讀取Looper從MessageQueue上獲取的訊息。一個Handler僅與一個Looper相關聯,一個Message也僅與一個目標Hander相關聯。

Handler傳送訊息的方法:


Looper獲取到訊息後,會交由訊息的目標(即訊息的target屬性)處理,訊息一般是在Handler.handleMessage(...)方法中處理


關係圖示



在簡單瞭解過這些知識後,我們寫一個小程式,利用了Handler與Message。該應用的主介面僅有一個ImageView故程式碼不再給出,主要功能是開啟一個子執行緒模擬下載圖片,然後下載完成後在UI執行緒上進行更新。

主要程式碼

宣告handler,模擬下載到的圖片的資源Id陣列

<span style="font-size:18px;"><pre name="code" class="java"><span style="font-size:18px;"><span style="white-space:pre">	</span>private ImageView mImageView;
	// 定義一個Handler
	private Handler mHandler;

	private int mIndex;
	// 存放照片資源id的陣列
	int[] imageIds = new int[] { R.drawable.pic1, R.drawable.pic2,
			R.drawable.pic3, R.drawable.pic4

	};</span></span>



模擬下載圖片的方法

<span style="font-size:18px;">// 模擬下載的操作,隨機生成一個數字
	public int virtualDown() {
		Random ran = new Random();
		int value = ran.nextInt(5);
		return value;
	}</span>



使用TimerTask開啟一個下載的子執行緒,獲取到訊息,併發送

<span style="font-size:18px;"><span style="font-size:18px;">// 開啟一個TimerTask模擬下載的子執行緒
		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {

				Bundle bundle = new Bundle();
				bundle.putInt("value", virtualDown());
				// 獲取到訊息
				// Message msg = new Message();
				Message msg = mHandler.obtainMessage();
				msg.what = 0x1233;
				msg.setData(bundle);
				// 傳送訊息,並將生成的數字傳遞過去
				mHandler.sendMessage(msg);
				

			}
		}, 0, 2000);
	}</span></span>


生成handle例項並在handleMessage中處理訊息

<span style="font-size:18px;"><span style="white-space:pre">	</span>mHandler = new Handler() {

			@Override
			// 處理訊息的方法
			public void handleMessage(Message msg) {
				// what屬性是訊息的標識,用來區分每個訊息
				if (msg.what == 0x1233) {
					mIndex = msg.getData().getInt("value");
					// 設定圖片的顯示,這是在主執行緒進行的
					switch (mIndex) {
					case 0:
						mImageView.setImageResource(imageIds[0]);
						break;
					case 1:
						mImageView.setImageResource(imageIds[1]);
						break;
					case 2:
						mImageView.setImageResource(imageIds[2]);
						break;
					case 3:
						mImageView.setImageResource(imageIds[3]);
						break;
					}
				}
			}
		};</span>


執行程式我們就可以看到主介面的圖片每隔兩秒就會變化一次了,當然圖片是事先準備好的,並非下載的,過段時間博主會實現真正的下載。

在上面的程式中為什麼沒有見到Looper呢?這是因為主執行緒在啟動過程中自動建立了Looper。而子執行緒預設是不帶Looper的,所以我們就需要自己建立Looper,這時候就需要用到上述的兩個方法,Looper.prepare和Looper.loop啟動Looper了,官方的API Demo的示例為:

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();
		}
	}