1. 程式人生 > >AsyncTask使用及解析

AsyncTask使用及解析

目錄

 

1.AsyncTask介紹

1)AsyncTask抽象類的3引數

2)繼承AsyncTask可以實現的函式

3)常用公共函式

2.AsyncTask使用

 1)demo1:載入單張圖片

2)demo2:非同步載入多張圖片

3)取消非同步任務

3.注意點

1)AsyncTask不與任何元件繫結生命週期

2)記憶體洩漏

3)螢幕旋轉

4)序列或者並行的執行非同步任務

4.原始碼分析


1.AsyncTask介紹

AsyncTask可以用來處理一些後臺較耗時的任務,檢視原始碼發現其內部就是一個Handler和執行緒池的封裝,可以幫助我們處理耗時任務的同時去更新UI。

1)AsyncTask抽象類的3引數

public abstract class AsyncTask<Params, Progress, Result> {
......
}
  •  Params 啟動任務執行的輸入引數,比如下載URL
  •  Progress 後臺任務執行的百分比,比如下載進度
  •  Result 後臺執行任務最終返回的結果,比如下載結果

2)繼承AsyncTask可以實現的函式

1. onPreExecute():(執行在UI執行緒中) (非必須方法,可以不用實現)

在任務沒被執行緒池執行之前呼叫,通常用來做一些準備操作,比如下載檔案任務執行前,在這個方法中顯示一個進度條。

2.doInBackground(Params... params):(執行在子執行緒中)(此函式是抽象函式必須實現)

在任務被執行緒池執行時呼叫 ,可以在此方法中處理比較耗時的操作,比如下載檔案等等。

3.onProgressUpdate(Progress... values) (執行在UI執行緒中) (非必須方法,可以不用實現)

此函式是任務線上程池中執行處於Running狀態,回撥給UI主執行緒的進度,比如上傳或者下載進度。

4.onPostExecute(Result result)(執行在UI執行緒中) (非必須方法,可以不用實現)

此函式代表任務線上程池中執行結束了,回撥給UI主執行緒的結果。比如下載結果。

5.onCancelled(Result result)onCancelled()任務關閉的函式

3)常用公共函式

1.cancel (boolean mayInterruptIfRunning)嘗試取消這個任務的執行(如果這個任務已經結束或者已經取消或者不能被取消或者某些其他原因,那麼將導致這個操作失敗)

2.execute (Params... params)用指定的引數來執行此任務(此方法必須在UI執行緒中呼叫,因為要將handler繫結到主執行緒的Looper,方便後臺任務執行完畢後,由後臺執行緒切換到UI執行緒去更新UI)。

3.executeOnExecutor(Executor exec,Params... params)用指定的引數行此任務,並執行在指定的執行緒池中(此方法必須在UI執行緒中呼叫)

4.getStatus ()獲得任務的當前狀態  PENDING(等待執行)、RUNNING(正在執行)、FINISHED(執行完成)

5.isCancelled ()在任務正常結束之前能成功取消任務則返回true,否則返回false

2.AsyncTask使用

 1)demo1:載入單張圖片

public class ImageText extends Activity {

    ImageView imageView;
    ProgressBar progressBar;
    String URL = "http://b.zol-img.com.cn/sjbizhi/images/4/320x510/1366618073898.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.imagetext);
        imageView = (ImageView) findViewById(R.id.imageview_imagetext);
        progressBar = (ProgressBar) findViewById(R.id.progress_imagetext);
        //開始非同步載入圖片——傳入圖片的URL
        new MyAsyncTask().execute(URL);  
    }


 //3泛型:url型別,void(不要進度),返回值型別
 class  MyAsyncTask extends AsyncTask<String,Void,Bitmap>{ 

          //第一階段————準備階段讓進度條顯示
          @Override
          protected void onPreExecute() {
              super.onPreExecute();
              progressBar.setVisibility(View.VISIBLE);  
          } 	

          //第二階段——網路獲取圖片
          @Override
          protected Bitmap doInBackground(String... params) {
              //從可變引數的陣列中拿到第0位的圖片地址
              String newurl = params[0]; 
              Bitmap bitmap = null;
              URLConnection urlConnection;
              InputStream  is = null;
              //圖片下載操作
              try {
                  urlConnection = new URL(newurl).openConnection(); 
                  is = urlConnection.getInputStream();  
                  BufferedInputStream bf=new BufferedInputStream(is);
                  //圖片載入的太快,可以用執行緒睡眠
                  Thread.sleep(1000);
                  bitmap = BitmapFactory.decodeStream(bf);  
                  is.close(); 
                  bf.close();
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //返回結果bitmap
              return bitmap; 
          }

          //第三階段,拿到結果bitmap圖片,更新ui
          @Override
          protected void onPostExecute(Bitmap bitmap) {  
              super.onPostExecute(bitmap);
              progressBar.setVisibility(View.GONE); 
              imageView.setImageBitmap(bitmap);     
          }
      }
}

2)demo2:非同步載入多張圖片

public class MainActivity extends AppCompatActivity {

    private ProgressBar progressBar;
    private String imgUrls[] = {"https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=922669495,273204025&fm=80&w=179&h=119&img.JPEG",
            "https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=922669495,273204025&fm=80&w=179&h=119&img.JPEG"};
    private List<Bitmap> list = new ArrayList();
    private Bitmap bitmap = null;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        progressBar = (ProgressBar) findViewById(R.id.pro);
        new MyAsyncTask().execute(imgUrls);
    }

    class MyAsyncTask extends AsyncTask<String, Void, List<Bitmap>> {
        //第一階段————準備階段讓進度條顯示
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressBar.setVisibility(View.VISIBLE);
        }

        //第二階段——網路獲取圖片——返回型別為list集合
        //for迴圈遍歷可變引數,拿到單次任務所需的圖片路徑
        @Override
        protected List<Bitmap> doInBackground(String... params) {
            for (int i = 0; i < params.length; i++) {
                //從可變引數的陣列中拿到圖片地址
                String newurl = params[i];
                URLConnection urlConnection;
                InputStream is = null;
                try {
                    urlConnection = new URL(newurl).openConnection();
                    is = urlConnection.getInputStream();
                    BufferedInputStream bf = new BufferedInputStream(is);
                    bitmap = BitmapFactory.decodeStream(bf);
                    list.add(bitmap);
                    is.close();
                    bf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //返回存bitmap的集合結果給onPostExecute方法
            return list;
        }

        //第三階段———拿到結果資料,更新ui
        @Override
        protected void onPostExecute(List<Bitmap> list) {
            super.onPostExecute(list);
            progressBar.setVisibility(View.GONE);
        }
    }
}

3)取消非同步任務

public class MainActivity extends AppCompatActivity {

    ProgressBar progressBar;
    MyAsyncTask mytask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        progressBar = (ProgressBar) findViewById(R.id.pro);
        //開始非同步
        mytask = new MyAsyncTask();
        mytask.execute();
    }

    //將AsyncTask和activity生命週期繫結-暫停狀態時候去終止非同步操作
    @Override
    protected void onPause() {
        super.onPause();
        //執行狀態
        if (mytask != null && mytask.getStatus() == AsyncTask.Status.RUNNING) {
            //cancel方法只是將對應的AsyncTask標記為cancel狀態,並沒有真正的取消執行緒的執行。
            mytask.cancel(true);
        }
    }

    //3泛型引數:void,進度條int值,void
    class MyAsyncTask extends AsyncTask<Void, Integer, Void> {
        //任務執行邏輯:
        @Override
        protected Void doInBackground(Void... params) {
            for (int i = 0; i <= 100; i++) {
                //判斷是否為cancel的狀態,是就直接break
                if (isCancelled()) {
                    break;
                }
                //更新進度條進度需要使用的方法-繫結onProgressUpdate方法
                publishProgress(i);
                try {
                    //模擬進度條走動的效果
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        //拿到實時更新的進度值
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //判斷是否為cancel的狀態
            if (isCancelled()) {  
                return;
            }
            progressBar.setProgress(values[0]);
        }
    }
}

上面AsyncTask.cancel方法只是將對應的AsyncTask標記為cancel狀態,並沒有真正的取消執行緒的執行。會在任務執行時候判斷是cancel狀態,才會終止任務的執行。同時可以看到進度條值改變,更新值的方法為publishProgress(i),會將進度值傳遞給onProgressUpdate()方法,從而更新UI。

3.注意點

1)AsyncTask不與任何元件繫結生命週期

在Activity/或者Fragment中建立執行AsyncTask時,最好在Activity/Fragment的onDestory()呼叫 cancel(true);

2)記憶體洩漏

如果AsyncTask被宣告為Activity的非靜態的內部類,那麼AsyncTask會保留一個對建立了AsyncTask的Activity的引用。如果Activity已經被銷燬,AsyncTask的後臺執行緒還在執行,它將繼續在記憶體裡保留這個引用,導致Activity無法被回收,引起記憶體洩露。

3)螢幕旋轉

螢幕旋轉或Activity在後臺被系統殺掉等情況會導致Activity的重新建立,之前執行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時呼叫onPostExecute()再去更新介面將不再生效。

4)序列或者並行的執行非同步任務

        目前AsyncTask支援並行和序列的執行非同步任務,當想要序列執行時,直接執行execute()方法,如果需要並行執行,則要執行executeOnExecutor(Executor)。

        3.0後新增了一個方法executeOnExecutor(Executor exec, Object... params)。第一個是Executor(執行緒池例項),第二個是任務引數

public class MainActivity extends AppCompatActivity {

    AsyncTask<String[], Void, Void> mytask;
    String imgUrls[] = {"https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=922669495,273204025&fm=80&w=179&h=119&img.JPEG",
            "https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=922669495,273204025&fm=80&w=179&h=119&img.JPEG"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //開始非同步
        mytask = new MyAsyncTask().executeOnExecutor(Executors.newFixedThreadPool(1),imgUrls);
    }

    class MyAsyncTask extends AsyncTask<String[], Void, Void> {
        @Override
        protected Void doInBackground(String[]... params) {
            return null;
        }
    }
}

Executor主要由四種類型:

newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若不可回收,則新建執行緒

newFixedThreadPool:建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待

newScheduledThreadPool:建一個定長執行緒池,支援定時及週期性任務執行

newSingleThreadExecutor:建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照順序執行

建立執行緒池方式為:

asyncTask.executeOnExecutor(Executors.newFixedThreadPool(1), params)

asyncTask.executeOnExecutor(Executors.newSingleThreadExecutor, params)

4.原始碼分析

先看看建構函式:

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

  private final WorkerRunnable<Params, Result> mWorker;
  private final FutureTask<Result> mFuture;

   /**
    *建立一個新的非同步任務。這個構造器必須在UI執行緒上呼叫。
    */
    public AsyncTask() {
        this((Looper) null);
    }

    /**
     *建立一個新的非同步任務。這個構造器必須在UI執行緒上呼叫。
     *@hide---不可以直接呼叫
     */
    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }

    /**
     * 建立一個新的非同步任務。這個構造器必須在UI執行緒上呼叫。
     * @hide---不可以直接呼叫
     */
    public AsyncTask(@Nullable Looper callbackLooper) {
        //例項化handler
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        //單個任務執行的工作執行緒WorkerRunnable
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                //設定為true,代表當前方法被呼叫過
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    //設定執行緒的優先順序
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

                    //將任務引數傳遞給doInBackground方法處理
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    //處理任務結果,更新UI
                    postResult(result);
                }
                return 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);
                }
            }
        };
    }
}

可以看到例項化AsyncTask同時,內部也例項化了帶Looper的Handler,為UI更新做好準備。WorkRunnable可以理解為一個工作執行緒,同時自身實現了CallBack介面,Call方法中包含了AyncTask最終要執行的任務,

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
}

看看Callback的解釋:得到一個任務執行完後的結果,或者如果不能這樣做,就丟擲異常。

public interface Callable<V> {
    //得到一個任務執行完後的結果,或者如果不能這樣做,就丟擲異常。
    V call() throws Exception;
}

目光再回到AsyncTask構造方法,WorkRunnable例項化同時,內部通過doInBackground(mParams)拿到任務引數去執行任務,最終將返回結果result呼叫postResult()方法。

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

postResult就是將Result攜帶的資訊,傳送給指定target的Handler。

(題外話:在Java中一般通過繼承Thread類或者實現Runnable介面這兩種方式來建立多執行緒,但是這兩種方式都有個缺陷,就是不能在執行完成後獲取執行的結果,因此Java 1.5之後提供了Callable和Future介面,通過它們就可以在任務執行完畢之後得到任務的執行結果)

同時還能看到原始碼中將執行結果Result和WorkRunnable(自身實現了CallBack介面)傳給了FutureTask。下面看看FutureTask具體是什麼。

public class FutureTask<V> implements RunnableFuture<V> {

private Callable<V> callable;

public FutureTask(Callable<V> callable) {
        //必須傳入非空的實現CallBack介面的工作執行緒WorkRunnable
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}

......
}

由上圖知道FutureTask實現了RunnableFuture介面。官方註釋解釋FutureTask這個類提供非同步任務啟動和取消的方法。再看看RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

看到了RunnableFuture繼承了Callable和Future介面,再看看Future介面

public interface Future<V> {

 boolean cancel(boolean mayInterruptIfRunning);

 boolean isCancelled();

 boolean isDone();

 V get() throws InterruptedException, ExecutionException;

 V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, 
 TimeoutException;
}
  • cancel():用來取消非同步任務的執行。如果非同步任務已經完成或者已經被取消,或者由於某些原因不能取消,則會返回false。如果任務還沒有被執行,則會返回true並且非同步任務不會被執行。如果任務已經開始執行了但是還沒有執行完成,若mayInterruptIfRunning為true,則會立即中斷執行任務的執行緒並返回true,若mayInterruptIfRunning為false,則會返回true且不會中斷任務執行執行緒。
  • isCanceled():判斷任務是否被取消,如果任務在結束(正常執行結束或者執行異常結束)前被取消則返回true,否則返回false。
  • isDone():判斷任務是否已經完成,如果完成則返回true,否則返回false。需要注意的是:任務執行過程中發生異常、任務被取消也屬於任務已完成,也會返回true。
  • get():獲取任務執行結果,如果任務還沒完成則會阻塞等待直到任務執行完成。如果任務被取消則會丟擲CancellationException異常,如果任務執行過程發生異常則會丟擲ExecutionException異常,如果阻塞等待過程中被中斷則會丟擲InterruptedException異常。
  • get(long timeout,Timeunit unit):帶超時時間的get()版本,如果阻塞等待過程中超時則會丟擲TimeoutException異常。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

現在總結一下:AsyncTask構造方法中,WorkRunnable方法中將任務引數Params傳入doInBackground(),並將得到的結果和WorkRunnable物件傳給了FutureTask,FutureTask實現了RunnableFuture介面,且RunnableFuture介面繼承了Runnable介面和Future介面,所以FutureTask既能當做一個Runnable直接被Thread執行,也能作為Future用來得到Callable的計算結果。

現在理清了一條線:當WorkRunnable中定義的call方法被執行時,doInBackground就會開始執行,我們定義的後臺任務也就真正開始了。

那麼這個call方法什麼時候會被呼叫呢?對了,就是AsyncTask.execute()。

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

看到第一個引數sDefaultExecutor,是啥?看原始碼

(對Executor不熟悉的可以看看:https://blog.csdn.net/qq_25806863/article/details/71126867)

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;

        //加上了鎖,序列執行Runnable任務
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        //執行完之後才去,判斷是否有下個Runnable任務
                        scheduleNext();
                    }
                }
            });
            //為空的時候,也去判斷任務佇列中頭部是否有任務Runnable
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            //poll方法:拿到任務佇列中的頭部任務(為了方便,說成隊列了)
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive); 
            }
        }
}

可以看到sDefaultExecutor,最終例項化了一個靜態類SerialExecutor。確實是序列的執行任務,並且任務執行完,還會通過scheduleNext()函式看看佇列頂部是否還有後臺任務。

可以看到scheduleNext()中這麼一句話THREAD_POOL_EXECUTOR.execute(mActive)。THREAD_POOL_EXECUTOR是啥?看原始碼。

   public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

THREAD_POOL_EXECUTOR看出是系統提早建立好的執行緒池。說了這麼多其實一直在說executeOnExecutor()函式的第一個引數sDefaultExecutor,作用就是預設執行緒池序列的執行我們的任務。

跳出來,仔細看看executeOnExecutor()函式:

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {

       //這2個異常解釋了:為什麼任務再執行或者finish階段,不能再呼叫execute函式
                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();
        
        //任務執行引數-傳遞給WorkRunnable
        mWorker.mParams = params;

        //*******重點********
        //預設執行緒池或者指定的執行緒池,執行execute(),傳入FuturTask物件
        exec.execute(mFuture);

        return this;
}

重點就是:當我們執行到exec.execute(mFuture)時,就是呼叫WorkRunnable(也就是AsyncTask構造方法中mWorker)中的Call()函式,這是任務執行引數又有了,所以mWorker的Call方法中的 doInBackground(mParams)函式就開始執行後臺任務了。

上面這一套就是序列執行後臺任務的流程,那麼並行執行後臺任務是什麼流程?其實就是直接呼叫executeOnExecutor()這個函式,可以看到它的第一個引數就是傳入我們的指定執行緒池,O了。

 

還有一個重要的方法:onPostExecute。還記得WorkRunnable中呼叫的這個postResult()函式嗎。

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

這個函式,在後臺任務執行完後,通過Handler傳送了一個What==MESSAGE_POST_RESULT的訊息,看看如何處理的。

 @Override
 public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                //呼叫了finish()函式,去處理result結果
                case MESSAGE_POST_RESULT:
                    // 這兒只有一個結果,因為序列執行任務的原因
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
}

再看看finish()

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } 
        //任務沒被取消,會呼叫onPostExecute(result)處理
        else {
            onPostExecute(result);
        }
    //標記為完成狀態
    mStatus = Status.FINISHED;
}