Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> android線程 Handler Message Queue AsyncTask線程模型 線程交互 + 修改Button樣式 示例 最終easy整合版

android線程 Handler Message Queue AsyncTask線程模型 線程交互 + 修改Button樣式 示例 最終easy整合版

編輯:Android開發實例

    希望和我一樣正在學習android的新手可以通過這篇文章熟悉android線程的交互,以及使用方法。以便將來更好的在自己的程序中運用。也希望大家保持學習android的積極性,一起努力、交流、成長,因為文章比較長 大家可以分幾次閱讀。這篇文章包含了好幾個例子,我之所把它們寫在一起,是覺得它們有一定聯系。
   
  好吧我們開始讓 我們先從android進程說起吧。

   當一個程序第一次啟動的時候,Android會啟動一個LINUX進程和一個主線程。默認的情況下,所有該程序的組件都將在該進程和線程中運行。同時,Android會為每個應用程序分配一個單獨的LINUX用戶。Android會勁量保留一個正在運行進程,只在內存資源出現不足時,Android會參試停止一些進程從而釋放足夠的資源給其他新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時的響應用戶的事件。Android會 根據進程中運行的組件類別以及組件的狀態來判斷該進程的重要性,Android會 首先停止那些不重要的進程。按照重要性從高到低一共有五個級別:

l  前台進程

前台進程是用戶當前正在使用的進程。只有一些前台進程可以在任何時候都存在。他們是最後一個被結束的,當內存低到根本連他們都不能運行的時候。一般來說, 在這種情況下,設備會進行內存調度,中止一些前台進程來保持對用戶交互的響應。

l  可見進程

可見進程不包含前台的組件但是會在屏幕上顯示一個可見的進程是的重要程度很高,除非前台進程需要獲取它的資源,不然不會被中止。

l  服務進程

運行著一個通過startService() 方法啟動的service,這個service不屬於上面提到的2種更高重要性的。service所在的進程雖然對用戶不是直接可見的,但是他們執行了用戶非常關注的任務(比如播放mp3,從網絡下載數據)。只要前台進程和可見進程有足夠的內存,系統不會 回收他們。

l  後台進程

運行著一個對用戶不可見的activity(調用過 onStop() 方法).這些進程對用戶體驗沒有直接的影響,可以在服務進程、可見進程、前台進 程需要內存的時候回收。通常,系統中會有很多不可見進程在運行,他們被保存在LRU (least recently used) 列表中,以便內存不足的時候被第一時間回收。如果一個activity正 確的執行了它的生命周期,關閉這個進程對於用戶體驗沒有太大的影響。

l  空進程

未運行任何程序組件。運行這些進程的唯一原因是作為一個緩存,縮短下次程序需要重新使用的啟動時間。系統經常中止這些進程,這樣可以調節程序緩存和系統緩 存的平衡。

Android 對進程的重要性評級的時候,選取它最高的級別。另外,當被另外的一個進程依賴的時候,某個進程的級別可能會增高。一個為其他進程服務的進程永遠不會比被服 務的進程重要級低。因為服務進程比後台activity進程重 要級高,因此一個要進行耗時工作的activity最好啟動一 個service來做這個工作,而不是開啟一個子進程――特別 是這個操作需要的時間比activity存在的時間還要長的時 候。例如,在後台播放音樂,向網上上傳攝像頭拍到的圖片,使用service可 以使進程最少獲取到“服務進程”級別的重要級,而不用考慮activity目 前是什麼狀態。broadcast receivers做費時的工作的時候,也應該啟用一個服務而不是開一個線程。

2單線程模型
    當一個程序第一次啟動時,Android會同時啟動一個對應的 主線程(Main Thread),主線程主要負責處理與UI相關的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,並把相關的事件分發到對應的組件進行處理。所以主線程通常又被叫做UI線 程。在開發Android應用時必須遵守單線程模型的原則: Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行。

    單線程模型會在沒有考慮到它的影響的情況下引起Android應用程序性能低下,因為 所有的任務都在同一個線程中執行,如果執行一些耗時的操作,如訪問網絡或查詢數據庫,會阻塞整個用戶界面。當在執行一些耗時的操作的時候,不能及時地分發 事件,包括用戶界面重繪事件。從用戶的角度來看,應用程序看上去像掛掉了。更糟糕的是,如果阻塞應用程序的時間過長(5秒鐘)Android會向用戶提示一些信息,即打開一個“應用程序沒有相應(application not responding)”ANR 的對話框。

   其實單線程模型就是默認情況下android把所有操作都放在主線程也就是UI Thread線程中來執行 如果你想 O上邊那段不是說它會阻塞用戶界面嘛  那我可以另起一個線程來執行一些操作 沒錯你的想法非常good 。很給力。那麼接下來 你就會嘗試另起一個線程來 執行一些操作。OK 結果就有兩種可能 一:你在另外開啟的那個線程中執行了一些後台的操作 比如開啟一個服務啊。神馬的。那麼恭喜你 你成功了。 二:第二種可能結果就是 你會收到一個華麗的異常 。這個例子很簡單

下面我們就通過一個小例子來說明這個華麗的異常時怎麼回事? 因為本篇文章的例子比較多所以我為了大家好找 給例子起了名稱 這個例子的名稱是: 異常測試
布局文件
 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
	android:id="@+id/textview01" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
<Button
	android:id="@+id/myButton"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/textview01"
	android:text="異常測試"
	/>
<TextView
	android:id="@+id/myTextView"
	android:textSize="15pt"
	android:layout_toRightOf="@id/myButton"
	android:layout_alignTop="@id/myButton"
	android:textColor="#FF0000"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>
	
</RelativeLayout>



Activity01 類
 

package xiaohang.zhimeng;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class Activity01 extends Activity {
	private Button myButton;
	private TextView myTextView;

	@Override
	protected 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 MyButtonListener());
	}

	class MyButtonListener implements OnClickListener {
		@Override
		public void onClick(View v) {
			new Thread() {
				@Override
				public void run() {
					// 我們在這裡更新了UI 設置了TextView的值
					myTextView.setText("張三");
				}
			}.start();
		}
	}
}



接下來我們以看圖片的方式 看看這是怎樣的一個異常。請大家點擊圖片查看大圖





android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 就是這樣一個異常 這個異常告訴我們不可以再子線程中更新UI元素 比如我們上邊那個例子設置了一個TextView的值。所有與UI相關的操作都不可以在子線程中執行都必須在UI線程中執行。這個異常大家以後可能會經常遇到多加注意就是了。
  
    我們繼續,下面我們說說線程交互方面的東西。就比如我們想做點這些事情上邊不是說了不能在子線程中更新UI 那比如我想在子線程中定義 一個字符串 然後通過發消息的方式 Message 把這個字符串 發送給主線程也就是UI線程 讓UI線程來 設置這個TextView的值為我們剛剛在子線程中定義的字符串。 或者我想在子線程中開啟音樂服務,或者把它停止該怎麼做呢?  要知道怎麼做先讓我們了解 一下這幾個對象吧!它們分別是

MessageQueue        Handler             Looper   下面對它們進行詳細說明


Message Queue

    在單線程模型下,為 了解決類似的問題,Android設 計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue並結合Handler和Looper組 件進行信息交換。
l  Message Queue

     Message Queue是一個消息隊列,用來存放通過Handler發 布的消息。消息隊列通常附屬於某一個創建它的線程,可以通過Looper.myQueue()得 到當前線程的消息隊列。如果沒有消息隊列對象則會拋出空指針異常 。Android在 第一次啟動程序時會默認會為UI thread創建一個關聯的消息隊列,用來管理程序的一些上層組件,activities,broadcast receivers 等等。你可以在自己的子線程中創建Handler與UI thread通訊。也就是說我們程序一啟動我們的UI線程也就是主線程就會有一個消息隊列 ,而如果我們自己另外開啟的一個子線程就不會有MessageQueue(消息隊列)對象。這一點大家務必知道。

l  Handler

    通過Handler你 可以發布或者處理一個消息或者是一個Runnable的 實例。每個Handler都 會與唯一的一個線程以及該線程的消息隊列關聯。當你創建一個新的Handler時候,默認情況下,它將關聯到創建它的這個線程和該線程的消息隊列。也就是說,如果你通過Handler發 布消息的話,消息將只會發送到與它關聯的這個消息隊列,當然也只能處理該消息隊列中的消息。這裡大家就得理解一下了 也就是說我們 一個Handler對應一個線程以及附屬於該線程的消息隊列。就比如 我們現在有一個Handler對象這個Handler在UI線程中創建 叫xh_Handler 那麼我們根據上邊的說明 應該可以告訴我 這個xh_Handler是和那個線程關聯的?  如果我用這個Handler發消息的話它將發給那個線程的消息隊列? 如果你知道了 我想一般情況下你肯定是知道的。說明你很懂。我們繼續。這時我們用這個Handler對象發送消息
Handler對象也可以發送消息哦,它有發送消息的方法。下面就說到了 待會大家看示例程序也會發現 主要就是看它怎麼使用。

Handler的主要的方法有:

1)   public final boolean sendMessage(Message msg)

把消息放入該Handler所 關聯的消息隊列,放置在消息隊列尾部。
這裡請允許我多說一句就是 我們把消息放進去的一端 消息不會阻塞但是處理消息的一端就有可能會阻塞。歡迎收看 看圖頻道。


2)   public void handleMessage(Message msg)

    關聯該消息隊列的線 程將通過調用Handler的handleMessage方 法來接收和處理消息,通常需要子類化Handler來 實現handleMessage。

l  Looper

Looper扮演著一個Handler和 消息隊列之間通訊橋梁的角色。程序組件首先通過Handler把 消息傳遞給Looper,Looper把 消息放入隊列。Looper也 把消息隊列裡的消息廣播給所有的Handler,Handler接 受到消息後調用handleMessage進 行處理。

1)   可以通過Looper類 的靜態方法Looper.myLooper得 到當前線程的Looper實 例,如果當前線程未關聯一個Looper實 例,該方法將返回空(null)它不會拋空指針異常。

2)   可以通過靜態方法Looper. getMainLooper方法得到主線程的Looper實 例 這裡需要注意一下 主線程默認是有一個Looper對象的。但是我們自己定義的子線程沒有的。那麼我們怎麼在子線程中得到Looper對象呢?如果一個線程中調用Looper.prepare(),那麼系統就會自動的為該線程建立一個消息隊列,然後調用 Looper.loop();之後就進入了消息循環,這個之後就可以發消息、取消息、和處理消息。這個如何發送消息和如何處理消息可以再其他的線程中通過 Handle來做,但前提是我們的Hanle知道這個子線程的Looper,但是你如果不是在子線程運行 Looper.myLooper(),一般是得不到子線程的looper的。

好了示例演示時間到了。示例名稱:線程交互

  這個例子主要向大家說明線程之間是如何進行交互的。請大家靜下心來好好看看。如果你也是android新手的話。先說一下我們具體要實現什麼功能,一共有三個按鈕 第一個按鈕用來設置一個 TextView上顯示的值。當我們第一次點擊它的時候它就會把值顯示在TextView當我們在一點一下 就會把這個值清空。 什麼也不顯示了。這個按鈕的 的背景圖片我做了點處理。就是默認情況下這個按鈕一張圖片,按下按鈕又會換成另外一張圖片,松開按鈕又會換一張圖片。這是怎麼做到的說完這個例子會給大家解釋。至於線程之間是怎麼傳遞消息的 大家就看代碼吧。 另外2個按鈕時用來 開啟和關閉音樂服務的 ,注意當我們點擊開始按鈕的時候 就會開始播放音樂 並且把歌曲的名稱顯示在一個TextView上。當我們點擊關閉按鈕的時候就會停止音樂的播放,好了廢話不多說了大家看代碼吧。
  O 對了 差點忘記了我們應該先收看 看圖頻道之運行效果。







  好了下邊我們來看布局文件 一共有2個布局文件 一個是用來定義我們的布局我這裡用的是相對布局 RelativeLayout 下邊是main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:background="@drawable/bg0"
    android:layout_height="fill_parent">
<TextView 
	android:id="@+id/textview01" 
	android:textColor="#FF0000"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
<Button
	android:id="@+id/myButton"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/textview01"
	android:background="@drawable/newbtn"
	/>
<TextView
	android:id="@+id/myTextView"
	android:textSize="15pt"
	android:layout_toRightOf="@id/myButton"
	android:layout_alignTop="@id/myButton"
	android:textColor="#FF0000"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>

<Button
	android:id="@+id/myButton01"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/myButton"
	android:text="開始"
	/>
<Button
	android:id="@+id/myButton02"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_toRightOf="@id/myButton01"
	android:layout_marginLeft="10px"
	android:layout_alignTop="@id/myButton01" 
	android:text="停止"
	/>
<TextView
	android:id="@+id/textview2"
	android:textSize="15pt"
	android:layout_below="@id/myTextView"
	android:layout_toRightOf="@id/myButton02"
	android:layout_alignTop="@id/myButton02"
	android:textColor="#385E0F"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>
</RelativeLayout>



    那麼另外一個xml文件就是用來定義程序的第一個按鈕的圖片轉換效果。這個怎麼實現等這個例子講解完了,會給大家做一個簡單的說明。這個文件的名字是 newbtn.xml

 

 <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
	android:drawable="@drawable/im1"/>		
<item android:state_focused="true"
	android:drawable="@drawable/im2"/>
<item android:drawable="@drawable/im3"/>
</selector>



   下面就是這2個類了 先來看看我們的音樂服務類 MusicService這個類比較簡單。
 

package xiaohang.zhimeng;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;

public class MusicService extends Service{
	
	//MediaPlayer對象
	private MediaPlayer player;
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
	public void onStart(Intent intent, int startId){
		super.onStart(intent, startId);
		//這裡可以理解為裝載音樂文件
		player = MediaPlayer.create(this, R.raw.test);
		//開始播放
		player.start();
	}
	
	public void onDestroy(){
		super.onDestroy();
		//停止音樂-停止Service
		player.stop();
	}
}



  在來看我們實現線程交互的這個Activity02類 看看它是怎麼完成 子線程與UI線程的交互的
 

package xiaohang.zhimeng;

import android.app.Activity;
import android.content.Intent;
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 Activity02 extends Activity {
	private Button myButton;//這個按鈕就是我們的第一個按鈕就是實現了圖片變換的那個按鈕
	private Button myButton01;//這個按鈕是用來開啟音樂服務的
	private Button myButton02;//這個按鈕用來停止音樂服務
	//myTextView 由myButton控制這個TextView用來顯示"android小子"  textView由myButton01控制這個TextView用來顯示音樂名稱
	private TextView myTextView, textView;
	
	// 得到主線程的Looper對象
	Looper looper = Looper.myLooper();
	// 這個myHandler由主線程創建所以它與主線程關聯
	MyHandler myHandler = new MyHandler(looper);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//設置我們的布局文件
		setContentView(R.layout.main);
		//得到我們的Button 按鈕對象 和 TextView對象
		myButton02 = (Button) findViewById(R.id.myButton02);

		myButton = (Button) findViewById(R.id.myButton);
		myTextView = (TextView) findViewById(R.id.myTextView);

		myButton01 = (Button) findViewById(R.id.myButton01);
		textView = (TextView) findViewById(R.id.textview2);

		myButton.setOnClickListener(new MyButtonListener());
		myButton02.setOnClickListener(new MyButton02Listener());
		//給我們的myButton按鈕設置監聽器
		myButton01.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick(View v) {
				new Thread() {
					@Override
					public void run() {
						System.out.println("the thread id is "
								+ Thread.currentThread().getId());
						System.out.println("the thread name is"
								+ Thread.currentThread().getName());
						//獲取我們資源文件裡邊的音樂名稱 因為連路徑也獲取了 所以這裡截取了一下
						String tmusicName = getResources().getString(
								R.raw.music_name);
						int b = tmusicName.lastIndexOf("/");
						int e = tmusicName.lastIndexOf(".");
						String musicName = tmusicName.substring(b + 1, e);
						// String musicName =
						// getResources().getResourceName(R.raw.test);
					    /**	定義消息對象 並且賦予內容 最後發送給UI線程
						  *這裡這個方法要說明一下 myHandler.obtainMessage()-->obtainMessage這個方法用來獲得消息對象
						  *但是這個消息對象會關聯調用它的Handler對象。所以就會關聯myHandler對象 而這個myHandler對象是在UI線程裡邊創建的
						  * 所以很顯然我們這消息會發給主線程的消息隊列 我想這對大家來說根本沒有任何難度
						  */
						Message message = myHandler.obtainMessage();
						message.obj = musicName;
						//這裡這個 arg1 是Message對象攜帶的參數我主要用它來區分消息對象(Message)
						message.arg1 = 2;
						//把消息發送給目標對象,目標對象就是 myHandler 就是關聯我們得到的那個消息對象的Handler
						message.sendToTarget();
						//開啟音樂服務
						startService(new Intent(
								"xiaohang.zhimeng.Android.MUSIC"));
					}
					//啟動線程
				}.start();
			}
		});
	}

	// myButton02按鈕的監聽器
	class MyButton02Listener implements OnClickListener {
		@Override
		public void onClick(View v) {
			new Thread() {
				@Override
				public void run() {
					System.out.println("this thread id is "
							+ Thread.currentThread().getId());
					System.out.println("this thread name is "
							+ Thread.currentThread().getName());
					// 停止Service
					stopService(new Intent("xiaohang.zhimeng.Android.MUSIC"));
				}
			}.start();
		}
	}

	// myButton監聽器 這個按鈕用來設置 myTextView 的值 顯示 或 隱藏 有2種狀態
	class MyButtonListener implements OnClickListener {
		@Override
		public void onClick(View v) {
			CharSequence test_View = myTextView.getText();
			String str = test_View.toString();
			// 當myTextView上邊顯示的文本不等於空的時候
			if (!(str.trim().equals(""))) {
				String str1 = "";
				CharSequence charsq = str1;
				myTextView.setText(charsq);
			} else {
				Runnable r = new Runnable() {
					public void run() {
						Message message = myHandler.obtainMessage();
						message.arg1 = 1;
						message.obj = "android小子";
						message.sendToTarget();
						System.out.println("name is "
								+ Thread.currentThread().getName());
						System.out.println("id is "
								+ Thread.currentThread().getId());
					}
				};
				Thread thread = new Thread(r);
				thread.start();
			}
		}
	}
	//MyHandler繼承Handler類 用過覆寫handlerMessage方法 來處理發給附屬於UI線程的消息隊列的消息
	class MyHandler extends Handler {
		public MyHandler() {}
		public MyHandler(Looper looper) {
			super(looper);
		}
		@Override
		public void handleMessage(Message msg) {
			if (msg.arg1 == 1) {
				CharSequence xh_msg = (CharSequence) msg.obj;
				myTextView.setText(xh_msg);
			} else if (msg.arg1 == 2) {
				CharSequence xh2_msg = (CharSequence) msg.obj;
				textView.setText(xh2_msg);
			}
		}
	}
}


  例子的運行效果大家就下載源碼自行測試吧。
 
  下面說說我們上邊的那個按鈕怎麼設置那種圖片變換的效果。其實很簡單的,因為我們都知道按鈕有 三種狀態 分別是 當我們的應用程序啟動的時候 有一種狀態默認狀態。 還有就是當我們按下按鈕的時候一種狀態, 最後一種狀態就是 當我們松開這個按鈕的時候也是一種狀態。所有一共有三種狀態。 我們要實現這種效果無非就是 當不同狀態的時候去引用不同的圖片資源。 來實現這種效果。
這裡我找個一個文檔來說明這種配置文件的一些屬性 以及用法。E文的,小的英文灰常之差勁啊。看著太太給力了。基本不懂。
http://idunnolol.com/android/drawables.html#selector
至於到底該怎麼配置和設置大家看例子就是了 這裡就不過多解釋了。
  
   累了嗎?可以去喝喝茶。回來我們繼續。下面我們 來看看人人皆知的AsyncTasks類 Async就是異步的意思,然後Task是任務的意思。翻譯過來就是 異步任務了。首先我要告訴你它是用來搗鼓線程的。 這是你必須要一定要產生一個疑問就是 這個類的處理方式和我們上邊寫的那種 用 Hadler Looper MessageQueue 處理的方式有什麼不同??? 好了這個類我們是必須會。必須會用。必須地。

       其實平常一般都是使用AsyncTask的,而並非Thread和Handler去更新UI,這裡說下它們到底有什麼區別,我們平時應該使用哪種解決方案。從Android 1.5開始系統將AsyncTask引入到android.os包中,過去在很早1.1和1.0 SDK時其實官方將其命名為UserTask,其內部是JDK 1.5開始新增的concurrent庫,做過J2EE的網友可能明白並發庫效率和強大性,比Java原始的Thread更靈活和強大,但對於輕量級的使用更為占用系統資源。Thread是Java早期為實現多線程而設計的,比較簡單不支持concurrent中很多特性在同步和線程池類中需要自己去實現很多的東西,對於分布式應用來說更需要自己寫調度代碼,而為了Android UI的刷新Google引入了Handler和Looper機制,它們均基於消息實現,有時可能消息隊列阻塞或其他原因無法准確的使用。

   推薦大家使用AsyncTask代替Thread+Handler的方式,不僅調用上更為簡單,經過實測更可靠一些,Google在Browser中大量使用了異步任務作為處理耗時的I/O操作,比如下載文件、讀寫數據庫等等,它們在本質上都離不開消息,但是AsyncTask相比Thread加Handler更為可靠,更易於維護,但AsyncTask缺點也是有的比如一旦線程開啟即dobackground方法執行後無法給線程發送消息,僅能通過預先設置好的標記來控制邏輯,當然可以通過線程的掛起等待標志位的改變來通訊,對於某些應用Thread和Handler以及Looper可能更靈活。
     那麼先讓我們看看要使用AsyncTask 類首先要做哪些工作?
      1)  子類化AsyncTask
      2)  實現AsyncTask中定義的下面一個或幾個方法
         onPreExecute() 開始執行前的准備工作;
         doInBackground(Params...) 開始執行後台處理,可以調用publishProgress方法來更新實時的任務進度;
         onProgressUpdate(Progress...)  在publishProgress方法被調用後,UI thread將調用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。
         onPostExecute(Result) 執行完成後的操作,傳送結果給UI 線程。

         這4個方法都不能手動調用。而且除了doInBackground(Params...)方法,其余3個方法都是被UI線程所調用的,所以要求:
        1) AsyncTask的實例必須在UI thread中創建;
        2) AsyncTask.execute方法必須在UI thread中調用;
       
    同時要注意:該task只能被執行一次,否則多次調用時將會出現異常。而且是不能手動停止的,這一點要注意,看是否符合你的需求!

    在使用過程中,發現AsyncTask的構造函數的參數設置需要看明白:AsyncTask<Params, Progress, Result>
    Params對應doInBackground(Params...)的參數類型。而new AsyncTask().execute(Params... params),就是傳進來的Params數據,你可以execute(data)來傳送一個數據,或者execute(data1, data2, data3)這樣多個數據。
    Progress對應onProgressUpdate(Progress...)的參數類型;
    Result對應onPostExecute(Result)的參數類型。
    當以上的參數類型都不需要指明某個時,則使用Void,注意不是void。

     說到這裡就得多說幾句 你看AsyncTask 你就會發現這個類是個泛型類 這個類是這樣定義的。
 

public abstract class AsyncTask<Params, Progress, Result> {}


在把execute方法的源碼 貼上來供大家參考 。
 

 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        sExecutor.execute(mFuture);

        return this;
    }



      這個類判斷哪裡我也不知道它在做什麼沒有繼續往下深入的研究 內力不足。 顯然這個類返回一個AsyncTask實例。 這裡我要提醒大家的的是 onPreExecute();方法  就是告知大家這個前置方法是什麼時候執行的 當執行這個方法的時候 AsyncTask的工作流程就開始了。這裡我沒有用到這個方法就沒有重寫了。但是大家一定要知道有這麼一個方法 ,它可以用來做一些准備和初始化的工作。剛才說到它的工作流程其實就是 這幾個方法的執行順序我就以圖片的形式告知大家。 讓大家明白它們相互之間的執行順序,這裡一定一定 要搞清楚。又得點擊大圖查看圖片了。



     好了我們來看例子 我們這個例子 與上一個例子實現的效果是一樣的所以這裡就不貼運行效果了。其實 是稍微有些不同的 有2點不同 上邊那個例子 我們的第一個按鈕 第一個按鈕就是最上邊的那個按鈕 剛開始是沒有顯示TextView 我們點擊第一個按鈕就會顯示TextView並且會開啟一個線程來執行 當我們再點擊的時候 TextView沒有值了 但是我們 這次點擊並不會另外開啟一個線程。 而我們這個例子會。第一個例子(線程交互) 就是顯示TextView值的時候就會開啟一個線程。當我們把TextView的值 置空的時候不會開啟另外一個線程來執行。
第二點區別就是 我們這個例子 點擊停止按鈕之後 我們的音樂名也消失。其實也是把TextView顯示的值 置空了。 下面我們就來看看這個例子 因為布局都是一樣的 我在這裡就只貼出了2個類。 大家可以下載源碼進行測試。

示例名稱 :線程交互AsyncTask版
  MusicServie類 和 上一個例子一樣的
 

package xiaohang.zhimeng;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;

public class MusicService extends Service{
	
	//MediaPlayer對象
	private MediaPlayer player;
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
	public void onStart(Intent intent, int startId){
		super.onStart(intent, startId);
		//這裡可以理解為裝載音樂文件
		player = MediaPlayer.create(this, R.raw.test);
		//開始播放
		player.start();
	}
	
	public void onDestroy(){
		super.onDestroy();
		//停止音樂-停止Service
		player.stop();
	}
}



Activity02類
 

package xiaohang.zhimeng;


import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class Activity02 extends Activity {
	public static  String ARGS1;
	private Button myButton;
	private Button myButton01;
	private Button myButton02;
	private TextView myTextView, textView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		myTextView = (TextView) findViewById(R.id.myTextView);
		textView = (TextView) findViewById(R.id.textview2);

		myButton01 = (Button) findViewById(R.id.myButton01);
		myButton02 = (Button) findViewById(R.id.myButton02);
		myButton = (Button) findViewById(R.id.myButton);
		myButton.setOnClickListener(new MyButtonListener());
		myButton02.setOnClickListener(new MyButton02Listener());
		myButton01.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick(View v) {
				String arg2 = Activity02.ARGS1 = "2";
				new task().execute(arg2);
			}
		});
	}

	// myButton02按鈕的監聽器
	class MyButton02Listener implements OnClickListener {
		@Override
		public void onClick(View v) {
				String arg3 = Activity02.ARGS1 = "3";
			    new task().execute(arg3);
		}
	}
	//此類繼承AsyncTask
	private class task extends AsyncTask<String,String, String>{
		//開啟另外一個線程執行任務
		@Override
		protected String doInBackground(String...params) {
			System.out.println("the thread ID is " + Thread.currentThread().getId());
			System.out.println("the thread NAME is " + Thread.currentThread().getName());
			String test = params[0];
			System.out.println("test value is " + test);
			if (params[0] == "1") {
				String arg1 = params[0];
				return arg1;
			}else if(params[0] == "2"){
				//開啟音樂服務
				startService(new Intent(
				"xiaohang.zhimeng.Android.MUSIC"));
				//獲取參數值
				String arg2 = params[0];
				return arg2;
			}else if (params[0] == "3") {
				//停止音樂服務
				stopService(new Intent("xiaohang.zhimeng.Android.MUSIC"));
				//獲取參數值
				String arg3 = params[0];
				return arg3;
			}else {
				System.out.println("param post error---->");				
			}
			return null;
		}
		
		//執行完成後傳送結果給UI線程 此方法最後執行
		protected void onPostExecute(String result) {
			CharSequence test_View = myTextView.getText();
			String str = test_View.toString();
			// 當不等於空的時候
			if (!(str.trim().equals(""))) {
				String str1 = "";
				CharSequence charsq = str1;
				myTextView.setText(charsq);
			} else {
				if (result == "1") {
					//將 myTextView 賦值成 "android小子"
					String string = "android小子";
					myTextView.setText(string);
				}else if(result == "2"){//result等於2說明是myButton01那個按鈕也就是開啟音樂服務的那個開始按鈕
					//獲取資源文件裡邊的音樂名稱
					String tmusicName = getResources().getString(R.raw.test);
					int b = tmusicName.lastIndexOf("/");
					int e = tmusicName.lastIndexOf(".");
					//截取歌曲名稱
					String music_Name = tmusicName.substring(b+1, e);
					System.out.println("music name is " + music_Name);
					// 在textView上顯示歌曲名稱
					textView.setText(music_Name);
				}else if(result == "3"){
					String str_none = "";
					CharSequence csq = str_none;
					textView.setText(csq);
				}else {
					System.out.println("param post error");
				}
			}
		}
	}
	
	// myButton監聽器 這個按鈕用來 顯示和隱藏 值為"android小子"這個TextView
	public class MyButtonListener implements OnClickListener {
		@Override
		public void onClick(View v) {
			System.out.println("wo zhen de zhixing fou???");
			//給靜態變量賦值
			String arg1 = Activity02.ARGS1 = "1";
		    new task().execute(arg1);
		}
	}
}


  運行效果大家就得下載源碼運行了,程序有裡邊System.out 的輸出我用來查看一些操作是在那些線程中執行的。大家可以自行過濾。我配置的System.out如下圖所示
 
  
   下邊也是一個AsyncTask的使用示例 來自eoe  示例名稱:AsyncTask_eoe
   只有一個類,類名testAsync
 

package xiaohang.zhimeng;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.widget.TextView;
import android.widget.Toast;

/**
 * 一個使用異步任務的例子。一般來說一個異步任務只執行一次,這個例子有點非主流,任務結束後會觸發下一次任務執行。
 * 由任務task在屏幕上打印數字,第一次任務執行由主Activity的onCreate觸發,每次任務結束後
 * 設定下一次觸發的時間,共執行5次。對於任務來說doInBackground()接收任務的參數params,並執行產生數字的動作,每一個數字
 * 產生後調用一次publishProgress()來更新UI,這個函數本身也是異步的只是用來發個消息調用完成後立即返回,
 * 而產生數字的動作在繼續進行。更新界面的操作在onProgressUpdate()中設定。 所有的on函數都由系統調用,不能用戶調用。
 * 代碼中使用Handler是為了能觸發任務執行,android規定這種異步任務每次執行完就結束,若要重新執行需要new一個新的。
 * 異步任務只能在UI線程裡面創建和執行
 */
public class testAsync extends Activity {
	private final int MSG_TIMER = 10000;
	private TextView vText = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test);
		vText = (TextView) findViewById(R.id.TextView01);
		vText.setText("Num...");

		new task().execute("->");

	}

	// 接收任務task發來的消息,觸發一個新的任務
	private final Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			System.out.println("Handler name -----------> " + Thread.currentThread().getName());
			System.out.println("Handler id ------------> " + Thread.currentThread().getId());
			switch (msg.what) {
			case MSG_TIMER:
				new task().execute("->");
				break;
			}
		}
	};

	// 任務執行次數
	private static int times = 1;

	// AsyncTask<>的參數類型由用戶設定,這裡設為三個String
	// 第一個String代表輸入到任務的參數類型,也即是doInBackground()的參數類型
	// 第二個String代表處理過程中的參數類型,也就是doInBackground()執行過程中的產出參數類型,通過publishProgress()發消息
	// 傳遞給onProgressUpdate()一般用來更新界面
	// 第三個String代表任務結束的產出類型,也就是doInBackground()的返回值類型,和onPostExecute()的參數類型
	private class task extends AsyncTask<String, String, String> {
		
		// 後台執行的耗時任務,接收參數並返回結果
		// 當onPostExecute()執行完,在後台線程中被系統調用
		@Override
		protected String doInBackground(String... params) {
			System.out.println("doInBackground name -----> " + Thread.currentThread().getName());
			System.out.println("doInBackground  id -----> " + Thread.currentThread().getId());
			// TODO Auto-generated method stub
			// 在這裡產生數據,送給onProgressUpdate以更新界面
			String pre = params[0];
			System.out.println("pre is ----->" + pre);

			for (int i = 0; i < 5; i++) {
				System.out.println("note i am begin sleep ");
				publishProgress(pre + i);
					
				
				// 這裡是否需要停頓下
				System.out.println("hua li de bu zhuo " + pre + i);
				SystemClock.sleep(1000);
			}

			return "任務結束";
		}

		// 任務執行結束後,在UI線程中被系統調用
		// 一般用來顯示任務已經執行結束
		@Override
		protected void onPostExecute(String result) {
			// TODO Auto-generated method stub
			System.out.println("onPostExecute name --------> " + Thread.currentThread().getName());
			System.out.println("onPostExecute id --------> " + Thread.currentThread().getName());
			super.onPostExecute(result);

			Toast.makeText(testAsync.this, result, Toast.LENGTH_SHORT).show();

			// 任務執行5次後推出
			if (times > 5) {
				return;
			}

			// 設定下一次任務觸發時間
			Message msg = Message.obtain();
			msg.what = MSG_TIMER; 
			handler.sendMessageDelayed(msg, 10000L);
		}

		// 最先執行,在UI線程中被系統調用
		// 一般用來在UI中產生一個進度條
		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			System.out.println("onPreExecute id -------> " + Thread.currentThread().getId());
			System.out.println("onPreExecute name -------> " + Thread.currentThread().getName() );
			super.onPreExecute();
			Toast.makeText(testAsync.this, "開始執行第" + times + "次任務: " + this,
					Toast.LENGTH_SHORT).show();
			times++;
		}

		// 更新界面操作,在收到更新消息後,在UI線程中被系統調用
		@Override
		protected void onProgressUpdate(String... values) {
			// TODO Auto-generated method stub
			System.out.println("onProgressUpdate id ---------> " + Thread.currentThread().getId());
			System.out.println("onProgressUpdate name -------> " + Thread.currentThread().getName());
			super.onProgressUpdate(values);
			vText.append(values[0]);
		}

	}

}


   這個例子來自eoe大家可以好好看看 每個回調方法它都用到了不錯的。還有在這裡建議大家去看看泛型的東西 我以前不會 也看了看。不然 自己用AsyncTask類的時候 那幾個方法的參數 會有些暈。我現在還有些暈,有時間一定要好好研究一下。然後接下來的就是多多練習了。

  下面這例子也是線程交互的一個例子和上邊的稍有不同大家看看吧,在這個例子中子線程和主線程交互 傳送數據 是通過Bundle對象的方式。在這個例子中大家可以學會怎麼用Bundle對象。
  示例名稱:線程交互_Bundle版
  只有一個類HandleTest2
 

package mars.handler;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

public class HandlerTest2 extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 打印了當前線程的ID
		System.out.println("Activity-->" + Thread.currentThread().getId());
		// HandlerThread這個類繼承了Thread
		// 生成一個HandlerThread對象,實現了使用Looper來處理消息隊列的功能,這個類由Android應用程序框架提供
		// 這裡這個參數指定的是線程的名字
		HandlerThread handlerThread = new HandlerThread("handler_thread");
		// 在使用HandlerThread的getLooper()方法之前,必須先調用該類的start();
		handlerThread.start();// 啟動這個線程
		MyHandler myHandler = new MyHandler(handlerThread.getLooper());
		// 這裡這個obtainMessage()方法返回
		Message msg = myHandler.obtainMessage();
		// 將msg發送到目標對象,所謂的目標對象,就是生成該msg對象的handler對象
		Bundle b = new Bundle();
		b.putInt("age", 20);
		b.putString("name", "Jhon");
		msg.setData(b);
		msg.sendToTarget();
	}

	class MyHandler extends Handler {
		public MyHandler() {

		}

		public MyHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			// 這裡我們只做了簡單的輸出
			Bundle b = msg.getData();
			int age = b.getInt("age");
			String name = b.getString("name");
			System.out.println("age is " + age + ", name is" + name);
			System.out.println("Handler--->" + Thread.currentThread().getId());
			System.out.println("handlerMessage");
		}
	}
}



  布局文件 啥都沒有就一個TextView 為了 維持完成也貼出來吧。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
</LinearLayout> 

  這裡有一些我學習的時候在網上參考的文章,大家也可以看看 ,重要的就是多練習了。
  淺析Android線程模型http://www.cppblog.com/fwxjj/archive/2010/05/31/116787.html
   Android線程模型(Painless Threading)http://www.cppblog.com/fwxjj/archive/2010/05/31/116788.html
   有關Android線程的學習 http://android.blog.51cto.com/268543/343823
   Android中Message機制的靈活應用http://qaohao.iteye.com/blog/509145
    http://wghjay.iteye.com/blog/427086http://wghjay.iteye.com/blog/427086
    這些我認為寫的還不錯,大家 也可以看看 多多學習總是好事。

  下邊羅列我們源碼的下載地址 因為線程交互的那2個例子裡邊有一些資源文件所以會比較大eye傳不上來 ,我傳到趣盤了給大家下載。
   xh_Android_Test_1_2_線程交互     下載地址 趣盤下載 http://hangvip.qupan.cc/6789636.html
    XH_Android_Test_1_2_SyncTask_Demo_線程交互_AsyncTask版   http://hangvip.qupan.cc/6789683.html 
    Android_Test_01_AsyncTask_eoe       附件下載
    建議大家用IE下載直接把鏈接復制到IE下載 然後點迅雷下載 如果沒安裝迅雷 會彈出普通的下載方式,而火狐 貌似 只能用迅雷下了 還有趣盤有很多廣告很惡心,大家忍住。
    所有例子的測試平台 均為 android2.0  APILEVEL 5  下載鏈接已測試可用 ,如果大家發現不可以下載可以留言給我。

 

  •  

  • Android_Test_01_AsyncTask_eoe.rar
  • HandlerTest2_線程交互_Bundle版.rar
  • xh_Android_Test_異常測試.rar
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved