Android 使用 AsyncTask 執行非同步任務

Android AsyncTask

某些執行時間較長的任務必須放到背景執行,避免 ANR 的發生。

背景執行

要做到背景執行,可以使用 Service、Thread 及 AsyncTask 這 3 種類別。Service 可以向系統註冊,長期在背景執行。而 Thread 則可自己建立執行緒,配合 Handler 類別來和 GUI 執行緒同步,偏向什麼都自己來。AsyncTask 類別則幫我們做了很多事,我們要做的只是告訴 AsyncTask 什麼內容要在背景執行,執行完後要做什麼事之類的指定,算是比較簡單方便可以在背景執行達到非同步任務的方法。

繼承 AsyncTask 類別

要使用 AsyncTask 的第一步就是建立一個繼承 AsyncTask 的類別,然後覆寫需要的方法,如下:
@Override
protected void onStart() {
     super.onStart();

     txtResult = (TextView) findViewById(R.id.txtTaskResult);
     txtResult.setText("Loading......");

     new LoadingDataAsyncTask().execute(null);
}

private void getData(){
     try {
          Thread.sleep(3000);
     } catch (InterruptedException e) {
          e.printStackTrace();
     }
}

private void showData(){
     txtResult.setText("資料載入完畢!!");
}

class LoadingDataAsyncTask extends AsyncTask<String, Integer, Integer>{

     @Override
     protected Integer doInBackground(String... param) {
          getData();
          return null;
     }

     @Override
     protected void onPostExecute(Integer result) {
          super.onPostExecute(result);
          showData();
     }

     @Override
     protected void onProgressUpdate(Integer... values) {
          super.onProgressUpdate(values);
     }

     @Override
     protected void onPreExecute() {
          super.onPreExecute();
     }

}
LoadingDataAsyncTask 類別是內部類別,寫在 Activity 之內,所以 getData() 及 showData() 方法可以直接呼叫。

要執行 AsyncTask 很容易,建立物件 (new) 後,執行 execute(null) 方法即可,可以傳參數進去,若不用則給 null。execute() 方法不能重覆呼叫,在結束之前又呼叫一次的話會發生錯誤。

AsyncTask 類別的方法說明

doInBackground 方法

這是一定必須覆寫的方法,把會花時間執行的內容放在這裡,例如這裡的 getData()會是一個需要花時間執行的方法,我們就把它放到背景去執行。這裡我簡單用 Thread.sleep(3000),讓它「睡」3秒鐘,模擬這部份的內容花了3秒才做完。
*重要:這裡不能和UI有任何互動。

onPostExecute 方法

當 doInBackground 方法中的程式執行完畢後,就會執行這個方法。通常是用來把取得的資料送給 UI 來顯示。在 doInBackground 方法中是不能和任何 UI 元件互動的,只能在背景執行完畢後在這裡把資料送給 UI。

onProgressUpdate 方法

這個方法會取得一個數值,可以用來計算目前執行的進度,通常用來改變進度列(ProgressBar)的顯示。

onPreExecute 方法

在背景執行之前要做什麼事都是寫在這裡。 詳細資料可以參考AsyncTask類別。

此外,當執行這個範例時,你會發現程式看起來像是當掉,沒有任何反應,這是因為它正在背景執行,若沒有任何回饋訊息給使用者知道,會以為當掉了。在另外一篇文章介紹的ProgressDialog Demo可以跳出一個等待訊息對話框,告訴使用者稍等一下。
本文網址:http://blog.tonycube.com/2011/08/asynctask.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

11 則留言

  1. public class MainActivity extends Activity {
    private TextView txtResult;
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new LoadingDataAsyncTask().execute();
    initView();
    }
    private void initView() {
    txtResult = (TextView) findViewById(R.id.txtResult);
    }
    private void getData() {
    Price price = Utility.getPrice("1");
    String result = "按盤價" + price.getPriceNow() + "\n最高" + price.getHighest() + "\n最低" + price.getLowest();
    txtResult.setText(result);
    }
    class LoadingDataAsyncTask extends AsyncTask{
    @Override
    protected Integer doInBackground(String... param) {
    getData();
    return null;
    }

    @Override
    protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    txtResult.setText(result);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);
    }

    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    }

    }
    執行出來 跑出來一下下就當了 我不太懂是為何?? 如果 new LoadingDataAsyncTask().execute();的execute();裡面放素null就會錯誤不能執行
    大大 是我位置放錯嗎??還是我沒搞懂執行緒

    回覆刪除
    回覆
    1. 新手錯誤,在 doInBackground 中不能寫任何和 UI 有關的程式碼
      查 API 有寫,我的文章中也有寫喔~~~

      所以我的範例才會分成 getData() 和 showData(),在 doInBackground 中只能取資料,在 onPostExecute 中才能設定值給 UI ,調整一下吧。

      另外, new LoadingDataAsyncTask().execute(); 應該要寫在 initView(); 之後才對。

      刪除
  2. 似懂非懂 像是這樣嗎?? 大大 小弟功力很淺 但還是想學起來
    private void getData() {
    } private void showdata()
    {
    String result = "按盤價" + price.getPriceNow() + "\n最高" + price.getHighest() + "\n最低" + price.getLowest();
    txtResult.setText(result);
    }class LoadingDataAsyncTask extends AsyncTask{
    @Override
    protected Integer doInBackground(String... param) {
    getData();
    return null;
    }
    @Override
    protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    showdata();}

    回覆刪除
    回覆
    1. 關鍵在於"什麼叫做和 UI 有關的動作",txtResult.setText(result); 就是,它把值指定給 txtResult ,就是對 UI 元件做更新(對屬性 text 設定值),這個部份不能寫在 doInBackground 方法裡面。

      其它任何如"下載資料、處理資料"等可能"長時間處理"且只和"資料"有關的程式碼,都寫在 doInBackground 裡,當它處理完成後有兩種方式可以把資料"送出去",一種是使用類別裡面的全域變數,例如:

      private String data;

      把"資料"存到 data 這個變數中,在 onPostExecuted() 在把它指定給 UI 。

      另一種方式,是使用回傳值,我的範例中是使用 return null ,但實際上它是可回傳結果的,在 doInBackground() 回傳,在 onPostExecut 中接收。

      你可注意到 doInBackground() 的回傳型態是 Integer ,而 onPostExecute(Integer) 的輸入參數也是 Integer ,這個是可以依情況換成 String 等其他型態,你可以查 "AsyncTask"的API,它的三個參數是有意義的,並且可以換成你要的型態。

      你只要把握幾個重點
      1.在 doInBackground 只能處理資料,onPostExecute則是用來顯示結果
      2.傳遞資料的方式
      3.瞭解方法 AsyncTask 參數的設定

      基本就差不多會用 AsyncTask 的九成了。

      刪除
  3. !!非常感謝 你細心回答~

    我學到了^^ 非常感謝

    回覆刪除
  4. 經過且從您的文章中獲得啟發
    在此留言表達心中的謝意

    回覆刪除
  5. 請問使用版主的方法 在沒有網路之下 要怎麼讓他不至於當掉
    像是 Toast未連線網際網路就好,讓他不至於整個當掉????

    回覆刪除
    回覆
    1. 查檢網路是否有連線,在有連線的情況下才執行。

      檢查網路連線的方法,我寫了一篇新文章,方便其他人查看。
      在這
      http://blog.tonycube.com/2014/09/android-check-network-connection.html#more

      刪除
  6. 看完您的文章獲益良多。另外有個問題想請教您,關於AsyncTask您是把當寫成Activity 的內部類別嗎??
    因為我比較好奇的是為什麼onPostExecute可以直接存取Activity 的方法??
    先謝謝您囉

    回覆刪除
    回覆
    1. 是內部類別。
      除了 doInBackground 是在另一個執行緒外,其他都和 UI 同一個執行緒,又是內部類別,當然可以存取。

      刪除
  7. TaskAsync是跟著main thread or activity context? , 若是跟著main thread, 跟當前UI溝通就涉及需要知道當前的activity context是哪一個,以這個例子更新當前context UI是沒問題因為就在當前activity context ,不好意思,這問題是看完你文章的想法,試試也許就知道了,你寫得很好,thanks

    回覆刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。