編輯:關於Android編程
這幾天在做一個緩存網絡加載的信息模塊,在讀取緩存並更新UI的時候用到AsyncTask,本來是想這樣代碼比較干淨的,然後就發現問題了。
問題:
執行execute()以後,從onPreExecute()到doInBackground()之間竟然等待了長達7秒,而且這個時間時長時短,然後就開始查找原因。
後來發現了AsyncTask的黑歷史:
在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並行。
上面這段話中心思想就是,現在AsyncTask是默認串行執行的,AsyncTask默認自己維護一個靜態的線程池,而該線程池只允許同時執行一個線程,也就是說,你的應用中,可能有多個AsyncTask實例,而多次不管多少個AsyncTask,只要是調用execute()方法,都是共享這個默認進程池的,你的任務必須在之前的任務執行完以後,才能執行。可以理解為,默認情況下,所有的AsyncTask在一個獨立於UI線程的線程中執行,任務需要排隊,先execute的先執行,後面的只能等。所以才會出現樓主這樣漫長的等待問題。
解決:
除了excute方法外,我們可以自己調用executeOnExecutor,如果使用executeOnExecutor方法,可以在外部自定義線程池,解決不能並發執行異步任務的問題。
例如:
executeOnExecutor(Executors.newCachedThreadPool());
這樣這個AsyncTask實例就有了自己的線程池而不必使用AsyncTask默認的。
追加:
AsyncTask新增了兩個預定義的線程池SERIAL_EXECUTOR
和 THREAD_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而不是使用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的開發沒有想像中那樣簡單,要多花心思和時間在代碼上和測試上面,以確信程序是優質的
看完上面的建議,我發現,其實我做的這個緩存只是緩存字符串,根本用不著開線程,就直接放到UI線程裡好了。
好了,跟隨潮流,還是先看下效果,不然可能都沒人想看下去了(不會看到效果後不想看了吧O(∩_∩)O~)嗯,就是讓左面板在主面板的下面,所以我們自定義的控件S
首先描述下我們想要實現的內容,我們希望在一個應用中通過點擊按鈕,去操作另一個進程中應用的音樂播放功能。如圖,我們點擊“播放”時,系統就會去遠程調用我們提供的一個servi
Android下拉刷新庫,利用viewdraghelper實現。集成了下拉刷新,底部加載更多,以及剛進入加載數據的loadview。包括了listview與g
在項目開發中,可能系統自帶的一些widget不能滿足我們的需求,這時就需要自定義View。通過查看系統中的常用widget如Button,TextView,EditTex