為什麼要使用異步任務?
- Android 單線程模型,多線程的作業系統
- 耗時操作放在非主線程中運作
AsyncTask 為何而生?
- 子線程中更新UI
- 封裝簡化異步操作
建構AsyncTask子類的參數
AsyncTask
建構AsyncTask子類的的回調方法
doInBackground(): 必須重寫,異步執行背景線程将要完成的任務
onPreExecute(): 執行背景線程前被調用,通常用來做一些初始化操作
onPostExecute(): 當doInBackground() 方法完成後系統會自動調 用,并将doInBackground() 方法的傳回值作為參數春遞給onPostExecute()方法
onProgressUpdate(): 在doBackground() 方法中調用publishProgress()方法更新任務的執行進度後,就會調用該方法
接下來我們寫個程式測試一下這些方法的執行順序
首先建立一個AsyncTask的子類 MyAsyncTask
public class MyAsyncTask extends AsyncTask<Void, Void, Void>{
String LOGCAT = "LOGCAT";
@Override
protected Void doInBackground(Void... params) {
Log.d(LOGCAT, "doInBackground------------");
System.out.println("doInBackground------------");
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(LOGCAT, "onPreExecute");
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
Log.d(LOGCAT, "onPostExecute");
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Log.d(LOGCAT, "onProgressUpdate");
}
}
在 MainActivity 中進行測試
public class MainActivity extends Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyAsyncTask asyncTask = new
在模拟器上部署運作之後,檢視Logcat 可以看到下面的日志
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5yNyUzN3cjNkJmNkFWN5IjMzYzX1AjMwETM1EzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
從日志中可以看到,幾個方法的執行順序依次為 : onPreExecute –>doInBackground –>onPostExecute
然後我們在doInBackground 方法中添加這句代碼 publishProgress();
@Override
protected Void doInBackground(Void... params) {
Log.d(LOGCAT, "doInBackground------------");
//調用該方法後,會執行 onPostExecute() 方法
publishProgress();
return null;
}
再次運作,觀察logcat 輸出,可看到在 doInBackground() 方法中執行了 publishProgress()方法後會調用 onProgressUpdate() 方法,顧名思義就是更新進度條的方法
下面我們來看一個典型的異步操作的例子,網絡操作,從 Android4.0 之後,網絡操作就嚴禁被放入到主線程中執行.下面是一個采用在異步線程處理下載下傳圖像
在UI線程設定圖像的例子
布局界面代碼比較簡單,如下
<RelativeLayout 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"
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
<ProgressBar
android:id="@+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="loadImage"
android:text="下載下傳圖檔"
</RelativeLayout>
MainActivity代碼如下
public class MainActivity extends Activity
private ImageView image;// 要展示的圖檔
private ProgressBar pb;// 進度條
// 要加載的圖檔的url
String imageUrl = "https://www.baidu.com/img/2016_10_09logo_61d59f1e74db0be41ffe1d31fb8edef3.png";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = (ImageView) findViewById(R.id.iv);
pb = (ProgressBar) findViewById(R.id.pb);
}
// 下載下傳按鈕的點選事件
public void loadImage(View view) {
AsyncTaskTest asyncTaskTest = new AsyncTaskTest();
// execute()方法接受一個可變長數組的參數,可在 doInBackground()方法中擷取
asyncTaskTest.execute(imageUrl);
}
class AsyncTaskTest extends AsyncTask<String, Void, Bitmap> {
Bitmap bitmap;
// 下載下傳開始前的一些初始化操作
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
pb.setVisibility(View.VISIBLE);// 在下載下傳之前将 Progress 顯示出來
}
// 在此方法中進行網絡耗時操作,下載下傳完成後會執行 onPostExecute 方法,并把傳回值傳遞給它
@Override
protected Bitmap doInBackground(String... params) {
// 擷取傳遞進來的參數
String url = params[0];
Bitmap btm = null;
URLConnection connection;
InputStream is;
try {
connection = new URL(url).openConnection();
is = connection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
// 通過 BitmapFactory.decodeStream 方法吧輸入流轉換為 bitmap 對象
bitmap = BitmapFactory.decodeStream(bis);
is.close();
bis.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 為了看清楚進度條,人為加一個延時操作,便于觀察
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bitmap;
}
// doInBackground()方法執行完畢後會自動調用此方法, 此方法的參數是 doInBackground() 方法的傳回值.
@Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
pb.setVisibility(View.GONE);// 隐藏進度條
image.setImageBitmap(result);// 顯示下載下傳的網絡圖檔
上面代碼注釋很詳細,不再多做解釋,隻要搞懂了 AsyncTask 的幾個方法的作用于執行周期,上面的代碼很容易了解.
效果圖如下
下面我們再通過一個模拟進度條的小例子,進一步認識AsyncTask 異步任務的用法
布局界面很簡單,如下
<RelativeLayout 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"
<ProgressBar
android:id="@+id/pb"
android:padding="10dp"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
<TextView
android:id="@+id/tv_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/pb"
android:layout_centerHorizontal="true"
android:text="0%"
android:textAppearance="?android:attr/textAppearanceSmall"
</RelativeLayout>
Activity 代碼也很簡單
public class progressBarTest extends Activity
private ProgressBar pb;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pro);
pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView) findViewById(R.id.tv_show);
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}
class MyAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
//模拟進度的更新
for (int i = 0; i <= 100; i++) {
// 更新進度條,重寫 onProgressUpdate()方法,參數為 publishProgress(i)的參數
publishProgress(i);// 此方法傳入的參數就是 AsyncTask<Void, Integer,
// Void>的第二個指定的參數類型
// 睡眠200毫秒
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 更新顯示資料
tv.setText(values[0] + "%");
// 更新進度條
pb.setProgress(values[0]);// 水準進度條的進度為百分制
效果圖如下
是不是很簡單,但是不要高興太早,對于這個程式,當我們點選下載下傳,然後點選傳回,然後再點選下載下傳,進度條居然等了好久才開始更新,如下圖
這是為啥呢???????????????????????????????????????
其實AsyncTask 底層是通過線程池進行作用的,當一個線程沒有作用完畢的時候,其它線程即必須進入線程池進行等待,等到前面的線程完事後,才會輪到自己執行,是以,當我們傳回再次進入的時候,因為前一個線程正在執行更新進度條操作,是以目前線程必須等待前一個AsyncTask執行完畢後自己才可以執行.
那麼如何解決這個問題呢?
其實很簡單,AsyncTask 架構已經為我們考慮到了這個問題,我們可以通過 cancel() 方法來取消掉一個AsyncTask開啟的一個異步任務.此方法接受一個布爾值的參數,
我們要做的很簡單,重寫Activity的 onPause() 方法,把AsyncTask的聲明周期和Activity綁定到一起. 并且在 doInBackground() 方法中做異步判斷.代碼如下
@Override
protected Void doInBackground(Void... params) {
// 模拟進度的更新
for (int i = 0; i <= 100; i++) {
// 當收到取消請求時,不要在更新進度條,直接break結束for循環
if (isCancelled()) {
break;
}
// 更新進度條,重寫 onProgressUpdate()方法,參數為 publishProgress(i)的參數
publishProgress(i);// 此方法傳入的參數就是 AsyncTask<Void, Integer,
// Void>的第二個指定的參數類型
// 睡眠200毫秒
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
并且在 onProgressUpdate () 方法中也做同樣處理
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 當收到取消請求時,不要在更新進度條,直接return結束
if (isCancelled()) {
return;
}
// 更新顯示資料
tv.setText(values[0] + "%");
// 更新進度條
pb.setProgress(values[0]);// 水準進度條的進度為百分制
好了一切都做完了,我們再次運作程式可以看到
問題完美解決
這裡有一個注意事項,是關于AsyncTask 的 cancel(true);方法.其實當我們調用了 AsyncTask的cancel(true)方法時,并不會中斷目前的線程,有人對此做出的解釋是
AsyncTask不會不考慮結果而直接結束一個線程。調用cancel()其實是給AsyncTask設定一個”canceled”狀态。這取決于你去檢查AsyncTask是否已經取消,之後決定是否終止你的操作。對于mayInterruptIfRunning——它所作的隻是向運作中的線程發出interrupt()調用。在這種情況下,你的線程是不可中斷的,也就不會終止該線程。
說的不是很清楚,我們可以檢視 cancel()方法的源代碼
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
可以看到,這裡隻是調用了該線程的 t.interrupt(); 方法.對java線程中斷機制的了解在此就顯得非常重要了:
Java的中斷是一種協作機制。也就是說調用線程對象的interrupt方法并不一定就中斷了正在運作的線程,它隻是要求線程自己在合适的時機中斷自己,
總結
- 必須在UI線程中建立AsyncTask執行個體
- 必須在UI線程中調用AsyncTask的execute() 方法,而且execute() 方法隻能執行一次,多次調用,會抛出異常
- 在AsyncTask 的幾個方法中,隻有 doInBackground() 方法是運作在子線程,其它方法都是運作在主線程中,都可以更新UI.
- 重寫的四個方法都是系統自動調用的,我們不應該也不能手動去調用.