引言:搞 Android 這么久了,一直沒有主動(dòng)去學(xué)習(xí)使用 AsyncTask ,現(xiàn)在應(yīng)該很少有人在使用了,但面試中似乎總有人會(huì)問。最近一不小心在某項(xiàng)目中看到了相關(guān)的代碼,就決定對 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í)是對Handler
和Thread
的封裝,再者就是AsyncTask
內(nèi)部是線程池
實(shí)現(xiàn)。
在單線程模式中需要始終明確兩條:
- UI 線程中能耗時(shí)操作,不能被阻塞
- 非 UI 線程不能直接去更新 UI
02. 介紹
官方網(wǎng)站上介紹到AsyncTask
是方便的線程操作,允許在后臺(tái)進(jìn)行操作并將結(jié)果返回給 UI 線程。建議在AsyncTask
中執(zhí)行比較短的操作,如果是灰常灰常耗時(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ù)重寫其他的方法。
/**
* 簡單使用
*
* @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 方法只是將對應(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 方法只是將對應(yīng)的 AsyncTask 標(biāo)記為了取消狀態(tài),并不是真的取消線程執(zhí)行了。
到這里,便對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的使用中查看