Android入門教程 | AsyncTask 使用介紹

Android_anzi發表於2021-12-07

AsyncTask 有助於使用 UI 執行緒。這個類能讓你不主動使用多執行緒或 Handler,在子執行緒種執行耗時任務,並在UI執行緒釋出結果。

AsyncTask 是一個在不需要開發者直接操作多執行緒和 Handler 的情況下的幫助類,適用於短時間的操作(最多幾秒)。 如需長時間的執行緒操作,建議使用多執行緒包  java.util.concurrent 中的功能,比如執行緒池。

假設我們有個需要在後臺執行緒中執行的非同步計算任務,並且結果需要更新 ui。 那我們需要關注3個範型引數: Params,  Progress 和  Result
再關注4個步驟方法:
onPreExecutedoInBackgroundonProgressUpdate 和  onPostExecute

屬性介紹

使用 AsyncTask 之前,我們先看一下它的三個型別。

AsyncTask<Params, Progress, Result>
屬性 描述
Params 執行任務前,傳入的引數的型別
Progress 後臺執行緒執行的時候,用來表示進度的型別
Result 表示執行結果的型別

這 3 個型別需要開發者自己指定。比如指定 String, Integer 等。這 3 個型別在後面的方法裡會用到。

不用的泛型可以用 Void表示。例如

private class MyTask extends AsyncTask<Void, Void, Void> { ... }

方法介紹

要使用 AsyncTask ,必須新建一個類來繼承它,並且重寫  doInBackground 方法。通常也會重寫  onPostExecute 方法。 執行非同步任務的時候,我們主要關心下面這4個方法。

方法 描述
onPreExecute() 執行任務前在ui執行緒呼叫。通用用來設定任務,比如在介面上顯示一個進度條。
Result doInBackground(Params... params) 在  onPreExecute() 結束後立即呼叫這個方法。耗時的非同步任務就在這裡操作。執行任務時傳入的引數會被傳到這裡。非同步任務的中間結果在這裡可以用  publishProgress 傳送到主執行緒。
onProgressUpdate(Progress... values) 在 ui 執行緒中執行。後臺任務還在進行的時候,這裡負責處理進度資訊。比如在這顯示進度條動畫,修改文字顯示等。
onPostExecute(Result result) 後臺任務結束了調這個方法。它在 ui 執行緒執行。最後的結果會傳到這。

AsyncTask 的三種狀態

每個狀態在一個任務的生命週期中只會被執行一次。

狀態 描述
PENDING 等待(還沒有開始執行任務)
RUNNING 執行中
FINSHED 完成

用法示例

虛構一個計算任務

/**
 * 虛擬的計算任務
 */private class CalculationTask extends AsyncTask<Float, Integer, Float> {    protected Float doInBackground(Float... inputs) {
        Log.d(TAG, "doInBackground thread ID = " + Thread.currentThread().getId());        long step = 0;        float result = 0;        for (float f : inputs) {            // 假設這裡有一些耗時的操作
            result += f;
        }        while (step < 5) {
            result += step;            step++;
            publishProgress((int) step);
        }        return result;
    }    protected void onProgressUpdate(Integer... progress) {
        Log.d(TAG, "onProgressUpdate thread ID = " + Thread.currentThread().getId());
        Log.d(TAG, "onProgressUpdate: " + progress[0]);
    }    protected void onPostExecute(Float result) {
        Log.d(TAG, "onPostExecute thread ID = " + Thread.currentThread().getId());
        Log.d(TAG, "任務執行完畢");
    }
}// 執行任務new CalculationTask().execute(1.2f, 2.3f, 6.3f);

取消任務

呼叫  cancel(boolean) 可隨時取消任務。取消任務後  isCancelled() 會返回true。

呼叫這個方法後,後臺任務  doInBackground(Object[]) 執行完畢後會呼叫  onCancelled(Object) 而不再是  onPostExecute(Object)。 為保證任務能被及時地取消,在  doInBackground(Object[]) 中應該經常檢查  isCancelled() 返回值

執行緒規則 Threading rules

  • 非同步任務必須從UI執行緒啟動
  • 必須在UI執行緒例項化AsyncTask類
  • 必須在UI執行緒呼叫 execute(Params...)
  • 不要手動呼叫 onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...)
  • 同一個非同步任務例項只能被執行一次。重複執行同一個非同步任務例項會丟擲異常( IllegalStateException)。

AsyncTask 相關面試題

1. AsyncTask 是什麼?能解決什麼問題

2. 談談 AsyncTask 的三個泛型引數作⽤

3. 說說AsyncTask的原理

構造⽅法中建立了⼀個 WorkRunnable 和⼀個 FutureTask 物件,

在 WorkRunnable的Call ⽅法中調⽤ doInBackground ⽅法,並獲取 Result 返回值,然後返回撥⽤postResult ⽅法的返回值,建立 FutureTask 時傳⼊了 WorkRunnable 物件。

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();            return postResult(result);
        }
    };
    mFuture = new FutureTask<Result>(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 occurred while executing
                doInBackground()", e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

postResult ⽅法

getHandler⽅法獲取⾃帶Handler物件,來獲取Message

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

這個內部⾃帶InternalHandler構造⽅法中傳⼊getMainLooper()返回值,即主執行緒Looper,

然後在handlerMessage⾥⾯針對msg.what分別執⾏了finish和onProgressUpdate⽅法,其中finish⽅法則是調⽤onCancelled或onPostExecute⽅法

private static final InternalHandler sHandler = new 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;
               }
          }
}
private void finish(Result result) {     if (isCancelled()) {          onCancelled(result);
     } else {          onPostExecute(result);
     }     mStatus = Status.FINISHED; 
}

看看 AsyncTask 的 execute ⽅法,返回了 executeOnExecutor 的⽅法,並傳⼊sDefaultExecutor 和 params 為引數,params 即 AsyncTask 的 doInBackground中傳⼊的 params ,

⽽ sDefaultExecutor 是預設的串⾏執⾏器(執行緒池),⼀個SerialExecutor 再看看executeOnExecutor ⽅法,先判斷了任務狀態,如果是 RUNNING 或FINISHED,則會丟擲異常,

然後會把當前狀態從 PENDING 改為 RUNNING,把⼊參 params 傳到WorkRunnable 物件中,再調⽤傳⼊的 sDefaultExecutor 的 execute ⽅法,傳⼊mFuture 為引數

public final AsyncTask<Params, Progress, Result> 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;
 }

execute ⽅法中調⽤ mTasks 的 offer ⽅法新增⼀個任務到快取佇列中,在 run ⽅法中就調⽤了前⾯傳⼊的 mFuture 的 run ⽅法,

然後執⾏ THREAD_POOL_EXECUTOR 的 execute ⽅法,任務的實際執⾏就在這,THREAD_POOL_EXECUTOR 就是⼀個執行緒池

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static class SerialExecutor implements Executor {     final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
     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);
          }
      }
}

4. 你覺得AsyncTask有不⾜之處嗎?

AsyncTask 使⽤起來⽐較輕量,但是⾃身也存在⼀些問題。

主要表現在:

  • cancel ⽅法實現不是很好。如果你調⽤了 AsyncTask 的 cancel(false) ⽅法, doInBackground() 仍然會執⾏到⽅法結束,只是不會去調⽤ onPostExecute() ⽅法,但是實際上也是讓程式執⾏了沒有意義的操作。如果調⽤cancel(true),mayInterruptIfRunning 設定為 true,會使任務儘早結束,但是如果 doInBackground() 有不可被打斷的⽅法,就會失效,⽐如 BitmapFactory.decodeStream() 操作。
  • 記憶體洩露,在 Activity 中使⽤⾮靜態匿名內部類 AsyncTask 類,由於 Java 內部類的特點,內部類持有外部類引⽤,⽽由於 AsyncTask ⽣命週期可能⽐ Activity 的,當 Activity 銷燬時,AsyncTask 還在執⾏,由於AsyncTask 持有 Activity 的引⽤,導致 Activity 物件⽆法回收,進⽽產⽣記憶體洩露。
  • 結果丟失,當螢幕旋轉等造成 Activity 新建立時 AsyncTask 資料丟失的問題。當 Actviity 銷燬並建立新的收,還在運⾏的 AsyncTask 會持有⼀個 Activity 的⾮法引⽤,即之前 Activity 的例項,導致onPostExecute() ⽅法⽆效。
  • 串⾏,並⾏多版本不⼀致.1.6之前為串⾏,1.6-2.3為並⾏,3.0之後⼜改為串⾏,但是可以透過executeOnExecutor() 實現並⾏處理


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2846330/,如需轉載,請註明出處,否則將追究法律責任。

相關文章