Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android多線程開發之AsyncTask的使用

Android多線程開發之AsyncTask的使用

編輯:關於Android編程

一、AsyncTask簡介

AsyncTask是一種輕量級的異步任務類,它內部封裝了Handler和Thread,能將後台線程執行的進度和最終的結果分發到UI線程中進行處理,通過AsyncTask可以更加方便地執行後台任務。

AsyncTask並不構成一個通用的線程處理框架,理想情況下它只應該用於短時間的操作(最多幾秒鐘),若是需要保持線程運行較長一段時間的話,Google推薦我們使用java.util.concurrent包中的API,比如:Executor, ThreadPoolExecutor和FutureTask.

一個異步任務定義了三個泛型參數,分別是:Params, Progress和Result,還定義了4個方法執行的步驟,分別是:onPreExecute, doInBackground, onProgressUpdate和onPostExecute.

二、AsyncTask使用說明

AsyncTask是抽象類,必須實現它的抽象方法doInBackground(…),大多數情況下我們可能還需要實現onPostExecute(…)方法去獲取異步任務處理的結果。在使用AsyncTask類時,我們需要指定3個泛型參數,分別是:

Params:表示後台任務執行時傳入的參數的類型 Progress:表示後台任務的執行進度的類型 Result:表示後台任務的返回結果的類型

這三種參數類型並非一定要使用,若不想用它,傳一個Void就行。

private class MyTask extends AsyncTask { ... }

AsyncTask使用中經常需要重寫的四個方法分別是:

onPreExecute():執行在UI線程中,在異步任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

doInBackground(Params…):執行在後台線程中,在onPreExecute()方法執行之後調用,參數Params表示異步任務的輸入參數,該方法計算的結果Result參數會返回給onPostExecute()方法,在此方法中可以通過調用publishProgress()方法來更新任務的進度,然後傳遞到onProgressUpdate()進行顯示。

onProgressUpdate(Progress…):執行在UI線程中,publishProgress(Progress…)方法執行後調用,用於更新當前異步任務執行的進度,方法中攜帶的參數就是在doInBackground()中傳遞過來的。

onPostExecute(Result):執行在UI線程中,在異步任務執行完後調用,其中result參數是doInBackground()的返回值,可以利用該參數來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。


AsyncTask的線程規則:

AsyncTask的類必須在主線程中被加載,在4.1及以上的版本已經被系統自動完成了; AsyncTask的實例對象必須在UI中創建; AsyncTask的execute()方法必須在UI線程調用; 不要在程序中直接調用onPreExecute(), onPostExecute(), doInBackground()和onProgressUpdate()方法; 一個AsyncTask對象只能被執行一次, 即只能調用一次execute()方法, 否則會報運行時異常;

如何取消一個任務:

通過調用cancel()方法可以在任意時刻取消一個正在進行的異步任務; 調用cancel()方法後,後續調用isCancelled()方法會返回true; 調用cancel()方法後,一旦doInBackground()方法執行完,onPostExecute()方法不會被調用,而onCancelled()方法會被調用; 調用cancel()方法後,為確保任務能盡快的取消,我們應該在doInBackground()方法中對isCancelled()方法的返回值進行檢查。

AsyncTask的執行順序:

在DONUT(即Android 1.6)之前,AsyncTask是串行執行任務的; 在Android 1.6的時候AsyncTask開始采用在線程池裡處理並行任務; 後來,從HONEYCOMB(即Android 3.0)開始,為了避免AsyncTask帶來的並發錯誤,AsyncTask又采用了在一個線程中串行的執行任務。

不過,在3.0以後,我們仍然可以通過AsyncTask#executeOnExecute()方法來並行的執行任務。

三、AsyncTask使用實例

異步文件下載AsyncTask類:

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;

public class DownloadFilesTask extends AsyncTask {
    private ProgressDialog mProgressDialog;
    private Context mContext;

    public DownloadFilesTask(Context context) {
        this.mContext = context;
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.show();
    }

    @Override
    protected Long doInBackground(String... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]); // pseudo-code
            publishProgress((int) ((i / (float) (count - 1)) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) {
                break;
            }
        }
        return totalSize;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        mProgressDialog.setMessage("Current download progress:" + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Long result) {
        mProgressDialog.dismiss();
        Toast.makeText(mContext, "Download " + result + " bytes.", Toast.LENGTH_SHORT).show();

    }

    @Override
    protected void onCancelled(Long result) {
        mProgressDialog.dismiss();
        Toast.makeText(mContext, "Download cancelled!", Toast.LENGTH_SHORT).show();
    }
}

啟動AsyncTask:

 new DownloadFilesTask(this).execute(url1, url2, url3);

四、AsyncTask源碼解析

由於各個版本的AsyncTask實現大同小異,我這裡僅以Android 5.1的源碼來進行分析。

初看AsyncTask的源碼,會發現裡面有一大堆的用static標識的成員變量、類及代碼塊,對此我們需要了解一下類的初始化過程,不太了解的請移步博客:Java中的類加載順序

接下類,我們看下AsyncTask類被加載時執行的代碼片段:

成員變量及構造函數的初始化

public abstract class AsyncTask {
    private static final String LOG_TAG = "AsyncTask";

    //獲得當前運行狀態的cup數量
     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //根據當前機器CPU的個數設置線程池中的核心線程數
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    //根據當前機器CPU的個數設置線程池中的最大線程數
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    //線程的存活時間
    private static final int KEEP_ALIVE = 1;

    //初始化線程工廠類,為線程池創建所需的線程
    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());
        }
    };

    //初始化線程池中的緩存隊列,設置為128個
    private static final BlockingQueue sPoolWorkQueue =
            new LinkedBlockingQueue(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
     //初始化線程池執行器
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    //初始化順序執行的線程池執行器
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    //初始化異步任務處理結果碼及進度更新碼
    private static final int MESSAGE_POST_RESULT = 0x1;
    private static final int MESSAGE_POST_PROGRESS = 0x2;

    //初始化線程池中默認的執行器
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;    
    //初始化消息的執行者Handler對象,在getHandler()中進行構造
     private static InternalHandler sHandler;

    //異步任務回調接口
    private final WorkerRunnable mWorker;
    private final FutureTask mFuture;

    //當前異步任務的狀態,初始狀態為“未執行”狀態
    private volatile Status mStatus = Status.PENDING;

    //初始化boolean型原子類
    private final AtomicBoolean mCancelled = new AtomicBoolean();
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

......................

    private static class SerialExecutor implements Executor {
        final ArrayDeque mTasks = new ArrayDeque();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }


  /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    //創建一個異步任務實例,該構造方法必須在UI線程中調用
    public AsyncTask() {
        mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);  

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

        mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(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) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }
..................

}

從這裡可以知道,AsyncTask的成員變量大部分都是static的,也就是說一個應用中的內存中只保存有一份這些成員變量的值。 然後構造函數中只初始化了兩個變量,分別是mWorker和mFuture,並在初始化mFuture的時候將mWorker作為參數傳入,而mWorker是一個Callable對象,mFuture是一個FutureTask對象,FutureTask繼承自Runnable,也即可以理解成:mFuture對象封裝了一個後台的異步耗時任務,並等待線程池執行器去處理該耗時任務,而且mFuture對象會作為一個線程接口提供給調用者。這裡面,實際的後台工作是由mWorker的call() 方法調用的,當然,call() 方法可能會失敗,所以在mFuture的done() 方法裡做了進一步的判斷。

其中mWorker的實現如下:

private static abstract class WorkerRunnable implements Callable {
    Params[] mParams;
}

接著我們看下它執行的入口方法execute()。

AsyncTask的執行方法execute()

public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

該方法僅僅是執行一個Runnable對象,實際用的並不多,我們看下另外一個方法:

 public final AsyncTask execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
 }

通過傳遞指定的參數執行異步任務,實際執行的是executeOnExecutor() 方法,我們看下executeOnExecutor() 的實現:

public final AsyncTask executeOnExecutor(Executor exec,
        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;
    exec.execute(mFuture);

    return this;
}

該方法帶有兩個參數,分別是Executor和Params,第一個參數在類加載的時候就已經初始化了,這裡傳遞進來的是sDefaultExecutor,實際開啟異步任務的線程池也是它,那麼它到底是串行執行還是並行執行呢?其實在前面我們已經總結了,它是串行執行還是並行執行跟平台版本有關。在1.6前通過單個後台線程去處理隊列中的任務,1.6後改為固定線程數的線程池去處理隊列中的任務,後來到了3.0開始,又改回到單個後台線程去處理隊列中的任務,這是為了解決Android1.6以後如果異步任務超過138個時AsyncTask會拋出異常。

當然如果我們想在3.0後並行執行異步任務也是可以的,直接用executeOnExecutor()方法,並往裡面傳入THREAD_POOL_EXECUTOR變量來啟動異步任務。

這裡需要知道,AsyncTask裡面有兩個線程池,分別是SerialExecutor和THREAD_POOL_EXECUTOR,其中SerialExecutor用於任務的排列,而THREAD_POOL_EXECUTOR用於實際的任務執行。

execute() 執行啟動後,最終在mFuture所構造的後台線程裡面運行異步耗時任務,耗時任務的執行實際上會通過抽象函數doInBackground() 回調給調用者進行處理,這也是我們必須要實現的方法。執行完後,通過postResult() 進行線程間的切換,通過Handler機制將後台線程切回到UI線程。

private Result postResult(Result result) {
     @SuppressWarnings("unchecked")
     Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult(this, result));
    message.sendToTarget();
    return result;
}

這裡將後台線程執行的結果Result通過AsyncTaskResult的包裝傳遞到UI線程,AsyncTaskResult是一個私有靜態內部類,只是封裝了AsyncTask的實例和泛型對象Data數組。

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

從前面的的類加載中我們知道,static標識的對象和代碼塊會優先進行加載,不過sHandler一開始初始化為null,通過調用靜態方法getHandler() 後才進行實例化,而該方法內部通過同步加鎖機制防止了多線程同時持有InternalHandler對象而導致的多線程安全問題。

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

在前面,我們說過:AsyncTask的類必須在主線程中被加載,為什麼呢?因為sHandler的緣故,它是靜態的,在類加載的時候進行初始化,而且我們還要用它來進行線程的切換呢,這就必須要求AsyncTask必須在UI線程中加載,否則AsyncTask工作就不正常了。

接下來我們看下InternalHandler對消息處理的實現:

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @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
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

InternalHandler 也是私有內部類,外部不能訪問,我們看到它的構造函數中傳入了Looper.getMainLooper(),說明它是通過UI線程的Looper作為參數構造的,因此InternalHandler是在UI線程中處理消息的。

它裡面有兩種消息類型的處理,分別是MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS。

1、MESSAGE_POST_RESULT:通過調用postResult() 方法,將結果處理分發到UI線程的finish() 方法中:

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

該方法首先判斷當前異步任務是否已經被取消,若取消回調onCancelled() 方法,否則回調onPostExecute() 方法,最後將mStatus標記為FINISHED。其中onPostExecute() 和onCancelled() 就是留給子類去實現的。

2、MESSAGE_POST_PROGRESS:表示這是一個進度更新消息,需要通過子類在doInBackground() 方法內部調用publishProgress() 方法來觸發,然後在onProgressUpdate() 方法中回調給子類去實現。

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
            new AsyncTaskResult
(this, alues)).sendToTarget(); } }

接下來我們看下怎麼取消異步任務:

/**
 * 

Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when cancel is called, * this task should never run. If the task has already started, * then the mayInterruptIfRunning parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task.

* *

Calling this method will result in {@link #onCancelled(Object)} being * invoked on the UI thread after {@link #doInBackground(Object[])} * returns. Calling this method guarantees that {@link #onPostExecute(Object)} * is never invoked. After invoking this method, you should check te * value returned by {@link #isCancelled()} periodically from * {@link #doInBackground(Object[])} to finish the task as early as * possible.

* * @param mayInterruptIfRunning true if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete. * * @return false if the task could not be cancelled, * typically because it has already completed normally; * true otherwise * * @see #isCancelled() * @see #onCancelled(Object) */ public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }

方法注釋挺多的,總結起來就是:

若異步任務已經結束,或已經取消過了,或由於某些原因不能被取消—>返回false; 若異步任務還沒啟動,但調用了cancel()方法,那麼任務永遠也不會運行; 若異步任務已經啟動了但未結束,則由mayInterruptIfRunning決定。此時參數若為true,那麼當前正在執行異步任務的線程會立即中斷;若為false,則等當前正在執行的異步任務完成之後再取消後面其他的異步任務。

五、AsyncTask應用場景

正如Google官方文檔所說,AsyncTask只適合處理短時間的耗時操作,再根據我們前面的分析,總結如下:
- AsyncTask用於不需要進行大數據下載的簡單耗時任務;
- 對磁盤讀寫只需要花費幾毫秒的短時間操作任務。

其它情況還是用Java並發包裡面的API靠譜些。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved