Android探索之旅 | 用AsyncTask實現多執行緒+例項

程式設計師聯盟發表於2017-03-24

Android探索之旅 | 用AsyncTask實現多執行緒+例項

-- 作者 謝恩銘 轉載請註明出處

用AsyncTask實現多執行緒


在Android應用開發中,有時我們需要實現任務的同步。

Android裡的AsyncTask類可以幫我們更好地管理執行緒同步(非同步方式),就像Thread類能做的,不過用法比Thread更簡單。

AsyncTask算是幫我們做了一層封裝吧,使我們可以不用操心那麼多,如果閱讀AsyncTask的原始碼就可以瞭解。

具體AsyncTask的使用方法,最好參看Google Android的官方文件:

developer.android.com/reference/a…

在你開發Android應用程式時,如果有一個耗時任務(通常是一個子執行緒),並且這個任務呼叫了主執行緒,應用就會丟擲著名的“ANR” (Application Not Responding,"應用無響應")錯誤。

Android探索之旅 | 用AsyncTask實現多執行緒+例項
ANR

AsyncTask類可以幫我們解圍,使用AsyncTask能讓我們正確及簡便地使用主執行緒,即使此時另有一個非同步執行緒被建立。

AsyncTask是asynchronous(英語“非同步的”的意思)和task(英語“任務”的意思)的縮寫,表示“非同步任務”。

它使得耗時任務可以在後臺執行,並在前臺(UI執行緒,或稱主執行緒)把執行結果展現出來,不必用到Thread類或Handler類。執行緒間通訊也隨之變得更簡單,優雅。

主執行緒(User Interface Thread,UI執行緒)是在Android裡負責和使用者介面進行互動的執行緒。

AsyncTask是一個抽象類(abstract class),必須被繼承才能例項化。有三個泛型引數,分別是:

  • Params : 傳遞給執行的任務的引數,也就是doInBackground方法的引數。

  • Progress : 後臺任務執行過程中在主執行緒展現更新時傳入的引數,也就是onProgressUpdate方法的引數。

  • Result : 後臺執行的任務返回的結果,也就是onPostExecute方法的引數。

除此之外,繼承AsyncTask類時,一般需要實現四個方法。

當然應用程式不需要呼叫這些方法,這些方法會在任務執行過程中被自動呼叫: onPreExecute, doInBackground, onProgressUpdate 和 onPostExecute:

  • onPreExecute : 此方法在主執行緒中執行,用於初始化任務。

  • doInBackground : 此方法在後臺執行,是一個抽象方法,必須要被子類重寫。此方法在onPreExecute方法執行完後啟動。這個方法中執行的操作可以是耗時的,並不會阻塞主執行緒。通過呼叫publishProgress方法來在主執行緒顯示後臺任務執行的結果更新。

  • onProgressUpdate : 此方法也在主執行緒中執行,每當publishProgress方法被呼叫時,此方法就被執行,此方法只在doInBackground執行過程中才能被呼叫。

  • onPostExecute : 在doInBackground方法執行完之後啟動的方法,在後臺任務結束後才呼叫此方法,也在主執行緒執行。

例項


為了更好地理解AsyncTask的使用,我們來實現一個計時器的小應用。

首先我們建立一個Android專案,就命名為AsyncTaskActivity好了(名字無所謂),修改 res->layout 裡的定義主使用者介面的 xml 檔案(比如是main.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="15dp" >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="5dp"
        android:text="Time in min"
        android:textSize="22sp"
        android:textStyle="bold" />

    <EditText
        android:id="@+id/chronoValue"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginBottom="15dp"
        android:layout_gravity="center"
        android:hint="minutes"
        android:inputType="number"
        android:maxLines="1"
        android:text="1"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/chronoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="0:0"
        android:textSize="80sp" />

    <Button
        android:id="@+id/start"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="Start" />
</LinearLayout>複製程式碼

在以上的main.xml檔案中,我們主要定義了:

  • 一個EditText,用於輸入需要計數的時間

  • 一個TextView,用於顯示計數的變化

  • 一個Button,用於啟動計數任務。

在我們的類AsyncTaskActivity中,我們首先宣告三個private變數,對應以上三個元素。

private EditText chronoValue;
private TextView chronoText;
private Button start;複製程式碼

然後建立一個內部類,繼承AsyncTask類,命名為“Chronograph”,就是英語“秒錶,計時器”的意思。

private class Chronograph extends AsyncTask<Integer, Integer, Void> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // 在計時開始前,先使按鈕和EditText不能用
        chronoValue.setEnabled(false);
        start.setEnabled(false);
        chronoText.setText("0:0");
    }
    @Override
    protected Void doInBackground(Integer... params) {
        // 計時
        for (int i = 0; i <= params[0]; i++) {
            for (int j = 0; j < 60; j++) {
                try {
                    // 釋出增量
                    publishProgress(i, j);
                    if (i == params[0]) {
                        return null;
                    }
                    // 暫停一秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if (isCancelled()) {
            return null;
        }
        return null;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 更新UI介面
        chronoText.setText(values[0] + ":" + values[1]);
    }

    @Override 
    protected void onPostExecute(Void result) {
        super.onPostExecute(result);
        // 重新使按鈕和EditText可以使用
        chronoValue.setEnabled(true);
        start.setEnabled(true);
    }
}複製程式碼

以上,我們重寫了我們需要的四個方法。最後我們再完成我們AsyncTaskActivity類的onCreate方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // 獲取三個UI元件
    start = (Button)findViewById(R.id.start);
    chronoText = (TextView)findViewById(R.id.chronoText);
    chronoValue = (EditText)findViewById(R.id.chronoValue);

    start.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // 獲取EditText裡的數值
            int value = Integer.parseInt(String.valueOf(chronoValue.getText()));
            // 驗證數值是否大於零
            if (value > 0) {
                new Chronograph().execute(value);
            }
            else {
                Toast.makeText(AsyncTaskActivity.this, "請輸入一個大於零的整數值 !", Toast.LENGTH_LONG).show();
            }
        }
    });
}複製程式碼

如果我們在繼承AsyncTask類時,對於三個引數中有不需要的,可以定義為Void型別(注意,與小寫的 void 不同),例如:

private class Chronograph extends AsyncTask<Integer, Integer, Void> {...}複製程式碼

執行我們的專案,可以得到如下面三張圖所示的結果:

Android探索之旅 | 用AsyncTask實現多執行緒+例項
按下Start按鈕前


Android探索之旅 | 用AsyncTask實現多執行緒+例項
計數中


Android探索之旅 | 用AsyncTask實現多執行緒+例項
計數結束後

怎麼樣,AsyncTask不難使用吧~

這個例子專案我發在我的Github上了,請參看:

github.com/frogoscar/A…

總結


  1. 今後,當有非同步任務需要執行時,可以使用AsyncTask類,可以根據自己的需要來定製。

  2. AsyncTask使用了執行緒池(Thread Pool)的機制,使得同時執行多個AsyncTask成為可能。但是要注意的是,這個執行緒池的容量是5個執行緒同時執行,如果超過了這個數量,多餘的執行緒必須等待執行緒池裡的執行緒執行完才能啟動。

  3. 使用AsyncTask,最好在明確知道任務會有一個確定和合理的結束的情況下。否則,還是使用傳統的Thread類為好。

  4. 在doInBackground方法中的耗時操作最好是能保證在幾秒鐘之內完成的,不要做特別久的耗時操作。


人世間,
萬千情感皆有溫度,
千萬程式碼似有性格。
這裡有原創教程,IT叢林......
和你一起探索程式人生。
微信公眾號「程式設計師聯盟」ProgrammerLeague
我是謝恩銘,在巴黎奮鬥的嵌入式軟體工程師。
個人簡介
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:“向著標杆直跑”

相關文章