我們在實際開發中,會遇到很多耗時操作爲了解決ANR錯誤,我們一般採用子線程更新UI線程的方法,我們都知道只有在UI線程中才可以更新界面,所以耗時操作一般放在子線程中去搞。首選是Handler機制(異步消息機制),但是我們知道Handler是單線程的,我們還可以採用異步任務AsyncTask(是線程池)來解決。
android提供了一套專門用於異步處理的類。即:AynsTask類。使用這個類可以爲耗時程序開闢一個新線程進行處理,處理完時返回。
其實,AsynTask類就是對Thread類的一個封裝,並且加入了一些新的方法。編程時,兩者都可以實現同樣的功能。
要點:爲了調用關係明確及安全,AsynTask類在繼承時要傳入3個泛型。第一個泛型對應execute()向doInBackground()的傳遞類型。第二個泛型對應doInBackground()的返回類型和傳遞給onPostExecute()的類型。第三個泛型對應publishProgress()向progressUpdate()傳遞的類型。傳遞的數據都是對應類型的數組,數組都是可變長的哦。可以根據具體情況使用。
一、AsynTask類結構
asysTask類主要用到的幾個內部回調函數有:
二、回調邏輯關係
三、AsyncTask裏面幾個方法的執行順序
1、主線程調用AsynTask子類實例的execute()方法後,首先會調用onPreExecute()方法。onPreExecute()在主線程中運行,可以用來寫一些開始提示代碼。
2、之後啓動新線程,調用doInBackground()方法,進行異步數據處理。
3、處理完畢之後異步線程結束,在主線程中調用onPostExecute()方法。onPostExecute()可以進行一些結束提示處理。
補充:在doInBackground()方法異步處理的時候,如果希望通知主線程一些數據(如:處理進度)。這時,可以調用publishProgress()方法。這時,主線程會調用AsynTask子類的onProgressUpdate()方法進行處理。
4、各個函數間數據的傳遞
通過上面的調用關係,我們就可以大概看出一些數據傳遞關係。如下:
execute()向doInBackground()傳遞。
doInBackground()的返回值會傳遞給onPostExecute()。
publishProgress()向progressUpdate()傳遞。
四、範例:使用AsyncTask在網絡上下載一個圖片
一、效果圖:
以下就是從網絡上獲取百度圖片的效果圖,當點擊下載按鈕的時候就會去下載圖片
二、代碼實現
2.1、main.xml的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ProgressBar>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下載圖片" >
</Button>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
2.3、Activity的編寫
package com.jun.asynctask;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
public class AsyncTaskActivity extends Activity {
//定義ImageView組件
private ImageView mImageView;
//定義按鈕組件
private Button mButton;
//定義進度條組件
private ProgressBar mProgressBar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//取得ImageView組件
mImageView= (ImageView) findViewById(R.id.imageView);
//取得按鈕組件
mButton = (Button) findViewById(R.id.button);
//取得進度條組件
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
//給按鈕添加監聽事件
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//實例化GetCSDNLogoTask
GetMeiNvLogoTasktask = new GetMeiNvLogoTask();
//execute方法執行以後執行onPostExecute方法,此方法在UI線程中,此方法中開啓一個子線程,和UIt線程不是同一樣線程。 task.execute("http://img1.jike.com/get?name=T16vLoB_Ag1RCvBVdK");
}
});
}
//AsyncTask的子類
class GetMeiNvLogoTaskextends AsyncTask<String,Integer,Bitmap> {//繼承AsyncTask
/**
* 其中的三個參數
* String:指的是doInBackground()方法中的參數類型 即第一個參數
* Integer:指的是onProgressUpdate()方法中的參數類型 即第二個參數
* Bitmap:指的是onPostExecute()的方法中的參數類型和doInBackground()方法的返回值類型 即第三個參數
*/
//處理後臺執行的任務,在後臺線程執行,主要用於進行異步操作
@Override
protected Bitmap doInBackground(String... params) {
//用於發佈更新消息,每次執行引方法都將會調用onProgressUpdate(Integer... progress)方法
publishProgress(0);
HttpClient hc = new DefaultHttpClient();
//用於發佈更新消息,每次執行到此方法都會調用onProgressUpdate(Integer... progress)方法
publishProgress(10);
HttpGet hg = new HttpGet(params[0]);//獲取csdn的logo
//定義一個Bitmap
final Bitmap bm;
try {
HttpResponse hr = hc.execute(hg);
bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
} catch (Exception e) {
return null;
}
publishProgress(100);
//mImageView.setImageBitmap(result); 不能在後臺線程操作ui
return bm; //返回的值會傳入到onPostExecute()方法的參數中
}
//在doInBackground方法中每次執行publishProgress之後此方法都會被調用,在ui線程執行
//用於在執行異步任務的過程中,對用戶進行提示,例如:控制進度條等。
protected void onProgressUpdate(Integer... progress) {
mProgressBar.setProgress(progress[0]);//更新進度條的進度
}
//後臺任務(doInBackground())執行完之後被調用,在ui線程執行,
//主要用於將異步任務執行的結果返回給客戶
protected void onPostExecute(Bitmap result) {
if(result != null) {
Toast.makeText(AsyncTaskActivity.this, "成功獲取圖片", Toast.LENGTH_LONG).show();
//把下載下來的圖片設置在ImageView上
mImageView.setImageBitmap(result);
}else {
Toast.makeText(AsyncTaskActivity.this, "獲取圖片失敗", Toast.LENGTH_LONG).show();
}
}
//在 doInBackground(Params...)之前,execute()方法之後被調用,在ui線程執行,所以可以設置控件大小屬性等。
//主要用於異步操作之前的UI的準備工作
protected void onPreExecute () {
mImageView.setImageBitmap(null);
mProgressBar.setProgress(0);//進度條復位
}
protected void onCancelled () {//在ui線程執行
mProgressBar.setProgress(0);//進度條復位
}
}
}
2.4、由於要訪問網絡,所以一定要進行權限的配置
<uses-permission android:name="android.permission.INTERNET"/>
以上就簡單的完成了一個使用AsyncTask從網絡下載圖片的方法
總結一下,些案例的流程:
1、當點擊按鈕時,創建一個task,並且傳入百度的美女地址(String類型參數,因此AsyncTask的第一個模板參數爲String類型)
2、UI線程執行onPreExecute(),把ImageView的圖片清空,progrssbar的進度清零。
3、後臺線程執行doInBackground(),不可以在doInBackground()操作ui,調用publishProgress(0)更新進度,此時會調用onProgressUpdate(Integer...progress)更新進度條(進度用整形表示,因此AsyncTask的第二個模板參數是Integer)。函數最後返回result(例子中是返回Bitmap類型,因此AsyncTask的第二個模板參數是Bitmap)。
4、當後臺任務執行完成後,調用onPostExecute(Result),傳入的參數是doInBackground()中返回的對象。
五、此案例中AsyncTask的實現原理
分析:
在分析實現流程之前,我們先了解一下AsyncTask有哪些成員變量。
private static final int CORE_POOL_SIZE =5;//5個核心工作線程
private static final int MAXIMUM_POOL_SIZE = 128;//最多128個工作線程
private static final int KEEP_ALIVE = 1;//空閒線程的超時時間爲1秒
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);//等待隊列
private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//線程池是靜態變量,所有的異步任務都會放到這個線程池的工作線程內執行。
回到例子中,點擊按鈕之後會新建一個GetCSDNLogoTask對象:
GetMeiNvLogoTasktask = new GetMeiNvLogoTask();
此時會調用父類AsyncTask的構造函數AsyncTask.java
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return doInBackground(mParams);
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
Message message;
Result result = null;
try {
result = get();
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
message.sendToTarget();//取消任務,發送MESSAGE_POST_CANCEL消息
return;
} catch (Throwable t) {
throw new RuntimeException("An error occured while executing "
+ "doInBackground()", t);
}
message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(AsyncTask.this, result));//完成任務,發送MESSAGE_POST_RESULT消息並傳遞result對象
message.sendToTarget();
}
};
}
WorkerRunnable類實現了callable接口的call()方法,該函數會調用我們在AsyncTask子類中實現的doInBackground(mParams)方法,由此可見,WorkerRunnable封裝了我們要執行的異步任務。FutureTask中的protectedvoid done() {}方法實現了異步任務狀態改變後的操作。當異步任務被取消,會向UI線程傳遞MESSAGE_POST_CANCEL消息,當任務成功執行,會向UI線程傳遞MESSAGE_POST_RESULT消息,並把執行結果傳遞到UI線程。
由此可知,AsyncTask在構造的時候已經定義好要異步執行的方法doInBackground(mParams)和任務狀態變化後的操作(包括失敗和成功)。
當創建完GetCSDNLogoTask對象後,執行GetCSDNLogoTask task = new GetCSDNLogoTask();
此時會調用AsyncTask的execute(Params...params)方法
AsyncTask.java
public final AsyncTask<Params,Progress, Result> execute(Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw newIllegalStateException("Cannot execute task:"
+ " the taskis already running.");
case FINISHED:
throw newIllegalStateException("Cannot execute task:"
+ " the taskhas already been executed "
+ "(a task canbe executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();//運行在ui線程,在提交任務到線程池之前執行
mWorker.mParams = params;
sExecutor.execute(mFuture);//提交任務到線程池
return this;
}
當任務正在執行或者已經完成,會拋出IllegalStateException,由此可知我們不能夠重複調用execute(Params...params)方法。在提交任務到線程池之前,調用了onPreExecute()方法。然後才執行sExecutor.execute(mFuture)是任務提交到線程池。
前面我們說到,當任務的狀態發生改變時(1、執行成功2、取消執行3、進度更新),工作線程會向UI線程的Handler傳遞消息。在《Android異步處理三:Handler+Looper+MessageQueue深入詳解》一文中我們提到,Handler要處理其他線程傳遞過來的消息。在AsyncTask中,InternalHandler是在UI線程上創建的,它接收來自工作線程的消息,實現代碼如下:
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
@Override
public voidhandleMessage(Message msg) {
AsyncTaskResult result =(AsyncTaskResult) msg.obj;
switch (msg.what) {
caseMESSAGE_POST_RESULT:
// There is onlyone result
result.mTask.finish(result.mData[0]);//執行任務成功
break;
caseMESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);//進度更新
break;
caseMESSAGE_POST_CANCEL:
result.mTask.onCancelled();//取消任務
break;
}
}
}
當接收到消息之後,AsyncTask會調用自身相應的回調方法。
總結:
1、 AsyncTask的本質是一個靜態的線程池,AsyncTask派生出的子類可以實現不同的異步任務,這些任務都是提交到靜態的線程池中執行。
2、線程池中的工作線程執行doInBackground(mParams)方法執行異步任務
3、當任務狀態改變之後,工作線程會向UI線程發送消息,AsyncTask內部的InternalHandler響應這些消息,並調用相關的回調函數。
六、小結
AsyncTask內部也是採用Handler機制完成的,不過其內部使用是一個線程池。運行的核心線程爲5條,最大線程數量爲128條。
AsyncTask是異步任務是用來處理線程阻塞問題的,AsyncTask,異步任務,它是一個抽象類,必須由子類實現,才能使用。而子類至少要重寫一個子類的方法:doInBackground(Params...)
AsyncTask要重寫幾個常用的方法
doInBackGround()運行在子線程中
onPreExecute()運行在UI線程中
onPostExecute()運行在UI線程中
onProgressUpdate()à運行在UI線程中
這個方法的執行順序,在UI線程中實例化出AsyncTask的子類以後,調用execute()àonPreExecute()àdoInBackground()àonPostExecute()。其中只有doInBackground()是運行在子線程中,其它都是運行在主線程中的方法。
在doInBackground()方法異步處理的時候,如果希望通知主線程一些數據(如:處理進度)。這時,可以調用publishProgress()方法。這時每當調用publishProgress方法時,主線程會調用AsynTask子類的onProgressUpdate()方法進行處理。
AsyncTask是在Android SDK 1.5之後推出的一個方便編寫後臺線程與UI線程交互的輔助類。AsyncTask的內部實現是一個線程池,每個後臺任務會提交到線程池中的線程執行,然後使用Thread+Handler的方式調用回調函數(如需深入瞭解原理請看《Android異步處理四:AsyncTask的實現原理》)。
AsyncTask抽象出後臺線程運行的五個狀態,分別是:1、準備運行,2、正在後臺運行,3、進度更新,4、完成後臺任務,5、取消任務,對於這五個階段,AsyncTask提供了五個回調函數:
1、準備運行:onPreExecute(),該回調函數在任務被執行之後立即由UI線程調用。這個步驟通常用來建立任務,在用戶接口(UI)上顯示進度條。
2、正在後臺運行:doInBackground(Params...),該回調函數由後臺線程在onPreExecute()方法執行結束後立即調用。通常在這裏執行耗時的後臺計算。計算的結果必須由該函數返回,並被傳遞到onPostExecute()中。在該函數內也可以使用publishProgress(Progress...)來發佈一個或多個進度單位(unitsof progress)。這些值將會在onProgressUpdate(Progress...)中被髮布到UI線程。
3. 進度更新:onProgressUpdate(Progress...),該函數由UI線程在publishProgress(Progress...)方法調用完後被調用。一般用於動態地顯示一個進度條。
4. 完成後臺任務:onPostExecute(Result),當後臺計算結束後調用。後臺計算的結果會被作爲參數傳遞給這一函數。
5、取消任務:onCancelled (),在調用AsyncTask的cancel()方法時調用
AsyncTask的構造函數有三個模板參數:
1.Params,傳遞給後臺任務的參數類型。
2.Progress,後臺計算執行過程中,進步單位(progress units)的類型。(就是後臺程序已經執行了百分之幾了。)
3.Result, 後臺執行返回的結果的類型。
AsyncTask並不總是需要使用上面的全部3種類型。標識不使用的類型很簡單,只需要使用Void類型即可。