AsyncTask,即異步任務,是Android給我們提供的一個處理異步任務的類.通過此類,可以實現UI線程和后台線程進行通訊,后台線程執行異步任務,並把結果返回給UI線程.
.為什么需要使用異步任務?
我們知道,Android中只有UI線程,也就是主線程才能進行對UI的更新操作,而其他線程是不能直接操作UI的.這樣的好處是保證了UI的穩定性和准確性,避免多個線程同時對UI進行操作而造成UI的混亂.但Android是一個多線程的操作系統,我們總不能把所有的任務都放在主線程中進行實現,比如網絡操作,文件讀取等耗時操作,如果全部放到主線程去執行,就可能會造成后面任務的阻塞.Android會去檢測這種阻塞,當阻塞時間太長的時候,就會拋出Application Not Responsed(ANR)錯誤.所以我們需要將這些耗時操作放在非主線程中去執行.這樣既避免了Android的單線程模型,又避免了ANR.
.AsyncTask為何而生?
提到異步任務,我們能想到用線程,線程池去實現.確實,Android給我們提供了主線程與其他線程通訊的機制.但同時,Android也給我們提供了一個封裝好的組件--AsyncTask.利用AsyncTask,我們可以很方便的實現異步任務處理.AsyncTask可以在子線程中更新UI,也封裝簡化了異步操作.使用線程,線程池處理異步任務涉及到了線程的同步,管理等問題.而且當線程結束的時候還需要使用Handler去通知主線程來更新UI.而AsyncTask封裝了這一切,使得我們可以很方便的在子線程中更新UI.
.構建AsyncTask子類的泛型參數
AsyncTask<Params,Progress,Result>是一個抽象類,通常用於被繼承.繼承AsyncTask需要指定如下三個泛型參數:
Params:啟動任務時輸入的參數類型.
Progress:后台任務執行中返回進度值的類型.
Result:后台任務執行完成后返回結果的類型.
.構建AsyncTask子類的回調方法
AsyncTask主要有如下幾個方法:
doInBackground:必須重寫,異步執行后台線程要完成的任務,耗時操作將在此方法中完成.
onPreExecute:執行后台耗時操作前被調用,通常用於進行初始化操作.
onPostExecute:當doInBackground方法完成后,系統將自動調用此方法,並將doInBackground方法返回的值傳入此方法.通過此方法進行UI的更新.
onProgressUpdate:當在doInBackground方法中調用publishProgress方法更新任務執行進度后,將調用此方法.通過此方法我們可以知曉任務的完成進度.
下面通過代碼演示一個典型的異步處理的實例--加載網絡圖片.網絡操作作為一個不穩定的耗時操作,從4.0開始就被嚴禁放入主線程中.所以在顯示一張網絡圖片時,我們需要在異步處理中下載圖片,並在UI線程中設置圖片.
MainActivity.java
package com.example.caobotao.learnasynctask; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button btn_image; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_image = (Button) findViewById(R.id.btn_image); btn_image.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this,ImageActivity.class)); } }); } }
ImageActivity.java
package com.example.caobotao.learnasynctask; import android.app.Activity; import android.graphics.*; import android.os.*; import android.view.View; import android.widget.*; import java.io.*; import java.net.*; /** * Created by caobotao on 15/12/2. */
public class ImageActivity extends Activity { private ImageView imageView ; private ProgressBar progressBar ; private static String URL = "http://pic3.zhongsou.com/image/38063b6d7defc892894.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image); imageView = (ImageView) findViewById(R.id.image); progressBar = (ProgressBar) findViewById(R.id.progressBar); //通過調用execute方法開始處理異步任務.相當於線程中的start方法.
new MyAsyncTask().execute(URL); } class MyAsyncTask extends AsyncTask<String,Void,Bitmap> { //onPreExecute用於異步處理前的操作
@Override protected void onPreExecute() { super.onPreExecute(); //此處將progressBar設置為可見.
progressBar.setVisibility(View.VISIBLE); } //在doInBackground方法中進行異步任務的處理.
@Override protected Bitmap doInBackground(String... params) { //獲取傳進來的參數
String url = params[0]; Bitmap bitmap = null; URLConnection connection ; InputStream is ; try { connection = new URL(url).openConnection(); is = connection.getInputStream(); //為了更清楚的看到加載圖片的等待操作,將線程休眠3秒鍾.
Thread.sleep(3000); BufferedInputStream bis = new BufferedInputStream(is); //通過decodeStream方法解析輸入流
bitmap = BitmapFactory.decodeStream(bis); is.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return bitmap; } //onPostExecute用於UI的更新.此方法的參數為doInBackground方法返回的值.
@Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); //隱藏progressBar
progressBar.setVisibility(View.GONE); //更新imageView
imageView.setImageBitmap(bitmap); } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:gravity="center" android:layout_height="match_parent">
<Button android:id="@+id/btn_image" android:text="加載圖片" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
progress.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:gravity="center" android:layout_height="match_parent">
<ProgressBar style="?android:attr/progressBarStyleHorizontal" android:id="@+id/progress" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
由於涉及到網絡操作,需要在AndroidManifest.xml中添加網絡操作權限:<uses-permission android:name="android.permission.INTERNET"/>
運行結果:
下面再演示一個模擬更新進度條的實例.
MainActivity.java
package com.example.caobotao.learnasynctask; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button btn_progress; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_progress = (Button) findViewById(R.id.btn_progress); btn_progress.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this,ProgressActivity.class)); } }); } }
ProgressActivity.java
package com.example.caobotao.learnasynctask; import android.app.Activity; import android.os.AsyncTask; import android.os.AsyncTask.Status; import android.os.Bundle; import android.widget.ProgressBar; import java.util.Scanner; /** * Created by caobotao on 15/12/2. */
public class ProgressActivity extends Activity{ private ProgressBar progressBar; private MyAsyncTask myAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.progress); progressBar = (ProgressBar) findViewById(R.id.progress); myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); }
} class MyAsyncTask extends AsyncTask<Void,Integer,Void>{ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //通過publishProgress方法傳過來的值進行進度條的更新.
progressBar.setProgress(values[0]); } @Override protected Void doInBackground(Void... params) { //使用for循環來模擬進度條的進度.
for (int i = 0;i < 100; i ++){ //調用publishProgress方法將自動觸發onProgressUpdate方法來進行進度條的更新.
publishProgress(i); try { //通過線程休眠模擬耗時操作
Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:gravity="center" android:layout_height="match_parent">
<Button android:id="@+id/btn_progress" android:text="加載進度條" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
progress.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:gravity="center" android:layout_height="match_parent">
<ProgressBar style="?android:attr/progressBarStyleHorizontal" android:id="@+id/progress" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
同樣需要在AndroidManifest.xml中添加網絡操作權限:<uses-permission android:name="android.permission.INTERNET"/>
運行結果:
點擊'加載進度條'按鈕后程序看起來運行正常.但是,正如上面圖示,如果接着點擊BACK鍵,緊接着再次點擊'加載進度條'按鈕,會發現進度條的進度一直是零,過了一會才開始更新.這是為什么呢?
根據上述的講解,我們知道,AsyncTask是基於線程池進行實現的,當一個線程沒有結束時,后面的線程是不能執行的.所以必須等到第一個task的for循環結束后,才能執行第二個task.我們知道,當點擊BACK鍵時會調用Activity的onPause()方法.為了解決這個問題,我們需要在Activity的onPause()方法中將正在執行的task標記為cancel狀態,在doInBackground方法中進行異步處理時判斷是否是cancel狀態來決定是否取消之前的task.
更改ProgressActivity.java如下:
package com.example.caobotao.learnasynctask; import android.app.Activity; import android.os.AsyncTask; import android.os.AsyncTask.Status; import android.os.Bundle; import android.widget.ProgressBar; import java.util.Scanner; /** * Created by caobotao on 15/12/2. */
public class ProgressActivity extends Activity{ private ProgressBar progressBar; private MyAsyncTask myAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.progress); progressBar = (ProgressBar) findViewById(R.id.progress); myAsyncTask = new MyAsyncTask(); //啟動異步任務的處理
myAsyncTask.execute(); } //AsyncTask是基於線程池進行實現的,當一個線程沒有結束時,后面的線程是不能執行的.
@Override protected void onPause() { super.onPause(); if (myAsyncTask != null && myAsyncTask.getStatus() == Status.RUNNING) { //cancel方法只是將對應的AsyncTask標記為cancelt狀態,並不是真正的取消線程的執行.
myAsyncTask.cancel(true); } } class MyAsyncTask extends AsyncTask<Void,Integer,Void>{ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //通過publishProgress方法傳過來的值進行進度條的更新.
progressBar.setProgress(values[0]); } @Override protected Void doInBackground(Void... params) { //使用for循環來模擬進度條的進度.
for (int i = 0;i < 100; i ++){ //如果task是cancel狀態,則終止for循環,以進行下個task的執行.
if (isCancelled()){ break; } //調用publishProgress方法將自動觸發onProgressUpdate方法來進行進度條的更新.
publishProgress(i); try { //通過線程休眠模擬耗時操作
Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } } }
.使用AsyncTask的注意事項
① 必須在UI線程中創建AsyncTask的實例.
② 只能在UI線程中調用AsyncTask的execute方法.
③ AsyncTask被重寫的四個方法是系統自動調用的,不應手動調用.
④ 每個AsyncTask只能被執行(execute方法)一次,多次執行將會引發異常.
⑤ AsyncTask的四個方法,只有doInBackground方法是運行在其他線程中,其他三個方法都運行在UI線程中,也就說其他三個方法都可以進行UI的更新操作.