AsyncTask用法簡介

語言: CN / TW / HK

本文已參與「新人創作禮」活動,一起開啟掘金創作之路。

寫在前面

  • AsyncTask簡介

    AsyncTask非同步任務,是Android提供給我們的一個處理非同步任務的類. 通過該類,可以實現UI執行緒和後臺執行緒間的通訊. 後臺執行緒執行非同步任務,並將結果及進度傳遞給UI執行緒,由UI執行緒做相應處理和顯示.

  • 為什麼使用非同步任務(子執行緒操作)

    • Android中只有UI執行緒能進行更新UI的操作. 這樣能保證UI的穩定性和準確性,避免多個執行緒同時對UI進行操作,造成UI混亂.
    • 但Android是一個多執行緒的OS,一些耗時操作,如請求網路、圖片載入、檔案讀取等放在UI執行緒會造成後面任務的阻塞,從而出現ANR(Application Not Responding)異常. 所以需要將耗時操作放在子執行緒中執行,這樣既避免了Android的單執行緒模型,又避免了ANR異常.
  • AsyncTask為何而生

    • UI執行緒能更新UI,子執行緒不能更新UI.
    • 當子執行緒操作過程中需要更新UI時需藉助Handler,比較麻煩.
    • AsyncTask方便我們在子執行緒中更新UI. 即對基於執行緒池的子執行緒和Handler的封裝.

AsyncTask類的介紹

  • 構建AsyncTask子類的引數

    AsyncTask<Params, Progress, Result>是一個抽象類,通常用於被繼承. 繼承AsyncTask需要指定如下三個泛型引數

    1. Params:啟動任務時輸入的引數型別(如String).
    2. Progress:後臺任務執行中返回進度值的型別(如Integer).
    3. Result:後臺任務執行完成後返回結果的型別(如Bitmap).
  • 構建AsyncTask子類的回撥方法

  • doInBackground():必須重寫,非同步執行後臺執行緒要完成的任務,耗時操作將在此方法中完成.

  • onPreExecute():執行後臺耗時操作前被呼叫,通常用於進行初始化操作.

  • onPostExecute():當doInBackground方法完成後,系統將自動呼叫此方法,並將doInBackground方法返回的值傳入此方法. 通過此方法進行UI的更新.

  • onProgressUpdate():當在doInBackground方法中呼叫publishProgress方法更新任務執行進度後,將呼叫此方法. 通過此方法我們可以知曉任務的完成進度.

案例一、載入圖片

  • AsyncTask的實現

    ``` /* * 1. 體驗AsyncTask各個方法的執行順序 * onPreExecute最先執行. 然後執行doInBackground * doInBackground呼叫publisProgress()後執行onProgressUpdate * doInBackground執行結束返回結果給onPostExecute執行 * * 2. 載入網路圖片onPostExecute / // arg1、2、3參考上面AsyncTask類的介紹 // arg1:輸入的圖片地址型別. arg2:非同步任務執行完畢返回的結果型別 class MyAsyncTask extends AsyncTask {

    @Override protected void onPreExecute() {
        super.onPreExecute();
        pb.setVisibility(View.VISIBLE);
        LogTool.d(TAG, "onPreExecute..."); // 非同步任務執行之前的初始化操作
    }
    
    // 可變引數為啟動任務時輸入的引數型別
    @Override protected Bitmap doInBackground(String... params) {
    
        // 獲取傳遞進來的引數,即圖片地址
        String url = params[0];
        Bitmap bitmap = null;
        URLConnection conn;
        InputStream is = null; // 使用InputStream讀取流資料
        BufferedInputStream bis = null;
    
        try {
            conn = new URL(url).openConnection();
            is = conn.getInputStream();
            bis = new BufferedInputStream(is);
            bitmap = BitmapFactory.decodeStream(bis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        LogTool.d(TAG, "doInBackground...");
        publishProgress(); // 非同步任務執行過程中呼叫onProgressUpdate更新介面進度
        return bitmap;
    }
    
    // 引數為doInBackground執行完畢返回的結果物件
    @Override protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        pb.setVisibility(View.GONE);
        iv.setImageBitmap(bitmap);
        LogTool.d(TAG, "onPostExecute...");
    }
    
    // 被doInBackground使用publishProgress後進行進度的更新
    @Override protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
        LogTool.d(TAG, "onProgressUpdate...");
    }
    

    } ```

  • AsyncTask的呼叫

    ``` /* * 在UI執行緒(如onCreate()方法)中建立非同步任務例項 /

    private static final String TAG = "AsyncTaskActivity";

    private static final String IMAGE_URL = "..."; // 圖片地址

    private ImageView iv; private ProgressBar pb;

    // 這裡有個BUG,由AsyncTask底層執行緒機制導致. 在案例二中提供解決方案. private MyAsyncTask myTask;

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task);

    iv = (ImageView) findViewById(R.id.iv_bg);
    pb = (ProgressBar) findViewById(R.id.pb_bg);
    
    // *** execute中的引數將傳遞給doInBackground() ***
    myTask = new MyAsyncTask();
    myTask.execute(IMAGE_URL);
    

    } ```

案例二、更新進度

  • AsyncTask的實現

    ``` class ProgressBarAsyncTask extends AsyncTask {

    @Override protected Void doInBackground(Void... voids) {
        // 通過for迴圈模擬進度條的進度
        for (int i = 0; i < 30; i++) {
            if (isCancelled()) {
                break;
            }
            // 通過publishProgress方法將呼叫onProgressUpdate來更新進度
            publishProgress(i);
            // 通過休眠模擬耗時操作
            SystemClock.sleep(300);
        }
        /**
         * 當for迴圈全部執行完畢才會進行下一個Task任務,由AsyncTask底層的基於執行緒池引起的
         *
         * 解決方案,將AsyncTask生命週期與當前所在Activity繫結
         */
        return null;
    }
    
    @Override protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        if (isCancelled()) { // 若執行緒結束,進度條置空
            return;
        }
        // 通過publishProgress方法傳過來的值進行進度更新
        pb_schedule.setProgress(values[0]);
    }
    

    } ```

  • 繫結AsyncTask的生命周到Activity中

    @Override protected void onPause() { super.onPause(); if (pbTask != null && pbTask.getStatus().equals(AsyncTask.Status.RUNNING)) { // 注意:cancel方法僅將AsyncTask任務置為CANCEL狀態,並未真正結束執行緒 pbTask.cancel(true); } } - AsyncTask的呼叫

    ``` private ProgressBarAsyncTask pbTask;

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task);

    pb_schedule = (ProgressBar) findViewById(R.id.pb_schedule);
    
    pbTask = new ProgressBarAsyncTask();
    pbTask.execute(); // 輸入引數為空
    

    } ```

END 使用AsyncTask的注意事項

  • 必須在UI執行緒中建立AsyncTask的例項.
  • 只能在UI執行緒中呼叫AsyncTask的execute()方法.
  • AsyncTask被重寫的四個方法是系統自動呼叫的,不應手動呼叫.
  • 每個AsyncTask只能被執行一次,多次執行將會引發異常.
  • AsyncTask的四個方法,只有doInBackground()方法是執行在其他執行緒中,其他三個方法都執行在UI執行緒中,可以對UI進行更新操作.