引言:搞 Android 這么久了,一直沒有主動(dòng)去學(xué)習(xí)使用 AsyncTask ,現(xiàn)在應(yīng)該很少有人在使用了,但面試中似乎總有人會(huì)問。最近一不小心在某項(xiàng)目中看到了相關(guān)的代碼,就決定對(duì) AsyncTask 進(jìn)行一番學(xué)習(xí)。
時(shí)間:2016年11月20日11:51:41
作者:JustDo23
01. 背景
首先,Android 中子線程是不能進(jìn)行 UI 操作的。單線程操作避免了 UI 混亂的情況。其次,在主線程不能進(jìn)行耗時(shí)操作,否則會(huì)阻塞主線程,造成 ANR。因此,將耗時(shí)操作放在子線程成為了必然,子線程將處理的結(jié)果交給 UI 線程進(jìn)行 UI 更新,線程間通信必不可少。說到這里,最先想起來的就是 Android 中的 Handler 消息處理機(jī)制,除此之外 Android 中還封裝了一個(gè)抽象類 AsyncTask。AsyncTask其實(shí)是對(duì)Handler和Thread的封裝,再者就是AsyncTask內(nèi)部是線程池實(shí)現(xiàn)。
在單線程模式中需要始終明確兩條:
- UI 線程中能耗時(shí)操作,不能被阻塞
- 非 UI 線程不能直接去更新 UI
02. 介紹
官方網(wǎng)站上介紹到AsyncTask是方便的線程操作,允許在后臺(tái)進(jìn)行操作并將結(jié)果返回給 UI 線程。建議在AsyncTask中執(zhí)行比較短的操作,如果是灰?;页:臅r(shí)的操作則強(qiáng)烈建議使用Executor等線程池進(jìn)行。一個(gè)異步任務(wù)定義3個(gè)泛型參數(shù)Params,Progress,Result,4個(gè)步驟onPreExecute,doInBackground,onProgressUpdate,onPostExecute;三個(gè)泛型就是啟動(dòng)參數(shù),任務(wù)進(jìn)度,返回結(jié)果,四個(gè)步驟就是啟動(dòng)準(zhǔn)備,后臺(tái)執(zhí)行,任務(wù)進(jìn)度,任務(wù)結(jié)果。
03. 入門代碼
新建一個(gè)SimpleTask繼承AsyncTak,接著需要指定三個(gè)泛型,看到報(bào)錯(cuò)提示必須重寫doInBackground方法。然后繼續(xù)重寫其他的方法。
/**
* 簡(jiǎn)單使用
*
* @author JustDo23
*/
public class SimpleTask extends AsyncTask<Void, Void, Void> {
/**
* 異步操作執(zhí)行前初始化操作
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
LogUtil.e("--->onPreExecute()");
}
/**
* 異步執(zhí)行后臺(tái)線程將要完成的任務(wù)[這個(gè)是必須重寫的方法]
*
* @param params 泛型中的參數(shù)
*/
@Override
protected Void doInBackground(Void... params) {
LogUtil.e("--->doInBackground()");
publishProgress();// 進(jìn)行進(jìn)度更新
return null;
}
/**
* 異步任務(wù)[doInBackground]完成后系統(tǒng)自動(dòng)回調(diào)
*
* @param result [doInBackground]返回的結(jié)果
*/
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
LogUtil.e("--->onPostExecute()");
}
/**
* 進(jìn)度更新 [doInBackground]方法中調(diào)用 publishProgress 方法后執(zhí)行
*
* @param progress
*/
@Override
protected void onProgressUpdate(Void... progress) {
super.onProgressUpdate(progress);
LogUtil.e("--->onProgressUpdate()");
}
@Override
protected void onCancelled(Void aVoid) {
super.onCancelled(aVoid);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
在 Activity 中的onCreate方法中進(jìn)行調(diào)用
new SimpleTask().execute();
打印出執(zhí)行步驟
E/JustDo23: --->onPreExecute()
E/JustDo23: --->doInBackground()
E/JustDo23: --->onProgressUpdate()
E/JustDo23: --->onPostExecute()
04. 異步加載網(wǎng)絡(luò)圖片
布局代碼
<?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"
android:orientation="vertical"
android:padding="20dp">
<ImageView
android:id="@+id/iv_net"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>
網(wǎng)絡(luò)加載代碼
/**
* 展示圖片
*
* @author JustDo23
*/
public class ImageShowActivity extends Activity {
private ImageView iv_net;// 圖片
private ProgressBar pb_loading;// 加載圈
private String imageUrl = "http://img4.duitang.com/uploads/item/201611/03/20161103224830_YGisc.thumb.700_0.jpeg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_show);
iv_net = (ImageView) findViewById(R.id.iv_net);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
new ImageTask().execute(imageUrl);// 在這里進(jìn)行調(diào)用
}
class ImageTask extends AsyncTask<String, Integer, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
pb_loading.setVisibility(View.VISIBLE);// 先將 loading 顯示
}
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];// 從參數(shù)中獲取網(wǎng)絡(luò)地址
Bitmap bitmap = null;// 從網(wǎng)絡(luò)加載的圖片
try {
Thread.sleep(3000);// 以下是進(jìn)行網(wǎng)絡(luò)獲取圖片
URLConnection urlConnection = new URL(url).openConnection();
InputStream inputStream = urlConnection.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
inputStream.close();
bufferedInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;// 將加載的圖片返回
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
pb_loading.setVisibility(View.GONE);// 將 loading 隱藏
iv_net.setImageBitmap(bitmap);// 界面上設(shè)置顯示圖片
}
}
}
05. 進(jìn)度條顯示進(jìn)度
使用水平的進(jìn)度條和一個(gè) for 循環(huán)來模擬一下網(wǎng)絡(luò)加載進(jìn)度及界面的更新
/**
* 利用 AsyncTask 實(shí)現(xiàn)進(jìn)度加載顯示
*
* @author JustDo23
*/
public class LoadingActivity extends Activity {
private ProgressBar pb_loading;// 加載條
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acitvity_loading);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
new LoadingTask().execute();
}
class LoadingTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
try {// 循環(huán)模擬加載進(jìn)度
for (int i = 0; i <= 100; i++) {
LogUtil.e("progress = " + i);
publishProgress(i);// 去更新界面
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
pb_loading.setProgress(progress[0]);
}
}
}
頻繁關(guān)閉啟動(dòng)界面,發(fā)現(xiàn)界面上的進(jìn)度條有時(shí)會(huì)一直沒有進(jìn)度;在日志打印過程中,發(fā)現(xiàn)AsyncTask是從0到100的打印,一圈打印完了再打印第二圈。也就是AsyncTask內(nèi)部維護(hù)的 task 是串行的,一個(gè)執(zhí)行完了,再去執(zhí)行下一個(gè)。
06. 優(yōu)化
public class LoadingActivity extends Activity {
private ProgressBar pb_loading;// 加載條
private LoadingTask loadingTask;// 異步加載任務(wù)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acitvity_loading);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
loadingTask = new LoadingTask();
loadingTask.execute();
}
@Override
protected void onPause() {
super.onPause();
if (loadingTask != null && loadingTask.getStatus() == AsyncTask.Status.RUNNING) {
loadingTask.cancel(true);// cancel 方法只是將對(duì)應(yīng)的 AsyncTask 標(biāo)記為了取消狀態(tài),并不是真的取消線程執(zhí)行了。
}
}
class LoadingTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
try {// 循環(huán)模擬加載進(jìn)度
for (int i = 0; i <= 100; i++) {
if (isCancelled()) {
break;// 如果是取消狀態(tài)就直接跳出自動(dòng)結(jié)束線程
}
LogUtil.e("progress = " + i);
publishProgress(i);// 去更新界面
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (isCancelled()) {
return;// 如果是取消狀態(tài)就直接返回
}
pb_loading.setProgress(progress[0]);
}
}
}
注意:cancel 方法只是將對(duì)應(yīng)的 AsyncTask 標(biāo)記為了取消狀態(tài),并不是真的取消線程執(zhí)行了。
到這里,便對(duì)AsyncTask有了一個(gè)基礎(chǔ)的入門。以上的學(xué)習(xí)主要參考慕課網(wǎng)視頻Android必學(xué)-AsyncTask基礎(chǔ)
07. 串并行
網(wǎng)上有很多關(guān)于AsyncTask的串并行問題介紹,起初AsyncTask是串行的,一個(gè)一個(gè)的執(zhí)行;接著修改成了并行,多線程并發(fā)執(zhí)行;接著又修改成了支持串行和并行,我們直接調(diào)用execute(Params... params)是進(jìn)行串行,調(diào)用executeOnExecutor(Executor exec, Params... params)是并傳遞類型,來選擇進(jìn)行并行還是串行。其實(shí)點(diǎn)開execute源碼
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
從中可以看到是同一方法。參數(shù)有:AsyncTask.SERIAL_EXECUTOR和AsyncTask.THREAD_POOL_EXECUTOR;第一個(gè)是默認(rèn)的,串行操作;第二個(gè)是進(jìn)行并行操作。
08. 強(qiáng)調(diào)
- 異步任務(wù)的實(shí)例必須在 UI 線程中創(chuàng)建
-
execute(Params... params)方法必須在 UI 線程中調(diào)用 - 不要手動(dòng)回調(diào)
onPreExecute(),doInBackground(Params... params),onPostExecute(Result result),onProgressUpdate(Progress... values)這四個(gè)方法 - 不能在
doInBackground(Params... params)方法中更新 UI - 一個(gè)任務(wù)實(shí)例只能執(zhí)行一次,第二次執(zhí)行就會(huì)拋出異常
java.lang.IllegalStateException: Cannot execute task: the task is already running
09. 小綜合
參考 CSDN 上的文章詳解Android中AsyncTask的使用自己動(dòng)手寫了一個(gè)完整的 Demo
- 在執(zhí)行
AsyncTask.cancel(true);的時(shí)候先回調(diào)onCancelled()然后再回調(diào)onCancelled(Result result)點(diǎn)擊看下源碼onCancelled(Result result)的內(nèi)部實(shí)現(xiàn)是直接調(diào)用onCancelled() - 在執(zhí)行
AsyncTask.cancel(true);后,onPostExecute(Result result)方法是沒有進(jìn)行回調(diào)的 - AsyncTask 的狀態(tài)被存放在一個(gè)枚舉
Status中,總共有三種狀態(tài)FINISHED,PENDING,RUNNING - 點(diǎn)擊查看一下
execute(Params... params)方法就會(huì)明白為啥只能執(zhí)行一次
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
//如果該任務(wù)正在被執(zhí)行則拋出異常
//值得一提的是,在調(diào)用cancel取消任務(wù)后,狀態(tài)仍未RUNNING
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
//如果該任務(wù)已經(jīng)執(zhí)行完成則拋出異常
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
//改變狀態(tài)為RUNNING
mStatus = Status.RUNNING;
//調(diào)用onPreExecute方法
onPreExecute();
mWorker.mParams = params;
sExecutor.execute(mFuture);
return this;
}
更多源碼解析可以到文章詳解Android中AsyncTask的使用中查看