閱讀837 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android_深入解析AsyncTask

1.AsyncTask的內幕

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

2.線程任務的調度

內部會創建一個進程作用域的線程池來管理要運行的任務,也就就是說當你調用了AsyncTask#execute()後,AsyncTask會把任務交給線程池,由線程池來管理創建Thread和運行Therad。

3.不同版本區別

(1).API10及以前版本

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

即,最大可同時運行為5個!

(2.API11及以後版本

可能是Google意識到了AsyncTask的局限性了,從Android 3.0開始對AsyncTask的API做出了一些調整:#execute()提交的任務,新增了二個預定義的線程池SERIAL_EXECUTOR(默認為序列線程池,即一次隻執行一個線程任務)THREAD_POOL_EXECUTOR(還為原來默認的線程池)

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

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

其實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()是一樣的。

4.典型問題

了解了AsyncTask,如果發現圖片異步任務還未執行,可能被SERIAL_EXECUTOR順序的使用線程執行。因為應用中可能還有其他地方使用AsyncTask,所以到網絡取圖片的AsyncTask也許會等待到其他任務都完成時才得以執行而不是調用executor()之後馬上執行。

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

5.注意事項及建議

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

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

  • 如果與主線程沒有交互不要使用AsyncTask

    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()):


最後更新:2017-04-03 12:55:32

  上一篇:go .NET視頻的那些
  下一篇:go 經典動態規劃基礎題-三角形最大和問題