Android探索之旅 | 用AsyncTask實現(xiàn)多線程+實例

-- 作者 謝恩銘 轉(zhuǎn)載請注明出處

用AsyncTask實現(xiàn)多線程


在Android應(yīng)用開發(fā)中,有時我們需要實現(xiàn)任務(wù)的同步。

Android里的AsyncTask類可以幫我們更好地管理線程同步(異步方式),就像Thread類能做的,不過用法比Thread更簡單。

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

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

https://developer.android.com/reference/android/os/AsyncTask.html

在你開發(fā)Android應(yīng)用程序時,如果有一個耗時任務(wù)(通常是一個子線程),并且這個任務(wù)調(diào)用了主線程,應(yīng)用就會拋出著名的“ANR” (Application Not Responding,"應(yīng)用無響應(yīng)")錯誤。

ANR

AsyncTask類可以幫我們解圍,使用AsyncTask能讓我們正確及簡便地使用主線程,即使此時另有一個異步線程被創(chuàng)建。

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

它使得耗時任務(wù)可以在后臺執(zhí)行,并在前臺(UI線程,或稱主線程)把執(zhí)行結(jié)果展現(xiàn)出來,不必用到Thread類或Handler類。線程間通信也隨之變得更簡單,優(yōu)雅。

主線程(User Interface Thread,UI線程)是在Android里負責(zé)和用戶界面進行交互的線程。

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

  • Params : 傳遞給執(zhí)行的任務(wù)的參數(shù),也就是doInBackground方法的參數(shù)。

  • Progress : 后臺任務(wù)執(zhí)行過程中在主線程展現(xiàn)更新時傳入的參數(shù),也就是onProgressUpdate方法的參數(shù)。

  • Result : 后臺執(zhí)行的任務(wù)返回的結(jié)果,也就是onPostExecute方法的參數(shù)。

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

當(dāng)然應(yīng)用程序不需要調(diào)用這些方法,這些方法會在任務(wù)執(zhí)行過程中被自動調(diào)用: onPreExecute, doInBackground, onProgressUpdate 和 onPostExecute:

  • onPreExecute : 此方法在主線程中執(zhí)行,用于初始化任務(wù)。

  • doInBackground : 此方法在后臺執(zhí)行,是一個抽象方法,必須要被子類重寫。此方法在onPreExecute方法執(zhí)行完后啟動。這個方法中執(zhí)行的操作可以是耗時的,并不會阻塞主線程。通過調(diào)用publishProgress方法來在主線程顯示后臺任務(wù)執(zhí)行的結(jié)果更新。

  • onProgressUpdate : 此方法也在主線程中執(zhí)行,每當(dāng)publishProgress方法被調(diào)用時,此方法就被執(zhí)行,此方法只在doInBackground執(zhí)行過程中才能被調(diào)用。

  • onPostExecute : 在doInBackground方法執(zhí)行完之后啟動的方法,在后臺任務(wù)結(jié)束后才調(diào)用此方法,也在主線程執(zhí)行。

實例


為了更好地理解AsyncTask的使用,我們來實現(xiàn)一個計時器的小應(yīng)用。

首先我們創(chuàng)建一個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,用于輸入需要計數(shù)的時間

  • 一個TextView,用于顯示計數(shù)的變化

  • 一個Button,用于啟動計數(shù)任務(wù)。

在我們的類AsyncTaskActivity中,我們首先聲明三個private變量,對應(yīng)以上三個元素。

private EditText chronoValue;
private TextView chronoText;
private Button start;

然后創(chuàng)建一個內(nèi)部類,繼承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 {
                    // 發(fā)布增量
                    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里的數(shù)值
            int value = Integer.parseInt(String.valueOf(chronoValue.getText()));
            // 驗證數(shù)值是否大于零
            if (value > 0) {
                new Chronograph().execute(value);
            }
            else {
                Toast.makeText(AsyncTaskActivity.this, "請輸入一個大于零的整數(shù)值 !", Toast.LENGTH_LONG).show();
            }
        }
    });
}

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

private class Chronograph extends AsyncTask<Integer, Integer, Void> {...}

運行我們的項目,可以得到如下面三張圖所示的結(jié)果:

按下Start按鈕前

計數(shù)中

計數(shù)結(jié)束后

怎么樣,AsyncTask不難使用吧~

這個例子項目我發(fā)在我的Github上了,請參看:

https://github.com/frogoscar/AsyncTaskExample

總結(jié)


  1. 今后,當(dāng)有異步任務(wù)需要執(zhí)行時,可以使用AsyncTask類,可以根據(jù)自己的需要來定制。

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

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

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


我是謝恩銘,在巴黎奮斗的軟件工程師。
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:「向著標(biāo)桿直跑」

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,068評論 25 709
  • Android Handler機制系列文章整體內(nèi)容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 3,422評論 1 15
  • 在Android中我們可以通過Thread+Handler實現(xiàn)多線程通信,一種經(jīng)典的使用場景是:在新線程中進行耗時...
    呂侯爺閱讀 2,178評論 2 23
  • 其一 十年前,鄰居家中五歲小兒找我玩耍。無意間從抽屜里翻將出一封信,因好奇問道,這是什么? 我說,一封信。 孩子的...
    輪回夢里閱讀 817評論 6 2
  • 一時書 智庫捐款一元 白血病兒童捐款兩元 二時書 友善的語言和鄰居溝通 水道漏水,曾經(jīng)種下破壞他人財產(chǎn)的種子。明智...
    我不叫許仲斌閱讀 305評論 0 0

友情鏈接更多精彩內(nèi)容