深入理解異步任務AsyncTask

                                                           異步任務AsyncTask

        1、Android 的UI線程主要負責處理用戶的按鍵事件、用戶觸屏事件及品目繪圖事件等,不能處理耗時操作,否則UI界面會停止響應。(Android UI想成超過20秒就會出現ANR,但是讓用戶等6秒鐘就會非常反感,所以這裏就不要記時間了)

          

        2、爲了避免失去響應,我通常是新開啓個線程去處理耗時任務,但是問題又來了,我們往往需要在執行完耗時任務時需要更新UI,例如在網上獲取個網頁,顯示在Android的UI中,這裏因爲是新的線程,就不能直接修改主線程裏的UI所以我們一種方法是通過Handler.發送消息去及時更新,Handler實例向UI線程發送消息,完成界面的更新,這種方式對於整個過程的控制比較精細,但也是有缺點的,例如代碼相對臃腫,在多個任務同時執行時,不易對線程進行精確的控制,但是還有另一種較好的方法就是用AsyncTask異步任務,此方法較爲輕量級。

        3、AsyncTask是一個抽象類,通常用於被繼承,繼承時需要制定三個範型參數。

                 public class AsyncTask<Params,Progress,Result>

                  1)Params:啓動任務執行的輸入參數。

                  2)Progress:後臺完成進度值的類型。

                  3)Result:後臺執行任務完成後返回結果的類型。

        4、使用步驟

               1)創建AsyncTask子類,併爲三個泛型參數指定類型。如果某個泛型參數不需要指定類型,可將它指定爲Void。

               2)根據需要實現對應的方法。提供的操作方法如下。

                           ①doInBackground(Params...):重寫該方法就是後臺線程將要完成的任務,這裏可以調用publishProgress(Progress...valuers)方法更新執行的進度。

                           ②onProgressUpdate(Progress... values):在doInBackground()方法中調用publishProgress()方法更新任務的執行進度後,將會觸發此方法,直接將進度信息更新到UI組件上。

                           ③onPreExecute():該方法將在執行後臺耗時操作前調用。因此他是完成一些初始化操作的,也就是準備工作,比如在界面上顯示進度條等等。

                           ④onPostExecute(Result result):當doInBackground()完成後,系統會自動調用onPostExecute()方法,並將doInBackground()方法的返回值傳給該方法。此方法用來將最後結果顯示的UI中

                            ⑤onCancelled()方法:當task調用cancel(True)時,被調用,是編寫取消任務對應UI的更改操作

                      3)使用AsyncTask時必須遵守如下原則。

                           ① 必須在UI線程中創建AsyncTask的實例。

                           ②必須在Ui線程中調用AsyncTask的execute方法。

                           ④AsyncTask的onPreExecute().onPostExecute(Result result),doInBackground(Params...params),onProgressUpdate(Progress....values)方法不應該由程序員代碼調用,而是由Android系統負責調用。

                           ⑤每個AsyncTask只能被執行一次,多次調用將會引發異常,就是一個任務實例只能執行一次,如果執行第二次將會拋出異常。

                執行的順序,onPreExecute()---->>doInBackground()--->>內部調用publishProgress(Progress...values)--->>結束觸發onProgressUpdate()--->>onPostExecute()

                如果中途取消,執行順序

                                    onPreExecute()---->>doInBackground()--->>內部調用publishProgress(Progress...values)--->>結束觸發onProgressUpdate()--->onCancelled()(這個方法是程序員調用cancel時才觸發的,和其他不一樣)

               下面Demo,


                新建一個AsyncTaskTest 類,

package org.crazyit.handler;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.view.View;
import android.widget.TextView;

public class AsyncTaskTest extends Activity
{
	private TextView show;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		show = (TextView) findViewById(R.id.show);
	}

	// 重寫該方法,爲界面的按鈕提供事件響應方法
	public void download(View source) throws MalformedURLException
	{
		DownTask task = new DownTask(this);
		task.execute(new URL("http://www.crazyit.org/ethos.php"));
	}

	class DownTask extends AsyncTask<URL, Integer, String>
	{
		// 可變長的輸入參數,與AsyncTask.exucute()對應
		ProgressDialog pdialog;
		// 定義記錄已經讀取行的數量
		int hasRead = 0;
		Context mContext;

		public DownTask(Context ctx)
		{
			mContext = ctx;
		}

		@Override
		protected String doInBackground(URL... params)
		{
			StringBuilder sb = new StringBuilder();
			try
			{
				URLConnection conn = params[0].openConnection();
				// 打開conn連接對應的輸入流,並將它包裝成BufferedReader
				BufferedReader br = new BufferedReader(
					new InputStreamReader(conn.getInputStream()
					, "utf-8"));
				String line = null;
				while ((line = br.readLine()) != null)
				{
					sb.append(line + "\n");
					hasRead++; 
					publishProgress(hasRead);
				}
				return sb.toString();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
			return null;
		}

		@Override
		protected void onPostExecute(String result)
		{
			// 返回HTML頁面的內容
			show.setText(result);
			pdialog.dismiss();
		}

		@Override
		protected void onPreExecute()
		{
			pdialog = new ProgressDialog(mContext);
			// 設置對話框的標題
			pdialog.setTitle("任務正在執行中");
			// 設置對話框 顯示的內容
			pdialog.setMessage("任務正在執行中,敬請等待...");
			// 設置對話框不能用“取消”按鈕關閉
			pdialog.setCancelable(false);
			// 設置該進度條的最大進度值
			pdialog.setMax(202);
			// 設置對話框的進度條風格
			pdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			// 設置對話框的進度條是否顯示進度
			pdialog.setIndeterminate(false);
			pdialog.show();
		}

		@Override
		protected void onProgressUpdate(Integer... values)
		{
			// 更新進度
			show.setText("已經讀取了【" + values[0] + "】行!");
			pdialog.setProgress(values[0]);
		}
	}
}

                  main.xml

<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	tools:context=".AsyncTaskTest">
	
	<TextView
		android:id="@+id/show"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:textSize="14dp" />
	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_alignParentBottom="true"
		android:layout_centerHorizontal="true"
		android:text="下載"
		android:onClick="download" />
	
</RelativeLayout>


AndroidManifest.xml ,添加權限


<?xml version="1.0" encoding="utf-8"?>
<manifest
	xmlns:android="http://schemas.android.com/apk/res/android"
	package="org.crazyit.handler"
	android:versionCode="1"
	android:versionName="1.0">
	<uses-sdk
		android:minSdkVersion="10"
		android:targetSdkVersion="17" />
	<uses-permission android:name="android.permission.INTERNET"/>
	<application
		android:allowBackup="true"
		android:icon="@drawable/ic_launcher"
		android:label="@string/app_name">
		<activity
			android:name=".AsyncTaskTest"
			android:label="@string/app_name">
			<intent-filter>
				<action android:name="android.intent.action.MAIN" />
				<category android:name="android.intent.category.LAUNCHER" />
			</intent-filter>
		</activity>
	</application>
</manifest>

 

                有些朋友也許會有疑惑,AsyncTask內部是怎麼執行的呢,它執行的過程跟我們使用Handler又有什麼區別呢?答案是:AsyncTask是對Thread+Handler良好的封裝,在android.os.AsyncTask代碼裏仍然可以看到Thread和Handler的蹤跡。下面就向大家詳細介紹一下AsyncTask的執行原理。

           看看這個圖,發現 只有doInBackground()是抽象方法,其餘幾個是空方法,我們需要的時候可以選擇性的覆寫它們;publishProgress(Progress... values)是final修飾的,不能覆寫,只能去調用,我們一般會在doInBackground(Params... params)中調用此方法;


                  另外,我們可以看到有一個Status的枚舉類和getStatus()方法,Status枚舉類代碼段如下:

                          

        //初始狀態
	private volatile Status mStatus = Status.PENDING;
	
	public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

/**
     * Returns the current status of this task.
     *
     * @return The current status.
     */
    public final Status getStatus() {
        return mStatus;
    }

可以看到,AsyncTask的初始狀態爲PENDING,代表待定狀態,RUNNING代表執行狀態,FINISHED代表結束狀態,這幾種狀態在AsyncTask一次生命週期內的很多地方被使用,非常重要。

介紹完大綱視圖相關內容之後,接下來,我們會從execute(Params... params)作爲入口,重點分析一下AsyncTask的執行流程,我們來看一下execute(Params... params)方法的代碼段:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
					//如果該任務正在被執行則拋出異常
					//值得一提的是,在調用cancel取消任務後,狀態仍未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)");
            }
        }
		
		//改變狀態爲RUNNING
        mStatus = Status.RUNNING;

		//調用onPreExecute方法
        onPreExecute();

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

        return this;
    }

             代碼中涉及到三個陌生的變量:mWorker、sExecutor、mFuture,我們也會看一下他們的真面目:

關於sExecutor,它是java.util.concurrent.ThreadPoolExecutor的實例,用於管理線程的執行。代碼如下

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 10;

	//新建一個隊列用來存放線程
    private static final BlockingQueue<Runnable> sWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);
	//新建一個線程工廠
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
		//新建一個線程
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
	//新建一個線程池執行器,用於管理線程的執行
    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

mWorker實際上是AsyncTask的一個的抽象內部類的實現對象實例,它實現了Callable<Result>接口中的call()方法,代碼如下

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
而mFuture實際上是java.util.concurrent.FutureTask的實例,下面是它的FutureTask類的相關信息:

 

/**
 * A cancellable asynchronous computation.
 * ...
 */
public class FutureTask<V> implements RunnableFuture<V> 
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看到FutureTask是一個可以中途取消的用於異步計算的類。

下面是mWorker和mFuture實例在AsyncTask中的體現:

    private final WorkerRunnable<Params, Result> mWorker;
    private final FutureTask<Result> mFuture;
	
	public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            //call方法被調用後,將設置優先級爲後臺級別,然後調用AsyncTask的doInBackground方法
			public Result call() throws Exception {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return doInBackground(mParams);
            }
        };

		//在mFuture實例中,將會調用mWorker做後臺任務,完成後會調用done方法
        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();
                    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.sendToTarget();
            }
        };
    }

 我們看到上面的代碼中,mFuture實例對象的done()方法中,如果捕捉到了CancellationException類型的異常,則發送一條“MESSAGE_POST_CANCEL”的消息;如果順利執行,則發送一條“MESSAGE_POST_RESULT”的消息,而消息都與一個sHandler對象關聯。這個sHandler實例實際上是AsyncTask內部類InternalHandler的實例,而InternalHandler正是繼承了Handler,下面我們來分析一下它的代碼:


    private static final int MESSAGE_POST_RESULT = 0x1;	//顯示結果
    private static final int MESSAGE_POST_PROGRESS = 0x2;	//更新進度
    private static final int MESSAGE_POST_CANCEL = 0x3;	//取消任務

    private static final InternalHandler sHandler = new InternalHandler();
	
	private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
					//調用AsyncTask.finish方法
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //調用AsyncTask.onProgressUpdate方法
					result.mTask.onProgressUpdate(result.mData);
                    break;
                case MESSAGE_POST_CANCEL:
					//調用AsyncTask.onCancelled方法
                    result.mTask.onCancelled();
                    break;
            }
        }
    }
我們看到,在處理消息時,遇到“MESSAGE_POST_RESULT”時,它會調用AsyncTask中的finish()方法,我們來看一下finish()方法的定義:
private void finish(Result result) {
        if (isCancelled()) result = null;
        onPostExecute(result);	//調用onPostExecute顯示結果
        mStatus = Status.FINISHED;	//改變狀態爲FINISHED
 }

原來finish()方法是負責調用onPostExecute(Result result)方法顯示結果並改變任務狀態的啊。

另外,在mFuture對象的done()方法裏,構建一個消息時,這個消息包含了一個AsyncTaskResult類型的對象,然後在sHandler實例對象的handleMessage(Message msg)方法裏,

AsyncTaskResult result = (AsyncTaskResult) msg.obj;


AsyncTask的一個內部類,是用來包裝執行結果的一個類

    @SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;
        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }


總結以下,當我們調用execute(Params... params)方法後,execute方法會調用onPreExecute()方法,然後由ThreadPoolExecutor實例sExecutor執行一個FutureTask任務,這個過程中doInBackground(Params... params)將被調用,如果被開發者覆寫的doInBackground(Params... params)方法中調用了publishProgress(Progress... values)方法,則通過InternalHandler實例sHandler發送一條MESSAGE_POST_PROGRESS消息,更新進度,sHandler處理消息時onProgressUpdate(Progress... values)方法將被調用;如果遇到異常,則發送一條MESSAGE_POST_CANCEL的消息,取消任務,sHandler處理消息時onCancelled()方法將被調用;如果執行成功,則發送一條MESSAGE_POST_RESULT的消息,顯示結果,sHandler處理消息時onPostExecute(Result result)方法被調用。

經過上面的介紹,相信朋友們都已經認識到AsyncTask的本質了,它對Thread+Handler的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。








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