AsyncTask:異步任務
在前面的章節有提到過,Android 系統默認會在主線程(UI 線程)執行任務,但是如果有耗時程序就會阻塞 UI 線程,導致頁面卡頓。這時候我們通常會將耗時任務放在獨立的線程,然后通過 Handler 等線程間通信機制完成 UI 的刷新。很多時候我們也許只是想執行一個簡單的任務,為此寫一套 Handler 線程通信就會顯得比較復雜,不用擔心,Android 系統為我們提供了一個專門用于執行異步任務的工具——Async Task,它可以讓我們執行后臺任務并輕松的與 UI 線程進行狀態同步,今天就一起來學習一下 AyncTask 的用法。
1. AsyncTask 簡介
AsyncTask 類通常用來在執行后臺任務的同時刷新 UI,通過調用execute()
方法觸發后臺任務的執行,首先會回調 AsyncTask 的onPreExecute()
,接著回調doInBackground()
來執行自定義的后臺任務,最后回調onPostExecute()
方法用來刷新 UI,整體流程示意圖如下:
2. AsyncTask 的基本用法
2.1 聲明 AsyncTask
我們不能直接創建 AsyncTask,正確的方法是繼承自 AsyncTask 實現一個它的子類,如下:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
// 執行后臺耗時任務
return;
}
protected void onProgressUpdate(Integer... progress) {
// 任務執行進度更新
}
protected void onPostExecute(Long result) {
// 執行完畢,更新UI
}
}
以上是 AsyncTask 的核心回調方法,每個方法的含義會在后面具體講到。
2.2 指定參數
AsyncTask 可以幫助你在子線程和主線程之間同步參數,根據不同的業務場景,參數類型和個數也會不一樣。剛剛在 2.1 小節聲明 AyncTask 子類的時候,需要傳入 3 個泛型參數:
-
TypeOfVarArgParams:
在任務啟動之后,傳入給后臺任務的參數類型 -
ProgressValue:
啟動之后到任務結束之間,系統會不斷回調此方法,用來更新任務的進度 -
** ResultValue:**
后臺任務的執行結果
2.3 啟動后臺任務
在聲明完后臺任務之后,就可以直接啟動了。啟動方式比較簡單,直接通過調用execute()
方法啟動后臺任務:
new DownloadFilesTask().execute(url1, url2, url3);
3 AsyncTask 關鍵回調方法
AsyncTask 是由 4 個回調方法配合組成,這 4 個回調方法按照一定的順序依次被調用,所以我們需要 focus 的是在正確的回調方法中實現我們想要的邏輯,下面詳細看看如何使用。
- onPreExecute():
在執行execute()
方法之后該方法立即被調用,標志著 AyncTask 正式開啟。通常用來做一些需要在后臺任務開啟之前完成的初始化工作,比如展示一個進度條、或者彈出一個對話框等等。該方法在 UI 線程執行。 - doInBackground(Params):
在執行完onPreExecute()
方法之后立即被調用,用來執行需要放在后臺執行的耗時任務。在創建 AsyncTask 的時候傳入的參數就是提供給doInBackground
使用的。在后臺任務執行完畢后,還需要將執行結果返回到onPostExecutes ()
中,同時我們也可以通過publishProgress(Progress…)
方法來手動發布任務進度,進度將從子線程發送到 UI 線程。毋庸置疑,該方法在子線程中執行。 - onProgressUpdate(Progress…):
當我們通過publishProgress(Params)
發布進度之后,系統會回調該方法,用來獲取任務執行進度并更新 UI。這一步就完成了子線程到主線程的通信,該方法在 UI 線程執行 - onPostExecute(Result):
當后臺任務執行完畢,該方法被回調,同時標志著整個 AyncTask 結束。與onPreExecute
相反,通常會在onPostExecute
中做一些回收工作,比如提示“下載完成”、“加載失敗”、隱藏進度條等等。
4 AsyncTask 示例
本節的功能和第 38 節 Handler 的功能類似,我們還是完成一個后臺執行耗時任務,并同步更新進度條的功能,可以直接在 Handler 的例子上修改。
4.1 布局文件
布局文件基本一樣,只是默認將進度條和進度顯示隱藏起來,等到onPreExecute()
方法的時候在做初始化展示,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:visibility="gone"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<Button
android:id="@+id/start_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar"
android:layout_alignParentStart="true"
android:layout_marginStart="24dp"
android:layout_marginTop="62dp"
android:text="開始任務" />
<TextView
android:visibility="gone"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/start_progress"
android:layout_alignBottom="@+id/start_progress"
android:layout_alignParentEnd="true"
android:layout_marginEnd="85dp"
android:gravity="center"
android:text="當前進度:0%"
android:textSize="16sp" />
</RelativeLayout>
4.2 異步任務控制
異步任務的代碼基本上和 Handler 一致,我們通過Thread.sleep(1000)
來模擬一秒鐘的耗時操作,然后在onPreExecute()
中展示進度條,在后臺任務執行完后通過publishProgress(int)
來將任務進度發送到主線程,由onProgressUpdate(int)
方法在 UI 線程接收進度,并更新進度條。最后在結束的時候返回“已完成”提示,在onPostExecute(String)
方法中進行收尾工作,隱藏進度條并展示“已完成”。代碼如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final int MAX = 100;
private ProgressBar progressBar;
private Button startProgress;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
startProgress = findViewById(R.id.start_progress);
textView = findViewById(R.id.textView);
progressBar.setMax(MAX);
startProgress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new DownloadTask().execute();
}
});
}
// 1、創建Async Task子類
private class DownloadTask extends AsyncTask<Integer, Integer, String> {
// 2、初始化階段,展示進度條
@Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
textView.setVisibility(View.VISIBLE);
}
// 3、執行后臺任務
@Override
protected String doInBackground(Integer... integers) {
int i;
for (i = 0; i < 100; i++) {
try {
// 一秒鐘的耗時操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4、發布進度
publishProgress(i);
}
return "異步任務已完成";
}
// 5、接收后臺任務數據并更新進度條
protected void onProgressUpdate(Integer... values) {
textView.setText("當前進度:" + values[0] + "%");
progressBar.setProgress(values[0]);
}
// 6、任務結束
@Override
protected void onPostExecute(String s) {
progressBar.setVisibility(View.GONE);
textView.setText(s);
}
}
}
編譯之后,效果如下:
可以看到大體的效果和 Handler 是一樣的,但是整個異步任務的處理流程更加清晰了,而且可以很方便的做任務前的初始化及任務后的回收工作,整個線程的切換也不再需要我們去控制,全部交給 AsyncTask 操作就行,一切就是這么簡單!
5 小結
本節介紹了一個專門用于執行異步任務的工具類,首先需要創建一個子類繼承自 AsyncTask,然后確定好需要傳入和返回的參數類型,接著覆寫 4 個關鍵的方法,剩下的線程切換及數據傳輸就交給系統完成了。
相比 Handler,AsyncTask使用更簡單,我們只需要關注我們的后臺邏輯,而 Handler 需要我們自行管理的東西比較多。所以在執行后臺任務刷新 UI 的場景,強烈推薦使用 AsyncTask,可以讓我們的開發更加順暢。