AsyncTask的源碼解析及遇到的一些問題

第一次寫博客,希望這種好習慣能堅持下來

在我們的開發過程中不得不涉及到的一個問題,那就是異步。而android已經幫我們封裝好了AsyncTask,下面我們就從源碼開始解析。


首先我們看一下AsyncTask的執行方法 execute(),這裏涉及到一個參數 mStatus 通過英文翻譯就會一目瞭然 就是狀態 


我們看一下都有哪些狀態


可以看到狀態一共有三種,分別是:沒有執行  正在執行 執行結束。繼續看上面的就會發現 當該狀態是正在執行或者執行結束的話,會拋出異常,通過該方式來保證AsyncTask裏面的線程只執行一次,然後將狀態改爲正在執行,然後執行onPreExecute()(在主線程中執行)並且將異步任務執行完的參數傳遞給mWorker的一個參數。那麼mWorker究竟是什麼東西呢?


裏面只有一個數據,通過上面的代碼,這應該就是一個記錄數據而已,並且WorkerRunnable還實現了一個接口,我們來看一下這個接口都有什麼東西


裏面只有一個call()方法,好吧 這裏已經到頭了,我們再回到上面看看它怎麼繼續執行的

我們回到sExector.execute(mFuture)這一行代碼


sExector也就是一個線程池,在這裏設置一些線程池的數據而已



而mFuture一步一步看,無非到最後就是繼承自Runnable而已,實現了run方法


而mworker在創建的時候  執行了doinbackground方法  剩下就看mworker的call方法在那執行了


mworker傳遞給了mfuture的構造方法中了


在mFuture中傳遞給了Sync 在Sync中調用了call方法  那麼這個innerRun在哪裏被調用了呢


當執行execute的時候  會走sync中的run方法
而innerrun在子線程中執行的  所以doinbackground在子線程中執行的


現在在回過頭來看這張圖是不是就覺得一目瞭然,簡單的和一一樣大笑

我們繼續往下面找



doinbackground如果將數據送到主線程中callable.call就是調用了doinbackground   返回的結果給了result  然後掉喲那個了set方法 將result傳遞過去
set是futureTask的 然後又將數據送回到sync的innerset了


從這可以看出  如果獲取到結果了  開始處理   首先先將結果v給result記錄  然後調用Done方法
Done方法在futureTask中



獲取到保存的數據  當執行done方法的時候  證明子線程已經處理完畢了  最後通過handlerobtainMessage發送到主線程中



在這裏 首先獲取到結果   然後判斷
會進入第一個  然後調用asynctask的finish方法  傳遞result給finish方法   而這個方法執行在主線程中


在finish方法中調用onpostExecute方法

好了,到現在大家應該會對AsyncTask會有一些清晰的認識了吧。

下面來說一下工作中在使用AsyncTask的時候遇到的一些問題和怎麼解決的

從上面的源碼分析我們可以看到AsyncTask內部會創建一個進程作用域的線程池來管理運行的任務,也就是說當你調用了AsyncTask.execyte()後,AsyncTask會把任務交給線程池,由線程池來管理創建Thread和運行Thread。對於內部的線程池不用版本的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())。

  2. 新增了接口#executeOnExecutor()

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

  3. 新增了二個預定義的線程池SERIAL_EXECUTORTHREAD_POOL_EXECUTOR

    其實THREAD_POOL_EXECUTOR並不是新增的,之前的就有,只不過之前(Android 2.3)它是AsyncTask私有的,未公開而已。THREAD_POOL_EXECUTOR是一個corePoolSize爲5的線程池,也就是說最多隻有5個線程同時運行,超過5個的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一樣的。而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的開發沒有想像中那樣簡單,要多花心思和時間在代碼上和測試上面,以確信程序是優質的
示例代碼:

public class AsyncTaskDemoActivity extends Activity {  
    private static int ID = 0;  
    private static final int TASK_COUNT = 9;  
    private static ExecutorService SINGLE_TASK_EXECUTOR;  
    private static ExecutorService LIMITED_TASK_EXECUTOR;  
    private static ExecutorService FULL_TASK_EXECUTOR;    
    static {  
        SINGLE_TASK_EXECUTOR = (ExecutorService) Executors.newSingleThreadExecutor();  
        LIMITED_TASK_EXECUTOR = (ExecutorService) Executors.newFixedThreadPool(7);  
        FULL_TASK_EXECUTOR = (ExecutorService) Executors.newCachedThreadPool();  
    };  
    @Override  
    public void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  
        setContentView(R.layout.asynctask_demo_activity);  
        String title = "AsyncTask of API " + VERSION.SDK_INT;  
        setTitle(title);  
        final ListView taskList = (ListView) findViewById(R.id.task_list);  
        taskList.setAdapter(new AsyncTaskAdapter(getApplication(), TASK_COUNT));  
    }  
    private class AsyncTaskAdapter extends BaseAdapter {  
        private Context mContext;  
        private LayoutInflater mFactory;  
        private int mTaskCount;  
        List<SimpleAsyncTask> mTaskList;  
        public AsyncTaskAdapter(Context context, int taskCount) {  
            mContext = context;  
            mFactory = LayoutInflater.from(mContext);  
            mTaskCount = taskCount;  
            mTaskList = new ArrayList<SimpleAsyncTask>(taskCount);  
        }  
        @Override  
        public int getCount() {  
            return mTaskCount;  
        }  
        @Override  
        public Object getItem(int position) {  
            return mTaskList.get(position);  
        }  
        @Override  
        public long getItemId(int position) {  
            return position;  
        }  
        @Override  
        public View getView(int position, View convertView, ViewGroup parent) {  
            if (convertView == null) {  
                convertView = mFactory.inflate(R.layout.asynctask_demo_item, null);  
                SimpleAsyncTask task = new SimpleAsyncTask((TaskItem) convertView);  
                /* 
                 * It only supports five tasks at most. More tasks will be scheduled only after 
                 * first five finish. In all, the pool size of AsyncTask is 5, at any time it only 
                 * has 5 threads running. 
                 */  
                task.executeOnExecutor(FULL_TASK_EXECUTOR);                  
                mTaskList.add(task);  
            }  
            return convertView;  
        }  
    }  
    private class SimpleAsyncTask extends AsyncTask<Void, Integer, Void> {  
        private TaskItem mTaskItem;  
        private String mName;  
          
        public SimpleAsyncTask(TaskItem item) {  
            mTaskItem = item;  
            mName = "Task #" + String.valueOf(++ID);  
        }  
        @Override  
        protected Void doInBackground(Void... params) {  
            int prog = 1;  
            while (prog < 101) {  
                SystemClock.sleep(100);  
                publishProgress(prog);  
                prog++;  
            }  
            return null;  
        }  
        @Override  
        protected void onPostExecute(Void result) {  
        }  
        @Override  
        protected void onPreExecute() {  
            mTaskItem.setTitle(mName);  
        }  
        @Override  
        protected void onProgressUpdate(Integer... values) {  
            mTaskItem.setProgress(values[0]);  
        }  
    }  
}  
class TaskItem extends LinearLayout {  
    private TextView mTitle;  
    private ProgressBar mProgress;  
    public TaskItem(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }
    public TaskItem(Context context) {  
        super(context);  
    } 
    public void setTitle(String title) {  
        if (mTitle == null) {  
            mTitle = (TextView) findViewById(R.id.task_name);  
        }  
        mTitle.setText(title);  
    }  
    public void setProgress(int prog) {  
        if (mProgress == null) {  
            mProgress = (ProgressBar) findViewById(R.id.task_progress);  
        }  
        mProgress.setProgress(prog);  
    }  
}  
佈局
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:paddingLeft="10dip"  
    android:paddingRight="10dip"  
    android:orientation="vertical" >  
    <ListView android:id="@+id/task_list"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:divider="#cccccc"  
        android:dividerHeight="0.6dip"  
        android:footerDividersEnabled="true"  
        android:headerDividersEnabled="true" />  
</LinearLayout>  
<?xml version="1.0" encoding="utf-8"?>  
<com.hilton.effectiveandroid.os.TaskItem xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="50dip"  
   android:gravity="center_vertical"  
   android:layout_gravity="center_vertical"  
   android:orientation="horizontal" >  
   <TextView android:id="@+id/task_name"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:textColor="#ffff00"  
       android:textSize="26sp" />  
   <ProgressBar android:id="@+id/task_progress"  
       android:layout_width="fill_parent"  
       android:layout_height="15dip"  
       android:max="100"  
       style="@android:style/Widget.ProgressBar.Horizontal" />  
</com.hilton.effectiveandroid.os.TaskItem > 

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