Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲

AsyncTask

編輯:關於Android編程

一、AsyncTask的基本用法

由於AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以為AsyncTask類指定三個泛型參數,這三個參數的用途如下:

 

1. Params
在執行AsyncTask時需要傳入的參數,可用於在後台任務中使用。

 

2. Progress
後台任務執行時,如果需要在界面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。

 

3. Result
當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值類型。

 

因此,一個最簡單的自定義AsyncTask就可以寫成如下方式:

  1. classDownloadTaskextendsAsyncTask{
  2. ……
  3. }

這裡我們把AsyncTask的第一個泛型參數指定為Void,表示在執行AsyncTask的時候不需要傳入參數給後台任務。第二個泛型參數指定為Integer,表示使用整型數據來作為進度顯示單位。第三個泛型參數指定為Boolean,則表示使用布爾型數據來反饋執行結果。


當然,目前我們自定義的DownloadTask還是一個空任務,並不能進行任何實際的操作,我們還需要去重寫AsyncTask中的幾個方法才能完成對任務的定制。經常需要去重寫的方法有以下四個:
1. onPreExecute()
這個方法會在後台任務開始執行之間調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。


2. doInBackground(Params...)
這個方法中的所有代碼都會在子線程中運行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。


3. onProgressUpdate(Progress...)
當在後台任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用,方法中攜帶的參數就是在後台任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。


4. onPostExecute(Result)
當後台任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。


因此,一個比較完整的自定義AsyncTask就可以寫成如下方式:

  1. classDownloadTaskextendsAsyncTask{
  2.  
  3. @Override
  4. protectedvoidonPreExecute(){
  5. progressDialog.show();
  6. }
  7.  
  8. @Override
  9. protectedBooleandoInBackground(Void...params){
  10. try{
  11. while(true){
  12. intdownloadPercent=doDownload();
  13. publishProgress(downloadPercent);
  14. if(downloadPercent>=100){
  15. break;
  16. }
  17. }
  18. }catch(Exceptione){
  19. returnfalse;
  20. }
  21. returntrue;
  22. }
  23.  
  24. @Override
  25. protectedvoidonProgressUpdate(Integer...values){
  26. progressDialog.setMessage("當前下載進度:"+values[0]+"%");
  27. }
  28.  
  29. @Override
  30. protectedvoidonPostExecute(Booleanresult){
  31. progressDialog.dismiss();
  32. if(result){
  33. Toast.makeText(context,"下載成功",Toast.LENGTH_SHORT).show();
  34. }else{
  35. Toast.makeText(context,"下載失敗",Toast.LENGTH_SHORT).show();
  36. }
  37. }
  38. }
這裡我們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。如果想要啟動這個任務,只需要簡單地調用以下代碼即可:
  1. newDownloadTask().execute();

以上就是AsyncTask的基本用法,我們並不需求去考慮什麼異步消息處理機制,也不需要專門使用一個Handler來發送和接收消息,只需要調用一下publishProgress()方法就可以輕松地從子線程切換到UI線程了。

二、AsyncTask內部線程池

AnsycTask執行任務時,內部會創建一個進程作用域的線程池來管理要運行的任務,也就就是說當你調用了AsyncTask.execute()後,AsyncTask會把任務交給線程池,由線程池來管理創建Thread和運行Therad。對於內部的線程池不同版本的Android的實現方式是不一樣的:

3.0之前規定同一時刻能夠運行的線程數為5個,線程池總大小為128。也就是說當我們啟動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務才會啟動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程序就會崩潰。

因此在3.0版本中AsyncTask的改動還是挺大的,在3.0之前的AsyncTask可以同時有5個任務在執行,而3.0之後的AsyncTask同時只能有1個任務在執行。為什麼升級之後可以同時執行的任務數反而變少了呢?這是因為更新後的AsyncTask已變得更加靈活,如果不想使用默認的線程池,還可以自由地進行配置。比如使用如下的代碼來啟動任務:

  1. Executorexec=newThreadPoolExecutor(15,200,10,TimeUnit.SECONDS,newLinkedBlockingQueue());
  2. newDownloadTask().executeOnExecutor(exec);
這樣就可以使用我們自定義的一個Executor來執行任務,而不是使用SerialExecutor。上述代碼的效果允許在同一時刻有15個任務正在執行,並且最多能夠存儲200個任務。

 

三、AsyncTask缺陷

1.生命周期

關於AsyncTask存在一個這樣廣泛的誤解,很多人認為一個在Activity中的AsyncTask會隨著Activity的銷毀而銷毀。然後事實並非如此。AsyncTask會一直執行doInBackground()方法直到方法執行結束。一旦上述方法結束,會依據情況進行不同的操作。
?如果cancel(boolean)調用了,則執行onCancelled(Result)方法
?如果cancel(boolean)沒有調用,則執行onPostExecute(Result)方法

AsyncTask的cancel方法需要一個布爾值的參數,參數名為mayInterruptIfRunning,意思是如果正在執行是否可以打斷,如果這個值設置為true,表示這個任務可以被打斷,否則,正在執行的程序會繼續執行直到完成。如果在doInBackground()方法中有一個循環操作,我們應該在循環中使用isCancelled()來判斷,如果返回為true,我們應該避免執行後續無用的循環操作。

總之,我們使用AsyncTask需要確保AsyncTask正確地取消。

 

2.不好好工作的cancel()

簡而言之的答案,有時候起作用

如果你調用了AsyncTask的cancel(false),doInBackground()仍然會執行到方法結束,只是不會去調用onPostExecute()方法。但是實際上這是讓應用程序執行了沒有意義的操作。那麼是不是我們調用cancel(true)前面的問題就能解決呢?並非如此。如果mayInterruptIfRunning設置為true,會使任務盡早結束,但是如果的doInBackground()有不可打斷的方法會失效,比如這個BitmapFactory.decodeStream() IO操作。但是你可以提前關閉IO流並捕獲這樣操作拋出的異常。但是這樣會使得cancel()方法沒有任何意義。

 

3.內存洩露

還有一種常見的情況就是,在Activity中使用非靜態匿名內部AsyncTask類,由於Java內部類的特點,AsyncTask內部類會持有外部類的隱式引用。由於AsyncTask的生命周期可能比Activity的長,當Activity進行銷毀AsyncTask還在執行時,由於AsyncTask持有Activity的引用,導致Activity對象無法回收,進而產生內存洩露。

 

4.結果丟失

另一個問題就是在屏幕旋轉等造成Activity重新創建時AsyncTask數據丟失的問題。當Activity銷毀並重新創建後,還在運行的AsyncTask會持有一個Activity的非法引用即之前的Activity實例。導致onPostExecute()沒有任何作用。

 

5.串行還是並行

下面的兩個任務時同時執行呢,還是AsyncTask1執行結束之後,AsyncTask2才能執行呢?實際上是結果依據API不同而不同。
關於AsyncTask時串行還是並行有很多疑問,這很正常,因為它經過多次的修改。如果你並不明白什麼時串行還是並行,可以通過接下來的例子了解,假設我們在一個方法體裡面有如下兩行代碼:

  1. newAsyncTask1.execute();
  2. newAsyncTask2.execute();

在1.6(Donut)之前:
在第一版的AsyncTask,任務是串行調度。一個任務執行完成另一個才能執行。由於串行執行任務,使用多個AsyncTask可能會帶來有些問題。所以這並不是一個很好的處理異步(尤其是需要將結果作用於UI試圖)操作的方法。

從1.6到2.3(Gingerbread)
後來Android團隊決定讓AsyncTask並行來解決1.6之前引起的問題,這個問題是解決了,新的問題又出現了。很多開發者實際上依賴於順序執行的行為。於是很多並發的問題蜂擁而至。

3.0(Honeycomb)到現在
好吧,開發者可能並不喜歡讓AsyncTask並行,於是Android團隊又把AsyncTask改成了串行。當然這一次的修改並沒有完全禁止AsyncTask並行。你可以通過設置executeOnExecutor(Executor)來實現多個AsyncTask並行。關於API文檔的描述如下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel

6.真的需要AsyncTask麼

並非如此,使用AsyncTask雖然可以以簡短的代碼實現異步操作,但是正如本文提到的,你需要讓AsyncTask正常工作的話,需要注意很多條條框框。推薦的一種進行異步操作的技術就是使用Loaders。這個方法從Android 3.0 (Honeycomb)開始引入,在android支持包中也有包含。可以通過查看官方的文檔來詳細了解Loaders。

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved