多線程和同步之Handler和Looper(三)

Android在android.os包中定義了兩個類,它們通常是多線程應用線程間通信的基石:Handler和Looper

AsyncTask對象隱藏了Handler和Looper的細節,在某些情況還是要直接跟Handler和Looper打交道,比如把Runnnable對象傳遞到主線程之外的線程


Message:消息,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。

Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。

MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message以鏈表的方式串聯起來的,等待Looper的抽取。

Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper

Thread:線程,負責調度整個消息循環,即消息循環的執行場所。


Handler的使用:

public class MyThread extends Thread{
		private static final String TAG="MyThread";
		private Handler mHandler;
		public MyThread(String name) {
			super(name);

		}
		public Handler getmHandler() {
			return mHandler;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			super.run();
			Looper.prepare();//把Looper綁定到此線程
			mHandler=new Handler(){
				public void handleMessage(Message msg){
					switch(msg.what){
					//處理消息
					}
				}
			};
			//Handler綁定到此線程的Looper
			Looper.loop();//調用loop()去啓動消息循環
		}		
	}
注:Handler對象在run()方法中創建,因爲它需要綁定到指定的Looper,這個Looper就是在run()方法中調用Looper.prepare()創建的,因此在線程產生之前,調用getHandler() 將返回null


Looper的使用:

public class MyHandlerThread extends HandlerThread{
		private static final String TAG="MyHandlerThread";
		private Handler mHandler;
		public MyHandlerThread(String name) {
			super(name);

		}
		public Handler getmHandler() {
			return mHandler;
		}
		public void start() {
			// TODO Auto-generated method stub
			super.start();
			Looper looper=getLooper();//這裏會一直阻塞到線程的Looper對象初始化結束
			mHandler=new Handler(looper){
				public void handleMessage(Message msg){
					switch(msg.what){
					//處理消息
					}
				}
			};
		}		
	}

android中的線程

1. 不要阻塞UI線程. 如果在UI線程中執行阻塞或者耗時操作會導致UI線程無法響應用戶請求.

2. 不能在非UI線程(也稱爲工作線程)中更新UI, 這是因爲android的UI控件都是線程不安全的.

由上所述, 開發者經常會啓動工作線程完成耗時操作或阻塞操作, 如果需要在工作線程的執行期間更新UI狀態, 則應該通知UI線程來進行.

線程間通信

請看下面的代碼:

Java代碼  收藏代碼
  1. public void onClick(View v) {  
  2.     new Thread(new Runnable() {  
  3.         public void run() {  
  4.             Bitmap b = loadImageFromNetwork("http://example.com/image.png");  
  5.             mImageView.setImageBitmap(b);  
  6.         }  
  7.     }).start();  
  8. }  

上面的代碼是錯誤的, mImageView.setImageBitmap(b)違反了第二條準則--不能在工作線程中更新UI. 

線程間通信可以解決工作線程如何通知UI線程更新控件的問題. android提供了3種線程間通信的方案:

1. 調用以下方法:

Activity.runOnUiThread(Runnable)

View.post(Runnable)

View.postDelayed(Runnable, long)

如果在工作線程中調用了這3個方法, 那麼方法中Runnable參數封裝的操作會在UI線程中執行.

使用這種方式可以修正例子中的錯誤之處:

Java代碼  收藏代碼
  1. public void onClick(View v) {  
  2.     new Thread(new Runnable() {  
  3.         public void run() {  
  4.             final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");  
  5.             mImageView.post(new Runnable() {  
  6.                 // run方法會在UI線程中執行  
  7.                 public void run() {  
  8.                     mImageView.setImageBitmap(bitmap);  
  9.                 }  
  10.             });  
  11.         }  
  12.     }).start();  
  13. }  

2. Handler機制. Handler機制允許開發者在工作線程中調用與UI線程綁定的handler對象的sendMessage()方法向UI線程的消息隊列發送一條消息, UI線程會在適當的時候從消息隊列中取出消息並完成處理.

3. 使用AsyncTask類. 創建一個AsyncTask類的子類, 並根據需要選擇覆寫onPreExecute(), doInBackground(), onProgressUpdate(), onPostExecute()方法.AsyncTask類的具體使用方法請參看文檔, 以下是一些大概的說明:

a. AsyncTask類是一個泛型類, 存在3個泛型參數. 第一個參數指定execute方法的參數類型, 第二個參數指定onProgressUpdate()方法的參數類型, 第三個參數指定 doInBackground()方法的返回值類型以及onPostExecute()方法的參數類型.

b. 執行流程: 在UI線程中調用AsyncTask類的execute方法(只有該步驟是由程序員控制的)-->系統調用onPreExecute(), 這個方法在UI線程中執行-->系統調用doInBackground()方法, 這個方法在工作線程中執行-->在doInBackground()方法中每調用一次publishProgress()方法, 就會在UI線程中執行一次onProgressUpdate()方法-->doInBackground()方法執行完成後, 系統將調用 onPostExecute()方法, 並將doInBackground()方法的返回值傳遞給 onPostExecute()方法的形參.  onPostExecute()方法在UI線程中執行.

採用這種方式也可以修正例子中的錯誤之處:

Java代碼  收藏代碼
  1. public void onClick(View v) {  
  2.     new DownloadImageTask().execute("http://example.com/image.png");  
  3. }  
  4. private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {  
  5.     protected Bitmap doInBackground(String... urls) {  
  6.         return loadImageFromNetwork(urls[0]);  
  7.     }  
  8.       
  9.     protected void onPostExecute(Bitmap result) {  
  10.         mImageView.setImageBitmap(result);  
  11.     }  
  12. }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章