文章目錄
- 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然後重寫相關方法,其構造方法的參數說明如圖所示:
接下來,AsyncTask中相應方法的執行流程如下:
最後,再給出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>
布局效果大緻如下:
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個參數的用途如下:
-
:在執行AsyncTask時需要傳入的參數,可用于在背景任務中使用。Params
-
:背景任務執行時,如果需要在界面上顯示目前的進度,則使用這裡指定的泛型作為進度機關。Progress
-
:當任務執行完畢後,如果需要對結果進行傳回,則使用這裡指定的泛型作為傳回值類型。Result
本例中已給出了詳盡的注釋,讀者也可參考注釋來配合了解。
在AsyncTask中,比較常用的重寫方法如圖所示:
在本例中,我們僅重寫了AsyncTask中的4個方法,這4個方法相對來說更加重要,它們的作用如下所示:
-
這個方法會在背景任務開始執行之前調用,用于進行一些界面上的初始化操作,比如顯示一個進度條對話框等。onPreExecute()
-
doInBackground(Params…)
,該方法強制要求重寫
這個方法中的所有代碼都會在子線程中運作,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來将任務的執行結果傳回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不傳回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說回報目前任務的執行進度,可以調用
方法來完成。publishProgress(Progress…)
-
當在背景任務中調用了onProgressUpdate(Progress…)
方法後,publishProgress(Progress…)
方法就會很快被調用,該方法中攜帶的參數就是在背景任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。onProgressUpdate (Progress…)
-
當背景任務執行完畢并通過return語句進行傳回時,這個方法就很快會被調用。傳回的資料會作為參數傳遞到此方法中,可以利用傳回的資料來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。onPostExecute(Result)
這裡為了友善示範,使用了
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);
}
}
完成以上兩步後,進度條的業務應該就已經實作了。可以運作一下項目,然後檢視效果,如圖所示:
3.5 注意事項
- 生命周期:AsyncTask不與任何元件綁定生命周期,使用時應該如同本例一樣在
或者Activity
的Fragment
中調用onDestroy()
來關閉AsyncTask;cancel(boolean)
- 記憶體洩漏:若AsyncTask被聲明為
的非靜态内部類,當Activity
需銷毀時,會因AsyncTask保留對Activity
的引用,而導緻Activity無法被回收,最終引起記憶體洩露,使用時應将AsyncTask聲明為Activity
的靜态内部類;Activity
- 線程執行結果:當
重新建立時(螢幕旋轉 /Activity
被意外銷毀時後恢複),之前運作的AsyncTask(非靜态的内部類)持有的之前Activity引用已無效,故複寫的Activity
将不生效,即無法更新UI操作,使用時需要在onPostExecute()
恢複時的對應方法重新開機任務線程。Activity