Android開發(fā)者:你真的會用AsyncTask嗎?
導讀.1
在Android應用開發(fā)中,我們需要時刻注意保證應用程序的穩(wěn)定和UI操縱響應及時,因為不穩(wěn)定或響應緩慢的應用將會給應用帶來不好的印象。本文試圖從AsyncTask的作用說起,進一步講解一下內部的實現(xiàn)機制。
為什么引入AsyncTask?
在Android程序開始運行的時候會單獨啟動一個進程,默認情況下所有這個程序操作都在這個進程中進行。一個Android默認情況下只有一個進程,但是一個進程確實可以有許多線程的。
在這些線程中,有一個線程叫做UI線程,也叫作Main Thread,除了Main Thread之外的線程都可稱為Worker Thread.Main Thread主要負責控制UI頁面的顯示、更新、交互等。因為所有在UI線程中的操作要求越短越好,只有這樣用戶才會覺得操作比較流暢。一個好的做法是把一些比較耗時的操作,例如網絡請求、數據庫操作、復雜計算等邏輯都封裝到單獨的線程,這樣就可以避免阻塞主線程。為此,有人寫了如下的代碼:
利用Thread來實現(xiàn)
Android為我們提供了一個輕量級的異步類可以直接繼承AsyncTask,在類中實現(xiàn)異步操作,并提供接口反饋當前異步執(zhí)行的結果以及執(zhí)行進度,這些接口中有直接運行在主線程的,例如onPostExecute,onPreExecute等方法。
也就是說,Android的程序運行時是多線程的,為了更方便的處理子線程和UI線程的交互,引入了AsyncTask。
AsyncTask內部機制
AsyncTask內部邏輯主要有二個部分:
與主線程的交互,它內部實例化了一個靜態(tài)的自定義類InternalHandler,這個類是繼承自Handler的,在這個自定義類中綁定了一個叫做AsyncTaskResult的對象,每次子線程需要通知主線程,就調用sendToTarget發(fā)送消息給handler。然后在handler的handleMessage中AsyncTaskResult根據消息的類型不同(例如MESSAGEPOSTPROGRESS會更新進度條,MESSAGEPOSTCANCEL取消任務)而做不同的操作,值得一提的是,這些操作都是在UI線程進行的,意味著,從子線程一旦需要和UI線程交互,內部自動調用了handler對象把消息放在了主線程了。
AsyncTask內部調度
雖然可以新建多個AsyncTask的子類的實例,但是AsyncTask的內部Handler和ThreadPoolExecutor都是static的,這么定義的變量屬于類的,是進程范圍內共享的,所以AsyncTask控制著進程范圍內的所有子類實例,而且該類的所有實例都共用一個線程池和Handler。
可以看出,AsyncTask使用過程中需要注意的地方不少
- 由于Handler需要和主線程交互,而Handler又是內置于AsnycTask中的,所以,AsyncTask的創(chuàng)建必須在主線程。
- AsyncTaskResult的doInBackground(mParams)方法執(zhí)行異步任務運行在子線程中,其他方法運行在主線程中,可以操作UI組件。
- 不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統(tǒng)自動調用的
- 一個任務AsyncTask任務只能被執(zhí)行一次。
- 運行中可以隨時調用cancel(boolean)方法取消任務,如果成功調用isCancelled()會返回true,并且不會執(zhí)行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。而且從源碼看,如果這個任務已經執(zhí)行了這個時候調用cancel是不會真正的把task結束,而是繼續(xù)執(zhí)行,只不過改變的是執(zhí)行之后的回調方法是 onPostExecute還是onCancelled。
AsyncTask和Activity OnConfiguration
上面提到了那么多的注意點,還有其他需要注意的嗎?當然有!我們開發(fā)App過程中使用AsyncTask請求網絡數據的時候,一般都是習慣在onPreExecute顯示進度條,在數據請求完成之后的onPostExecute關閉進度條。這樣做看似完美,但是如果您的App沒有明確指定屏幕方向和configChanges時,當用戶旋轉屏幕的時候Activity就會重新啟動,而這個時候您的異步加載數據的線程可能正在請求網絡。當一個新的Activity被重新創(chuàng)建之后,可能又重新啟動了一個新的任務去請求網絡,這樣之前的一個異步任務不經意間就泄漏了,假設你還在onPostExecute寫了一些其他邏輯,這個時候就會發(fā)生意想不到異常。
一般簡單的數據類型的,對付configChanges我們很好處理,我們直接可以通過onSaveInstanceState()和onRestoreInstanceState()進行保存與恢復。 Android會在銷毀你的Activity之前調用onSaveInstanceState()方法,于是,你可以在此方法中存儲關于應用狀態(tài)的數據。然后你可以在onCreate()或onRestoreInstanceState()方法中恢復。
但是,對于AsyncTask怎么辦?問題產生的根源在于Activity銷毀重新創(chuàng)建的過程中AsyncTask和之前的Activity失聯(lián),最終導致一些問題。那么解決問題的思路也可以朝著這個方向發(fā)展。Android官方文檔 也有一些解決問題的線索。
這里介紹另外一種使用事件總線的解決方案,是國外一個安卓大牛寫的。中間用到了Square開源的EventBus類庫http://square.github.io/otto/。首先自定義一個AsyncTask的子類,在onPostExecute方法中,把返回結果拋給事件總線,代碼如下:
Android中糟糕的AsyncTask
AsyncTask是一個很常用的API,尤其異步處理數據并將數據應用到視圖的操作場合。其實AsyncTask并不是那么好。本文會將AsyncTask會引起哪些問題,如何修復這些問題,并且關于AsyncTask的一些替代方案
AsyncTask是抽象類.AsyncTask定義了三種泛型類型 Params,Progress和Result。
Params 啟動任務執(zhí)行的輸入參數,比如HTTP請求的URL。
Progress 后臺任務執(zhí)行的百分比。
Result 后臺執(zhí)行任務最終返回的結果,比如String。
AsyncTask的執(zhí)行分為四個步驟:每一步都對應一個回調方法,這些方法不應該由應用程序調用,開發(fā)者需要做的就是實現(xiàn)這些方法.
- 子類化AsyncTask
- 實現(xiàn)AsyncTask中定義的下面一個或幾個方法
- onPreExecute(), 該方法將在執(zhí)行實際的后臺操作前被UI thread調用??梢栽谠摲椒ㄖ凶鲆恍蕚涔ぷ?,如在界面上顯示一個進度條。
- doInBackground(Params...), 將在onPreExecute 方法執(zhí)行后馬上執(zhí)行,該方法運行在后臺線程中。這里將主要負責執(zhí)行那些很耗時的后臺計算工作。可以調用 publishProgress方法來更新實時的任務進度。該方法是抽象方法,子類必須實現(xiàn)。
- onProgressUpdate(Progress...),在publishProgress方法被調用后,UI thread將調用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。
- onPostExecute(Result), 在doInBackground 執(zhí)行完成后,onPostExecute 方法將被UI thread調用,后臺的計算結果將通過該方法傳遞到UI thread.
為正確的使用AsyncTask**類,以下是幾條必須遵守的準則: **
- Task的實例必須在UI thread中創(chuàng)建
- execute方法必須在UI thread中調用
- 不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)這幾個方法
- 該task只能被執(zhí)行一次,否則多次調用時將會出現(xiàn)異常 doInBackground方法和onPostExecute的參數必須對應,這兩個參數在AsyncTask聲明的泛型參數列表中指定,第一個為doInBackground接受的參數,第二個為顯示進度的參數,第第三個為doInBackground返回和onPostExecute傳入的參數。
Android AsyncTask完全解析
http://blog.csdn.net/liubin8095/article/details/12705479?utm_source=tuicool&utm_medium=referral
AsyncTask本質是用handler更新界面;在3.0版本以后,它在AsyncTask中是以常量
的形式被使用的,因此在整個應用程序中的所有AsyncTask實例都會共用一個SerialExecutor;默認情況下SerialExecutor模仿的是單一線程池效果,如果我們快速地啟動了很多任務,同一時刻只會有一個線程正在執(zhí)行,其余的均處于等待狀態(tài);如果想同時啟動多個任務可以通過
Executor exec = new ThreadPoolExecutor(15,200,10,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
new DownloadTask().executeOnExecutor(exec);
這樣就可以使用我們自定義的一個Executor來執(zhí)行任務,而不是使用SerialExecutor。上述代碼的效果運行在同一時刻有15個任務正在執(zhí)行,并且最多能夠存儲200個任務。
我們都知道,Android UI線程是不安全的,如果想要在子線程里進行UI操作,就需要借助Android異步消息處理機制。(Android Handler、Message完全解析,從源碼角度)
不過為了更加方便我們在子線程中更新UI元素,Android從1.5版本就引入了一個AsyncTask類。首先從其基本用法開始,然后分析源碼。
AsyncTask的基本用法
由于AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創(chuàng)建一個子類去繼承它。在繼承時我們可以為AsyncTask類指定三個泛型參數,這三個參數的用途如下:
- Params
在執(zhí)行AsyncTask時需要傳入的參數,可用于在后臺任務中使用。
- Process
后臺任何執(zhí)行時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。 - Result
當任務執(zhí)行完畢后,如果需要對結果進行返回,則使用這里指定的泛型作為返回值類型。
第一個參數類型決定了doInBackground()方法的輸入參數類型
第二個參數類型決定了onProgressUpdate()方法的輸入參數類型
第三個參數類型決定了doInBackground()的返回值類型和onPostExecute()的輸入參數類型
因此一個最簡單的自定義AsyncTask就可以寫成如下形式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
....
}
這里我們把AsyncTask的第一個泛型參數指定為Void,表示在執(zhí)行AsyncTask色時候不需要傳入參數給后臺任務。第二個泛型參數指定為Integer,表示使用整型數據來作為進度顯示單位。第三個泛型參數指定為B哦哦了按,則表示使用布爾型數據來反饋執(zhí)行結果。
當然,目前我們定義的DownloadTask還是一個空任務,并不能進行任何實際的操作,我們還需要去重寫AsyncTask中的幾個方法才能完成對任務的定制。經常需要去重寫的方法有以下四個:
- onPreExecute()
這個方法會在后臺任務開始執(zhí)行之前調用,用于進行一些頁面上的初始化操作,比如顯示一個進度條對話框等。
- doInBackground(Params...)
這個方法的所有代碼都會在子線程中運行,我們應該在這里去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執(zhí)行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執(zhí)行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說返饋當前任務的執(zhí)行進度,可以調用publishProgress(Progess...)方法來完成。 - onProgressUpdate(Progress...)
當在后臺任務執(zhí)行完畢并通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如說提醒任務執(zhí)行的結果,以及關閉掉進度條對話框等。
因此一個完整的自定義AsyncTask就可以寫成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try{
while(true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if(downloadPercent >=100){
break;
}
}
} catch(Exception e){
return false;}
return true;
}
@Override
protected void onProgressUpdate(Integer...values) {
progressDialog.setMessage("當前下載進度:"+values[0]+"%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if(result) {
Toast.makeText(context, "下載成功",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context, "下載失敗",Toast.LENGTH_SHORT).show();
}
}
}
這里我們虛擬了一個下載任務,在doInBackground()方法中去執(zhí)行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執(zhí)行結果。如果想要啟動這個任務,只需要簡單地調用一下代碼即可:
new DownloadTask().execute();
以上就是AsyncTask的基本用法。是不是感覺在子線程和UI線程之間進行切換變得靈活了很多?我們并不需要去考慮什么異步消息處理機制,也不需要專門使用一個Handler來發(fā)送和接收消息,只需要調用一下publishProgess()方法就可以輕松地從子線程切換到UI線程了。
分析AsyncTask的源碼
這里分析的是Android 4.0的源碼。
從之前DownloadTask的代碼就可以看出,在啟動某一個任務之前,要先new出它的實例,因此,我們就先來看一看AsyncTask構造函數中的源碼,如下所示:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
final Result result = get();
postResultIfNotInvoked(result);
} 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) {
postResultIfNotInvoked(null);
} catch (Throwable t) {
throw new RuntimeException("An error occured while executing "
+ "doInBackground()", t);
}
}
};
}
這段代碼雖然看起來有點長,但實際上并沒有任務具體的邏輯會得到執(zhí)行,只是初始化了兩個變量,mWorker和mFuture,并在初始化mFuture的時候將mWorker作為參數傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變量會暫時保存在內存中,稍后才會用到它們。
接著如果想要啟動某一個任務,就需要調用該任務的execute()方法,因此現(xiàn)在我們來看一看execute()方法的源碼,如下所示:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
只有一行代碼,僅是調用了executeOnExecutor()方法,那么具體的邏輯就應該寫在這個方法里面了。
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
Android Handler、Message完全解析,帶你從源碼的角度徹底理解
解決Android UI線程不安全。
創(chuàng)建一個Message對象,然后借助Handler發(fā)送出去,之后在Handler的handleMessage()方法中獲得剛才發(fā)送的Message對象,然后在這里進行UI操作就不會再出現(xiàn)崩潰了。
這種處理方式被稱為異步消息處理線程。
Android中AsyncTask的使用
android 提供了幾種在其他線程中訪問UI線程的方法:
Activity.runOnUiThread( Runnable )
View.post( Runnable )
View.postDelayed( Runnable, long )
Hanlder
這些類或方法同樣會使你的代碼很復雜很難理解,然而當你需要實現(xiàn)一些很復雜的操作并需要頻繁地更新UI時這會變得更糟糕。
下面我們介紹一個在安卓中比較常用且輕量級的線程輔助類:AsyncTask。它使創(chuàng)建需要與用戶界面交互的長時間運行的任務變得更簡單。
http://blog.csdn.net/hguang_zjh/article/details/41518483
Android API提到,AsyncTask非常適合短時間異步操作。如果需要執(zhí)行長時間操作,最好使用線程池Executor。
- AsyncTask的生命周期沒有跟Activity的生命周期同步
- 容易內存泄漏
AsyncTask和Activity的生命周期
如果你在一個Activity中創(chuàng)建了一個AsyncTask,你旋轉了屏幕,這個Activity將會被銷毀并且會重新創(chuàng)建一個新的實例。但是AsyncTask沒有銷毀,它將會繼續(xù)執(zhí)行直到完成。當它執(zhí)行完成后,它實際上是更新了上一個已經不存在的Activity,如果你原本想在onPostExecute()中更新UI的話,這時的AsyncTask將不會更新新的Activity,并且這個操作會引發(fā)一個異常。
內存泄漏問題
在Activity中作為內部類創(chuàng)建AsyncTask很方便。因為AsyncTask在執(zhí)行完成或者執(zhí)行中需要使用Activity中的view,因為內部類可以直接訪問外部類的域(也就是變量)。然而這意味著內部類會持有外部類的一個引用。
當長時間運行時,因為AsyncTask持有Activity的引用,所以即使當該Activity不再顯示時Android也無法釋放其占用的資源。
使用AsyncTask更新UI的示例
GhyAsyncTask.java代碼
public class GhyAsycTask extends AsyncTask<Object,Integer,Integer>
private EditText editText;
//此方法是第一個調用的
@Override
protected Integer doInBackground(Object...arg0){
try {
editText = (EditText) arg0[2];
Log.v("!", "從外部傳入2個參數 arg0[0]=" + arg0[0] + " arg0[1]=" + arg0[1]);
Log.v("!", "在doInBackground方法中做一些耗時的動作");
for (int i = 0; i < 5; i++) {
// 每次執(zhí)行后方法onProgressUpdate就被調用
publishProgress(i + 1, i + 2);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 返回100代表任務成功運行結束了
return 100;
}
// 任務結束后運行onPostExecute方法
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
if (result == 100) {
Log.v("!", "方法doInBackground成功運行結束了 并且參數result值為" + result);
Log.v("!", "并進入onPostExecute方法中");
}
}
// publishProgress方法每一次被調用時
// onProgressUpdate就被執(zhí)行1次
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.v("!", "進入了onProgressUpdate方法");
editText.setText("values[0]=" + values[0] + " values[1]=" + values[1]);
}
}
文件Main.java的核心代碼
public class Main extends Activity {
private EditText editText1;
//btn Click中響應實現(xiàn)
new GhyAsyncTask().execute("ghy1","ghy2",editText1);
}
android開發(fā)一個使用AsyncTask實現(xiàn)異步刷新的功能
/** 一個使用AsyncTask實現(xiàn)簡單異步刷新的功能。
- 功能介紹:用一個按鈕觸發(fā)事件,用TextView來顯示textview值的變化
*/
MainActivity
//onCreate實現(xiàn)
//btn Click監(jiān)聽
task = new GetCSDNLogoTask();
//觸發(fā)異步事件,并初始化參數為0
task.execute(0);
/**
- 此方法是在按回退鍵的時候停止后臺任務
*/
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
task.cancel(true);
}
Android中AsyncTask簡單實例操作
先來看看AsyncTask的定義:
public abstract class AsyncTask<Params, Progress, Result>
三種泛型類型分別代表“啟動任務執(zhí)行的輸入參數”、“后臺任務執(zhí)行的進度”、“后臺計算結果的類型”。在特定場合下,并不是所有類型都被使用,如果沒有被使用,可以用java.lang.Void類型代替。
一個異步任務的執(zhí)行一般包括以下幾個步驟:
1.execute(Params… params),執(zhí)行一個異步任務,需要我們在代碼中調用此方法,觸發(fā)異步任務的執(zhí)行。(這是這個類唯一應該被用戶調用的方法)
2.onPreExecute(),在execute(Params… params)被調用后立即執(zhí)行,一般用來在執(zhí)行后臺任務前對UI做一些標記。
3.doInBackground(Params… params),在onPreExecute()完成后立即執(zhí)行,用于執(zhí)行較為費時的操作,此方法將接收輸入參數和返回計算結果。在執(zhí)行過程中可以調用publishProgress(Progress… values)來更新進度信息。
4.onProgressUpdate(Progress… values),在調用publishProgress(Progress… values)時,此方法被執(zhí)行,直接將進度信息更新到UI組件上。
5.onPostExecute(Result result),當后臺操作結束時,此方法將會被調用,計算結果將做為參數傳遞到此方法中,直接將結果顯示到UI組件上。
MainActivity
mTask = new Mytask();
mTask.execute("http://www.baidu.com");
public void onClick(View v) {
//取消一個正在執(zhí)行的任務,onCancelled方法將會被調用
mTask.cancel(true);
}
private class MyTask extends AsyncTask<String, Integer, String> {
//onPreExecute方法用于在執(zhí)行后臺任務前做一些UI操作
@Override
protected void onPreExecute() {
Log.i(TAG, "onPreExecute() called");
textView.setText("loading...");
}
//doInBackground方法內部執(zhí)行后臺任務,不可在此方法內修改UI
@Override
protected String doInBackground(String... params) {
Log.i(TAG, "doInBackground(Params... params) called");
try {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(params[0]);
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
long total = entity.getContentLength();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int count = 0;
int length = -1;
while ((length = is.read(buf)) != -1) {
baos.write(buf, 0, length);
count += length;
//調用publishProgress公布進度,最后onProgressUpdate方法將被執(zhí)行
publishProgress((int) ((count / (float) total) * 100));
//為了演示進度,休眠500毫秒
Thread.sleep(500);
}
return new String(baos.toByteArray(), "gb2312");
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return null;
}
//onProgressUpdate方法用于更新進度信息
@Override
protected void onProgressUpdate(Integer... progresses) {
Log.i(TAG, "onProgressUpdate(Progress... progresses) called");
progressBar.setProgress(progresses[0]);
textView.setText("loading..." + progresses[0] + "%");
}
//onPostExecute方法用于在執(zhí)行完后臺任務后更新UI,顯示結果
@Override
protected void onPostExecute(String result) {
Log.i(TAG, "onPostExecute(Result result) called");
textView.setText(result); execute.setEnabled(true);
cancel.setEnabled(false);
}
//onCancelled方法用于在取消執(zhí)行中的任務時更改UI
@Override
protected void onCancelled() {
Log.i(TAG, "onCancelled() called");
textView.setText("cancelled");
progressBar.setProgress(0);
execute.setEnabled(true);
cancel.setEnabled(false);
}
}
}
Android官方圖片加載利器BitmapFun解析
通過BitmapFun在項目中使用,結合代碼了解一下BitmapFun加載圖片的原理,本文說明不包括BitmapFun的緩存部分。
Bitmap中有總結
利用AsyncTask加載Bitmap大圖片,Bitmap中有總結,參考下面的文章,采用了弱引用
Android 之Bitmap大圖片加載處理