天天看點

每天學習一個Android中的常用架構——13.AsyncTask

文章目錄

  • ​​1.簡介​​
  • ​​2.特性​​
  • ​​3.示範​​
  • ​​3.1 內建​​
  • ​​3.2 布局檔案​​
  • ​​3.3 建立AsyncTask子類​​
  • ​​3.4 擷取AsyncTask對象并執行任務​​
  • ​​3.5 注意事項​​
  • ​​4.源碼位址​​

1.簡介

AsyncTask,是除了Handler之外,Android提供給我們友善地在子線程中對UI進行操作的另一個工具。借助AsyncTask,即使你對異步消息處理機制完全不了解,也可以十分簡單地從子線程切換到主線程。當然,AsyncTask背後的實作原理也是基于異步消息處理機制的,隻是Android幫我們做了很好的封裝而已。

AsyncTask屬于已經封裝好的輕量級異步類,主要的功能仍然是多線程通信以及消息處理。其優點主要為:

  • 友善實作異步通信:不需要使用 “任務線程(如繼承​

    ​Thread​

    ​​類) +​

    ​Handler​

    ​”的複雜組合
  • 節省資源:采用線程池的緩存線程 + 複用線程,避免了頻繁建立 & 銷毀線程所帶來的系統資源開銷。

下面我們再來看看AsyncTask擁有的一些特性。

2.特性

在菜鳥教程中對于AsyncTask的介紹中,寫得較為詳細,這裡就直接貼出部分說明圖,供讀者參考:

首先,AsyncTask是一個抽象類,一般我們都會定義一個類繼承AsyncTask然後重寫相關方法,其構造方法的參數說明如圖所示:

每天學習一個Android中的常用架構——13.AsyncTask

接下來,AsyncTask中相應方法的執行流程如下:

每天學習一個Android中的常用架構——13.AsyncTask

最後,再給出AsyncTask的使用注意事項,如下所示:

每天學習一個Android中的常用架構——13.AsyncTask

直接說明這些概念可能會讓初次學習AsyncTask的讀者覺得費解,接下來就會示範AsyncTask最經典的應用案例:進度條來說明AsyncTask的使用方法。

3.示範

3.1 內建

AsyncTask和Handler一樣,預設就是內建在Android SDK(具體來說,在Android 1.5版本之後)中的,是以這一步可以省略,你可以在代碼中直接使用AsyncTask來處理消息機制。

3.2 布局檔案

為了實作進度條的功能,在布局檔案activity_main.xml中,我們定義一個按鈕控件,用來觸發進度條的運動,然後再定義一個進度條控件(ProgressBar),最後再定義一個文本控件,記錄進度條的進度值,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/pb_test"
        android:layout_width="match_parent"
        android:layout_height="50dp"style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:max="100"
        android:progress="0"/>

    <Button
        android:id="@+id/btn_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="啟動進度條"/>

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="100sp"
        android:gravity="center"
        android:text="進度值"/>


</LinearLayout>      

布局效果大緻如下:

每天學習一個Android中的常用架構——13.AsyncTask

3.3 建立AsyncTask子類

前面說過,AsyncTask是一個抽象類。要想使用,就需要先建立一個內建它的子類。這裡建立一個名為ProgressAsyncTask的AsyncTask子類,然後重寫相應的方法,代碼如下:

/**
 * AsyncTask<Params, Progress, Result>三個泛型參數
 * Params
 * 在執行AsyncTask時需要傳入的參數,可用于在背景任務中使用。本例中第一個泛型參數指定為Void,表示在執行AsyncTask的時候不需要傳入參數給背景任務。
 * Progress
 * 背景任務執行時,如果需要在界面上顯示目前的進度,則使用這裡指定的泛型作為進度機關。本例中第二個泛型參數指定為Integer,表示使用整型資料來作為進度顯示機關。
 * Result
 * 當任務執行完畢後,如果需要對結果進行傳回,則使用這裡指定的泛型作為傳回值類型。本例中第三個泛型參數指定為Boolean,則表示使用布爾型資料來回報執行結果。
 */
public class ProgressAsyncTask extends AsyncTask<Void,Integer,Boolean> {

    // 定義ProgressBar
    private ProgressBar pb_test;

    // 定義文本控件
    private TextView tv_test;

    public ProgressAsyncTask(ProgressBar pb_test, TextView tv_test) {
        super();
        this.pb_test = pb_test;
        this.tv_test = tv_test;
    }

    /**
     * 這個方法會在背景任務開始執行之間調用,用于進行一些界面上的初始化操作,
     * 比如顯示一個進度條對話框等。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 這個方法中的所有代碼都會在子線程中運作,我們應該在這裡去處理所有的耗時任務。
     * 任務一旦完成就可以通過return語句來将任務的執行結果進行傳回,如果AsyncTask的
     * 第三個泛型參數指定的是Void,就可以不傳回任務執行結果。注意,在這個方法中是不
     * 可以進行UI操作的,如果需要更新UI元素,比如說回報目前任務的執行進度,可以調用
     * publishProgress(Progress...)方法來完成。
     */
    @Override
    protected Boolean doInBackground(Void... voids) {
        for (int i = 0; i < 100; i++) {
            try {
                // 模拟耗時操作
                Thread.sleep(100);
                publishProgress(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    /**
     * 當在背景任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用,
     * 方法中攜帶的參數就是在背景任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參
     * 數中的數值就可以對界面元素進行相應的更新。
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int value = values[0];
        pb_test.setProgress(value);
        tv_test.setText(value + "");
    }

    /**
     * 當背景任務執行完畢并通過return語句進行傳回時,這個方法就很快會被調用。傳回的資料
     * 會作為參數傳遞到此方法中,可以利用傳回的資料來進行一些UI操作,比如說提醒任務執行
     * 的結果,以及關閉掉進度條對話框等。
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        tv_test.setText("進度條加載完畢!");
    }
}      

我們首先來看一下AsyncTask的基本格式。由于AsyncTask是一個抽象類,是以如果我們想使用它,就必須要建立一個子類去繼承它。在繼承時我們需要為AsyncTask類指定3個泛型參數,這3個參數的用途如下:

  • ​Params​

    ​:在執行AsyncTask時需要傳入的參數,可用于在背景任務中使用。
  • ​Progress​

    ​:背景任務執行時,如果需要在界面上顯示目前的進度,則使用這裡指定的泛型作為進度機關。
  • ​Result​

    ​:當任務執行完畢後,如果需要對結果進行傳回,則使用這裡指定的泛型作為傳回值類型。

本例中已給出了詳盡的注釋,讀者也可參考注釋來配合了解。

在AsyncTask中,比較常用的重寫方法如圖所示:

每天學習一個Android中的常用架構——13.AsyncTask

在本例中,我們僅重寫了AsyncTask中的4個方法,這4個方法相對來說更加重要,它們的作用如下所示:

  • ​onPreExecute()​

    ​ 這個方法會在背景任務開始執行之前調用,用于進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
  • ​doInBackground(Params…)​

    ​,該方法強制要求重寫

    這個方法中的所有代碼都會在子線程中運作,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來将任務的執行結果傳回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不傳回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說回報目前任務的執行進度,可以調用​​

    ​publishProgress(Progress…)​

    ​方法來完成。
  • ​onProgressUpdate(Progress…)​

    ​​ 當在背景任務中調用了​

    ​publishProgress(Progress…)​

    ​方法後,​

    ​onProgressUpdate (Progress…)​

    ​方法就會很快被調用,該方法中攜帶的參數就是在背景任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。
  • ​onPostExecute(Result)​

    ​ 當背景任務執行完畢并通過return語句進行傳回時,這個方法就很快會被調用。傳回的資料會作為參數傳遞到此方法中,可以利用傳回的資料來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

這裡為了友善示範,使用了​

​Thread.sleep(100)​

​模拟耗時操作。

簡單來說,使用AsyncTask的步驟就是,在​

​doInBackground()​

​​方法中執行具體的耗時任務, 在​

​onProgressUpdate()​

​​方法中進行UI操作,在​

​onPostExecute()​

​方法中執行一些任務的收尾工作。

3.4 擷取AsyncTask對象并執行任務

當我們建立好AsyncTask的執行個體後,接下來就是擷取其對象,然後調用AsyncTask中的​

​execute()​

​來執行這個任務。MainActivity的代碼如下:

package com.androidframelearn.event_asynctask;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // 定義ProgressBar
    private ProgressBar pb_test;

    // 定義文本控件
    private TextView tv_test;

    // 定義按鈕
    private Button btn_test;

    // 定義ProgressAsyncTask
    private ProgressAsyncTask mProgressAsyncTask;

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

        // 初始化UI
        initUI();

        // 注冊點選事件
        btn_test.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mProgressAsyncTask = new ProgressAsyncTask(pb_test, tv_test);
                mProgressAsyncTask.execute();
            }
        });
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        pb_test = findViewById(R.id.pb_test);
        tv_test = findViewById(R.id.tv_test);
        btn_test = findViewById(R.id.btn_test);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 關閉AsyncTask,防止記憶體洩漏
        mProgressAsyncTask.cancel(true);
    }
}      

完成以上兩步後,進度條的業務應該就已經實作了。可以運作一下項目,然後檢視效果,如圖所示:

每天學習一個Android中的常用架構——13.AsyncTask
每天學習一個Android中的常用架構——13.AsyncTask

3.5 注意事項

  1. 生命周期:AsyncTask不與任何元件綁定生命周期,使用時應該如同本例一樣在​

    ​Activity​

    ​​或者​

    ​Fragment​

    ​​的​

    ​onDestroy()​

    ​​中調用​

    ​cancel(boolean)​

    ​來關閉AsyncTask;
  2. 記憶體洩漏:若AsyncTask被聲明為​

    ​Activity​

    ​​的非靜态内部類,當​

    ​Activity​

    ​​需銷毀時,會因AsyncTask保留對​

    ​Activity​

    ​的引用,而導緻Activity無法被回收,最終引起記憶體洩露,使用時應将AsyncTask聲明為​

    ​Activity​

    ​的靜态内部類;
  3. 線程執行結果:當​

    ​Activity​

    ​​重新建立時(螢幕旋轉 /​

    ​Activity​

    ​被意外銷毀時後恢複),之前運作的AsyncTask(非靜态的内部類)持有的之前Activity引用已無效,故複寫的​

    ​onPostExecute()​

    ​​将不生效,即無法更新UI操作,使用時需要在​

    ​Activity​

    ​恢複時的對應方法重新開機任務線程。

4.源碼位址