AsyncTask異步任務學習(詳細介紹)

    我們在實際開發中,會遇到很多耗時操作爲了解決ANR錯誤,我們一般採用子線程更新UI線程的方法,我們都知道只有在UI線程中才可以更新界面,所以耗時操作一般放在子線程中去搞。首選是Handler機制(異步消息機制),但是我們知道Handler是單線程的,我們還可以採用異步任務AsyncTask(是線程池)來解決。

android提供了一套專門用於異步處理的類。即:AynsTask類。使用這個類可以爲耗時程序開闢一個新線程進行處理,處理完時返回。

其實,AsynTask類就是對Thread類的一個封裝,並且加入了一些新的方法。編程時,兩者都可以實現同樣的功能。

要點:爲了調用關係明確及安全,AsynTask類在繼承時要傳入3個泛型。第一個泛型對應execute()向doInBackground()的傳遞類型。第二個泛型對應doInBackground()的返回類型和傳遞給onPostExecute()的類型。第三個泛型對應publishProgress()向progressUpdate()傳遞的類型。傳遞的數據都是對應類型的數組,數組都是可變長的哦。可以根據具體情況使用。

一、AsynTask類結構

asysTask類主要用到的幾個內部回調函數有:


二、回調邏輯關係


三、AsyncTask裏面幾個方法的執行順序

1、主線程調用AsynTask子類實例的execute()方法後,首先會調用onPreExecute()方法。onPreExecute()在主線程中運行,可以用來寫一些開始提示代碼。

2、之後啓動新線程,調用doInBackground()方法,進行異步數據處理。

3、處理完畢之後異步線程結束,在主線程中調用onPostExecute()方法。onPostExecute()可以進行一些結束提示處理。

補充:在doInBackground()方法異步處理的時候,如果希望通知主線程一些數據(如:處理進度)。這時,可以調用publishProgress()方法。這時,主線程會調用AsynTask子類的onProgressUpdate()方法進行處理。

4、各個函數間數據的傳遞

通過上面的調用關係,我們就可以大概看出一些數據傳遞關係。如下:

  execute()向doInBackground()傳遞。

  doInBackground()的返回值會傳遞給onPostExecute()。

publishProgress()向progressUpdate()傳遞。

 

四、範例:使用AsyncTask在網絡上下載一個圖片

一、效果圖:

以下就是從網絡上獲取百度圖片的效果圖,當點擊下載按鈕的時候就會去下載圖片


二、代碼實現

2.1、main.xml的代碼

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
    </ProgressBar>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下載圖片" >
    </Button>

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

2.3、Activity的編寫

package com.jun.asynctask;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
public class AsyncTaskActivity extends Activity {
	//定義ImageView組件
	private ImageView mImageView;
	//定義按鈕組件
	private Button mButton;
	//定義進度條組件
	private ProgressBar mProgressBar;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //取得ImageView組件
        mImageView= (ImageView) findViewById(R.id.imageView);
        //取得按鈕組件
        mButton = (Button) findViewById(R.id.button);
        //取得進度條組件
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        //給按鈕添加監聽事件
        mButton.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				//實例化GetCSDNLogoTask
				GetMeiNvLogoTasktask = new GetMeiNvLogoTask();
				//execute方法執行以後執行onPostExecute方法,此方法在UI線程中,此方法中開啓一個子線程,和UIt線程不是同一樣線程。		task.execute("http://img1.jike.com/get?name=T16vLoB_Ag1RCvBVdK");
			}
		});
    }
    //AsyncTask的子類
    class GetMeiNvLogoTaskextends AsyncTask<String,Integer,Bitmap> {//繼承AsyncTask
    	/**
    	 * 其中的三個參數
    	 * String:指的是doInBackground()方法中的參數類型  即第一個參數
    	 * Integer:指的是onProgressUpdate()方法中的參數類型 即第二個參數
    	 * Bitmap:指的是onPostExecute()的方法中的參數類型和doInBackground()方法的返回值類型 即第三個參數
    	 */
    	//處理後臺執行的任務,在後臺線程執行,主要用於進行異步操作
		@Override
		protected Bitmap doInBackground(String... params) {
			//用於發佈更新消息,每次執行引方法都將會調用onProgressUpdate(Integer... progress)方法
			publishProgress(0);
			HttpClient hc = new DefaultHttpClient();
			//用於發佈更新消息,每次執行到此方法都會調用onProgressUpdate(Integer... progress)方法
			publishProgress(10);
			HttpGet hg = new HttpGet(params[0]);//獲取csdn的logo
			//定義一個Bitmap
			final Bitmap bm;
			try {
				HttpResponse hr = hc.execute(hg);
	bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
			} catch (Exception e) {
				return null;
			}
			publishProgress(100);
			//mImageView.setImageBitmap(result); 不能在後臺線程操作ui
			return bm; //返回的值會傳入到onPostExecute()方法的參數中
		}
		
		//在doInBackground方法中每次執行publishProgress之後此方法都會被調用,在ui線程執行
		//用於在執行異步任務的過程中,對用戶進行提示,例如:控制進度條等。
		protected void onProgressUpdate(Integer... progress) {
			mProgressBar.setProgress(progress[0]);//更新進度條的進度
	     }
		
		//後臺任務(doInBackground())執行完之後被調用,在ui線程執行,
		//主要用於將異步任務執行的結果返回給客戶
	     protected void onPostExecute(Bitmap result) {
	    	 if(result != null) {
	    		 Toast.makeText(AsyncTaskActivity.this, "成功獲取圖片", Toast.LENGTH_LONG).show();
	    		 //把下載下來的圖片設置在ImageView上
	    		 mImageView.setImageBitmap(result);
	    	 }else {
	    		 Toast.makeText(AsyncTaskActivity.this, "獲取圖片失敗", Toast.LENGTH_LONG).show();
	    	 }
	     }
	     
	   //在 doInBackground(Params...)之前,execute()方法之後被調用,在ui線程執行,所以可以設置控件大小屬性等。
			//主要用於異步操作之前的UI的準備工作
	     protected void onPreExecute () {
	    	 mImageView.setImageBitmap(null);
	    	 mProgressBar.setProgress(0);//進度條復位
	     }
	     
	     protected void onCancelled () {//在ui線程執行
	    	 mProgressBar.setProgress(0);//進度條復位
	     }
    }
}

2.4、由於要訪問網絡,所以一定要進行權限的配置

<uses-permission android:name="android.permission.INTERNET"/>

以上就簡單的完成了一個使用AsyncTask從網絡下載圖片的方法

總結一下,些案例的流程:

1、當點擊按鈕時,創建一個task,並且傳入百度的美女地址(String類型參數,因此AsyncTask的第一個模板參數爲String類型)

2、UI線程執行onPreExecute(),把ImageView的圖片清空,progrssbar的進度清零。

3、後臺線程執行doInBackground(),不可以在doInBackground()操作ui,調用publishProgress(0)更新進度,此時會調用onProgressUpdate(Integer...progress)更新進度條(進度用整形表示,因此AsyncTask的第二個模板參數是Integer)。函數最後返回result(例子中是返回Bitmap類型,因此AsyncTask的第二個模板參數是Bitmap)。

4、當後臺任務執行完成後,調用onPostExecute(Result),傳入的參數是doInBackground()中返回的對象。

五、此案例中AsyncTask的實現原理

分析:

在分析實現流程之前,我們先了解一下AsyncTask有哪些成員變量。

private static final int CORE_POOL_SIZE =5;//5個核心工作線程
private static final int MAXIMUM_POOL_SIZE = 128;//最多128個工作線程
private static final int KEEP_ALIVE = 1;//空閒線程的超時時間爲1秒
private static final BlockingQueue<Runnable> sWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);//等待隊列
private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//線程池是靜態變量,所有的異步任務都會放到這個線程池的工作線程內執行。

回到例子中,點擊按鈕之後會新建一個GetCSDNLogoTask對象:

GetMeiNvLogoTasktask = new GetMeiNvLogoTask();
此時會調用父類AsyncTask的構造函數AsyncTask.java
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return doInBackground(mParams);
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            Message message;
            Result result = null;

            try {
                result = get();
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                        new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
                message.sendToTarget();//取消任務,發送MESSAGE_POST_CANCEL消息
                return;
            } catch (Throwable t) {
                throw new RuntimeException("An error occured while executing "
                        + "doInBackground()", t);
            }

            message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult<Result>(AsyncTask.this, result));//完成任務,發送MESSAGE_POST_RESULT消息並傳遞result對象
            message.sendToTarget();
        }
    };
}

WorkerRunnable類實現了callable接口的call()方法,該函數會調用我們在AsyncTask子類中實現的doInBackground(mParams)方法,由此可見,WorkerRunnable封裝了我們要執行的異步任務。FutureTask中的protectedvoid done() {}方法實現了異步任務狀態改變後的操作。當異步任務被取消,會向UI線程傳遞MESSAGE_POST_CANCEL消息,當任務成功執行,會向UI線程傳遞MESSAGE_POST_RESULT消息,並把執行結果傳遞到UI線程。

由此可知,AsyncTask在構造的時候已經定義好要異步執行的方法doInBackground(mParams)和任務狀態變化後的操作(包括失敗和成功)。

當創建完GetCSDNLogoTask對象後,執行GetCSDNLogoTask task = new GetCSDNLogoTask();

此時會調用AsyncTaskexecute(Params...params)方法

AsyncTask.java


public final AsyncTask<Params,Progress, Result> execute(Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw newIllegalStateException("Cannot execute task:"
                        + " the taskis already running.");
            case FINISHED:
                throw newIllegalStateException("Cannot execute task:"
                        + " the taskhas already been executed "
                        + "(a task canbe executed only once)");
        }
    }
    mStatus = Status.RUNNING;
    onPreExecute();//運行在ui線程,在提交任務到線程池之前執行
    mWorker.mParams = params;
    sExecutor.execute(mFuture);//提交任務到線程池
    return this;
}


當任務正在執行或者已經完成,會拋出IllegalStateException,由此可知我們不能夠重複調用execute(Params...params)方法。在提交任務到線程池之前,調用了onPreExecute()方法。然後才執行sExecutor.execute(mFuture)是任務提交到線程池。

前面我們說到,當任務的狀態發生改變時(1、執行成功2、取消執行3、進度更新),工作線程會向UI線程的Handler傳遞消息。在Android異步處理三:Handler+Looper+MessageQueue深入詳解》一文中我們提到,Handler要處理其他線程傳遞過來的消息。在AsyncTask中,InternalHandler是在UI線程上創建的,它接收來自工作線程的消息,實現代碼如下:

private static class InternalHandler extends Handler {
    @SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
     @Override
     public voidhandleMessage(Message msg) {
         AsyncTaskResult result =(AsyncTaskResult) msg.obj;
         switch (msg.what) {
             caseMESSAGE_POST_RESULT:
                 // There is onlyone result
                 result.mTask.finish(result.mData[0]);//執行任務成功
                 break;
             caseMESSAGE_POST_PROGRESS:
               result.mTask.onProgressUpdate(result.mData);//進度更新
                 break;
             caseMESSAGE_POST_CANCEL:
                 result.mTask.onCancelled();//取消任務
                 break;
         }
     }
 }

當接收到消息之後,AsyncTask會調用自身相應的回調方法。

總結:

1、 AsyncTask的本質是一個靜態的線程池,AsyncTask派生出的子類可以實現不同的異步任務,這些任務都是提交到靜態的線程池中執行。

2、線程池中的工作線程執行doInBackground(mParams)方法執行異步任務

3、當任務狀態改變之後,工作線程會向UI線程發送消息,AsyncTask內部的InternalHandler響應這些消息,並調用相關的回調函數。

六、小結

       AsyncTask內部也是採用Handler機制完成的,不過其內部使用是一個線程池。運行的核心線程爲5條,最大線程數量爲128條。

       AsyncTask是異步任務是用來處理線程阻塞問題的,AsyncTask,異步任務,它是一個抽象類,必須由子類實現,才能使用。而子類至少要重寫一個子類的方法:doInBackground(Params...)

       AsyncTask要重寫幾個常用的方法

doInBackGround()運行在子線程中
onPreExecute()運行在UI線程中
onPostExecute()運行在UI線程中
onProgressUpdate()à運行在UI線程中

這個方法的執行順序,在UI線程中實例化出AsyncTask的子類以後,調用execute()àonPreExecute()àdoInBackground()àonPostExecute()。其中只有doInBackground()是運行在子線程中,其它都是運行在主線程中的方法。

在doInBackground()方法異步處理的時候,如果希望通知主線程一些數據(如:處理進度)。這時,可以調用publishProgress()方法。這時每當調用publishProgress方法時,主線程會調用AsynTask子類的onProgressUpdate()方法進行處理。

       AsyncTask是在Android SDK 1.5之後推出的一個方便編寫後臺線程與UI線程交互的輔助類。AsyncTask的內部實現是一個線程池,每個後臺任務會提交到線程池中的線程執行,然後使用Thread+Handler的方式調用回調函數(如需深入瞭解原理請看《Android異步處理四:AsyncTask的實現原理》)。

AsyncTask抽象出後臺線程運行的五個狀態,分別是:1、準備運行,2、正在後臺運行,3、進度更新,4、完成後臺任務,5、取消任務,對於這五個階段,AsyncTask提供了五個回調函數:

1、準備運行:onPreExecute(),該回調函數在任務被執行之後立即由UI線程調用。這個步驟通常用來建立任務,在用戶接口(UI)上顯示進度條。

2、正在後臺運行:doInBackground(Params...),該回調函數由後臺線程在onPreExecute()方法執行結束後立即調用。通常在這裏執行耗時的後臺計算。計算的結果必須由該函數返回,並被傳遞到onPostExecute()中。在該函數內也可以使用publishProgress(Progress...)來發佈一個或多個進度單位(unitsof progress)。這些值將會在onProgressUpdate(Progress...)中被髮布到UI線程。

3. 進度更新:onProgressUpdate(Progress...),該函數由UI線程在publishProgress(Progress...)方法調用完後被調用。一般用於動態地顯示一個進度條。

4. 完成後臺任務:onPostExecute(Result),當後臺計算結束後調用。後臺計算的結果會被作爲參數傳遞給這一函數。

5、取消任務:onCancelled (),在調用AsyncTask的cancel()方法時調用

AsyncTask的構造函數有三個模板參數:

1.Params,傳遞給後臺任務的參數類型。

2.Progress,後臺計算執行過程中,進步單位(progress units)的類型。(就是後臺程序已經執行了百分之幾了。)

3.Result, 後臺執行返回的結果的類型。

AsyncTask並不總是需要使用上面的全部3種類型。標識不使用的類型很簡單,只需要使用Void類型即可。




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