Android實戰技巧:深入解析AsyncTask

來源:互聯網
上載者:User
文章目錄
  • 與主線程互動
  • 線程任務的調度
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())。


  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的開發沒有想像中那樣簡單,要多花心思和時間在代碼上和測試上面,以確信程式是優質的

附上相關資源:

使用自訂的CorePoolSize為7的Executor(Executors.newFixedThreadPool(7)):


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


這些例子所用的代碼:

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.execute();                // use AsyncTask#SERIAL_EXECUTOR is the same to #execute();//                task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);                // use AsyncTask#THREAD_POOL_EXECUTOR is the same to older version #execute() (less than API 11)                // but different from newer version of #execute()//                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                // one by one, same to newer version of #execute()//                task.executeOnExecutor(SINGLE_TASK_EXECUTOR);                // execute tasks at some limit which can be customized//                task.executeOnExecutor(LIMITED_TASK_EXECUTOR);                // no limit to thread pool size, all tasks run simultaneously                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 >

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.