深入解析AsyncTask

寫的挺好的,果斷存檔

AsyncTask的介紹及基本使用方法

關於AsyncTask的介紹和基本使用方法可以參考官方文檔Android實戰技巧:多線程AsyncTask這裏就不重複

AsyncTask引發的一個問題

上週遇到了一個極其詭異的問題,一個小功能從網絡上下載一個圖片,然後放到ImageView中,是用AsyncTask來實現的,本身邏輯也很簡單,僅是在doInBackground中用HTTP請求把圖片的輸入流取出,然後用BitmapFactory去解析,然後再把得到的Bitmap放到ImageView中。這個應用是用4.0的SDK開發的,也是運行在4.0上面的。但是有時候下載這張圖片去要用很久很久,甚至要等上幾分鐘。通過調試發現一個令人難以接受的事實:竟然是doInBackground()未及時執行,也就是它並沒有在#execute()調用之後馬上執行,而是等待了很久才得以執行。

神馬情況,難道AsyncTask不是線程,難道不是異步,難道AsyncTask另有內幕?

AsyncTask的內幕

AsyncTask主要有二個部分:一個是與主線各的交互,另一個就是線程的管理調度。雖然可能多個AsyncTask的子類的實例,但是AsyncTask的內部Handler和ThreadPoolExecutor都是進程範圍內共享的,其都是static的,也即屬於類的,類的屬性的作用範圍是CLASSPATH,因爲一個進程一個VM,所以是AsyncTask控制着進程範圍內所有的子類實例。

與主線程交互

與主線程交互是通過Handler來進行的,因爲本文主要探討AsyncTask在任務調度方面的,所以對於這部分不做細緻介紹,感興趣的朋友可以去看AsyncTask的源碼

線程任務的調度

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

Android2.3以前的版本,也即SDK/API 10和以前的版本

內部的線程池限制是5個,也就是說同時只能有5個線程運行,超過的線程只能等待,等待前面的線程某個執行完了才被調度和運行。換句話說,如果一個進程中的AsyncTask實例個數超過5個,那麼假如前5個都運行很長時間的話,那麼第6個只能等待機會了。這是AsyncTask的一個限制,而且對於2.3以前的版本無法解決。如果你的應用需要大量的後臺線程去執行任務,那麼你只能放棄使用AsyncTask,自己創建線程池來管理Thread,或者乾脆不用線程池直接使用Thread也無妨。不得不說,雖然AsyncTask較Thread使用起來比較方便,但是它最多隻能同時運行5個線程,這也大大侷限了它的實力,你必須要小心的設計你的應用,錯開使用AsyncTask的時間,盡力做到分時,或者保證數量不會大於5個,否則就可能遇到上面提到的問題。要不然就只能使用JavaSE中的API了。

Android 3.0以後,也即SDK/API 11和以後的版本

可能是Google意識到了AsyncTask的侷限性了,從Android 3.0開始對AsyncTask的API做出了一些調整:

  1. #execute()提交的任務,按先後順序每次只運行一個

    也就是說它是按提交的次序,每次只啓動一個線程執行一個任務,完成之後再執行第二個任務,也就是相當於只有一個後臺線程在執行所提交的任務(Executors.newSingleThreadPool())。

    1. 新增了接口#executeOnExecutor()

      這個接口允許開發者提供自定義的線程池來運行和調度Thread,如果你想讓所有的任務都能併發同時運行,那就創建一個沒有限制的線程池(Executors.newCachedThreadPool()),並提供給AsyncTask。這樣這個AsyncTask實例就有了自己的線程池而不必使用AsyncTask默認的。新增了二個預定義的線程池SERIAL_EXECUTORTHREAD_POOL_EXECUTOR

    2. 其實THREAD_POOL_EXECUTOR並不是新增的,之前的就有,只不過之前(Android 2.3)它是AsyncTask私有的,未公開而已。THREAD_POOL_EXECUTOR是一個corePoolSize爲5的線程池,也就是說最多隻有5個線程同時運行,超過5個的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一樣的。

      1. SERIAL_EXECUTOR是新增的,它的作用是保證任務執行的順序,也就是它可以保證提交的任務確實是按照先後順序執行的。它的內部有一個隊列用來保存所提交的任務,保證當前只運行一個,這樣就可以保證任務是完全按照順序執行的,默認的execute()使用的就是這個,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)與execute()是一樣的。

      前面問題的解法

      瞭解了AsyncTask的內幕就知道了前面問題的原因:因爲是4.0平臺,所以所有的AsyncTask並不都會運行在單獨的線程中,而是被SERIAL_EXECUTOR順序的使用線程執行。因爲應用中可能還有其他地方使用AsyncTask,所以到網絡取圖片的AsyncTask也許會等待到其他任務都完成時才得以執行而不是調用executor()之後馬上執行。

      那麼解決方法其實很簡單,要麼直接使用Thread,要麼創建一個單獨的線程池(Executors.newCachedThreadPool())。或者最簡單的解法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),這樣起碼不用等到前面的都結束了再執行。

      AsyncTask的使用注意事項

      前面的文章曾建議使用AsyncTask而不是使用Thread,但是AsyncTask似乎又有它的限制,這就要根據具體的需求情況而選擇合適的工具,No Silver Bullet。下面是一些建議:

      • 改善你的設計,少用異步處理

        線程的開銷是非常大的,同時異步處理也容易出錯,難調試,難維護,所以改善你的設計,儘可能的少用異步。對於一般性的數據庫查詢,少量的I/O操作是沒有必要啓動線程的。

      • 與主線程有交互時用AsyncTask,否則就用Thread

        AsyncTask被設計出來的目的就是爲了滿足Android的特殊需求:非主線程不能操作(UI)組件,所以AsyncTask擴展Thread增強了與主線程的交互的能力。如果你的應用沒有與主線程交互,那麼就直接使用Thread就好了。

      • 當有需要大量線程執行任務時,一定要創建線程池

        線程的開銷是非常大的,特別是創建一個新線程,否則就不必設計線程池之類的工具了。當需要大量線程執行任務時,一定要創建線程池,無論是使用AsyncTask還是Thread,因爲使用AsyncTask它內部的線程池有數量限制,可能無法滿足需求;使用Thread更是要線程池來管理,避免虛擬機創建大量的線程。比如從網絡上批量下載圖片,你不想一個一個的下,或者5個5個的下載,那麼就創建一個CorePoolSize爲10或者20的線程池,每次10個或者20個這樣的下載,即滿足了速度,又不至於耗費無用的性能開銷去無限制的創建線程。

      • 對於想要立即開始執行的異步任務,要麼直接使用Thread,要麼單獨創建線程池提供給AsyncTask

        默認的AsyncTask不一定會立即執行你的任務,除非你提供給他一個單獨的線程池。如果不與主線程交互,直接創建一個Thread就可以了,雖然創建線程開銷比較大,但如果這不是批量操作就沒有問題。

      • Android的開發沒有想像中那樣簡單,要多花心思和時間在代碼上和測試上面,以確信程序是優質的

      附上相關資源:

      使用自定義的CorePoolSize爲7的Executor(Executors.newFixedThreadPool(7)):

    3. 使用未設限制的Executor(Executors.newCachedThreadPool()):

    4. 這些例子所用的代碼:

  1. [代碼]java代碼:

    001 public class AsyncTaskDemoActivity extendsActivity { 
    002     privatestatic int ID = 0
    003     privatestatic final int TASK_COUNT = 9
    004     privatestatic ExecutorService SINGLE_TASK_EXECUTOR; 
    005     privatestatic ExecutorService LIMITED_TASK_EXECUTOR; 
    006     privatestatic ExecutorService FULL_TASK_EXECUTOR; 
    007        
    008     static
    009         SINGLE_TASK_EXECUTOR = (ExecutorService) Executors.newSingleThreadExecutor(); 
    010         LIMITED_TASK_EXECUTOR = (ExecutorService) Executors.newFixedThreadPool(7); 
    011         FULL_TASK_EXECUTOR = (ExecutorService) Executors.newCachedThreadPool(); 
    012     }; 
    013        
    014     @Override 
    015     publicvoid onCreate(Bundle icicle) { 
    016         super.onCreate(icicle); 
    017         setContentView(R.layout.asynctask_demo_activity); 
    018         String title ="AsyncTask of API " + VERSION.SDK_INT; 
    019         setTitle(title); 
    020         finalListView taskList = (ListView) findViewById(R.id.task_list); 
    021         taskList.setAdapter(newAsyncTaskAdapter(getApplication(), TASK_COUNT)); 
    022     
    023        
    024     privateclass AsyncTaskAdapterextends BaseAdapter { 
    025         privateContext mContext; 
    026         privateLayoutInflater mFactory; 
    027         privateint mTaskCount; 
    028         List<SimpleAsyncTask> mTaskList; 
    029            
    030         publicAsyncTaskAdapter(Context context, inttaskCount) { 
    031             mContext = context; 
    032             mFactory = LayoutInflater.from(mContext); 
    033             mTaskCount = taskCount; 
    034             mTaskList =new ArrayList<SimpleAsyncTask>(taskCount); 
    035         
    036            
    037         @Override 
    038         publicint getCount() { 
    039             returnmTaskCount; 
    040         
    041    
    042         @Override 
    043         publicObject getItem(intposition) { 
    044             returnmTaskList.get(position); 
    045         
    046    
    047         @Override 
    048         publiclong getItemId(intposition) { 
    049             returnposition; 
    050         
    051    
    052         @Override 
    053         publicView getView(intposition, View convertView, ViewGroup parent) { 
    054             if(convertView == null) { 
    055                 convertView = mFactory.inflate(R.layout.asynctask_demo_item,null); 
    056                 SimpleAsyncTask task =new SimpleAsyncTask((TaskItem) convertView); 
    057                 /*
    058                  * It only supports five tasks at most. More tasks will be scheduled only after
    059                  * first five finish. In all, the pool size of AsyncTask is 5, at any time it only
    060                  * has 5 threads running.
    061                  */ 
    062 //                task.execute(); 
    063                 // use AsyncTask#SERIAL_EXECUTOR is the same to #execute(); 
    064 //                task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 
    065                 // use AsyncTask#THREAD_POOL_EXECUTOR is the same to older version #execute() (less than API 11) 
    066                 // but different from newer version of #execute() 
    067 //                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 
    068                 // one by one, same to newer version of #execute() 
    069 //                task.executeOnExecutor(SINGLE_TASK_EXECUTOR); 
    070                 // execute tasks at some limit which can be customized 
    071 //                task.executeOnExecutor(LIMITED_TASK_EXECUTOR); 
    072                 // no limit to thread pool size, all tasks run simultaneously 
    073                 task.executeOnExecutor(FULL_TASK_EXECUTOR); 
    074                    
    075                 mTaskList.add(task); 
    076             
    077             returnconvertView; 
    078         
    079     
    080        
    081     privateclass SimpleAsyncTask extends AsyncTask<Void, Integer, Void> { 
    082         privateTaskItem mTaskItem; 
    083         privateString mName; 
    084            
    085         publicSimpleAsyncTask(TaskItem item) { 
    086             mTaskItem = item; 
    087             mName ="Task #" + String.valueOf(++ID); 
    088         
    089            
    090         @Override 
    091         protectedVoid doInBackground(Void... params) { 
    092             intprog = 1
    093             while(prog < 101) { 
    094                 SystemClock.sleep(100); 
    095                 publishProgress(prog); 
    096                 prog++; 
    097             
    098             returnnull
    099         
    100            
    101         @Override 
    102         protectedvoid onPostExecute(Void result) { 
    103         
    104            
    105         @Override 
    106         protectedvoid onPreExecute() { 
    107             mTaskItem.setTitle(mName); 
    108         
    109            
    110         @Override 
    111         protectedvoid onProgressUpdate(Integer... values) { 
    112             mTaskItem.setProgress(values[0]); 
    113         
    114     
    115
    116    
    117 class TaskItem extends LinearLayout { 
    118     privateTextView mTitle; 
    119     privateProgressBar mProgress; 
    120        
    121     publicTaskItem(Context context, AttributeSet attrs) { 
    122         super(context, attrs); 
    123     
    124    
    125     publicTaskItem(Context context) { 
    126         super(context); 
    127     
    128        
    129     publicvoid setTitle(String title) { 
    130         if(mTitle == null) { 
    131             mTitle = (TextView) findViewById(R.id.task_name); 
    132         
    133         mTitle.setText(title); 
    134     
    135        
    136     publicvoid setProgress(intprog) { 
    137         if(mProgress == null) { 
    138             mProgress = (ProgressBar) findViewById(R.id.task_progress); 
    139         
    140         mProgress.setProgress(prog); 
    141     
    142
    143  
    144 <?xml version="1.0"encoding="utf-8"?> 
    145 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    146     android:layout_width="match_parent" 
    147     android:layout_height="match_parent" 
    148     android:paddingLeft="10dip" 
    149     android:paddingRight="10dip" 
    150     android:orientation="vertical"
    151     <ListView android:id="@+id/task_list" 
    152         android:layout_width="fill_parent" 
    153         android:layout_height="wrap_content" 
    154         android:divider="#cccccc" 
    155         android:dividerHeight="0.6dip" 
    156         android:footerDividersEnabled="true" 
    157         android:headerDividersEnabled="true"/> 
    158 </LinearLayout> 

  1. [代碼]java代碼:

    01 <?xml version="1.0"encoding="utf-8"?> 
    02 <com.hilton.effectiveandroid.os.TaskItem xmlns:android="http://schemas.android.com/apk/res/android" 
    03     android:layout_width="match_parent" 
    04     android:layout_height="50dip" 
    05     android:gravity="center_vertical" 
    06     android:layout_gravity="center_vertical" 
    07     android:orientation="horizontal"
    08     <TextView android:id="@+id/task_name" 
    09         android:layout_width="wrap_content" 
    10         android:layout_height="wrap_content" 
    11         android:textColor="#ffff00" 
    12         android:textSize="26sp"/> 
    13     <ProgressBar android:id="@+id/task_progress" 
    14         android:layout_width="fill_parent" 
    15         android:layout_height="15dip" 
    16         android:max="100" 
    17         style="@android:style/Widget.ProgressBar.Horizontal"/> 
    18 </com.hilton.effectiveandroid.os.TaskItem >

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章