標籤:開發 android 原始碼
從事Java開發以來,接觸過很多的開原始碼,自己能夠明白代碼但是想要表達出來卻有點困難,從今天開始,逐漸開始對一些開原始碼進行解析並記錄成blog分享出來,希望以此提升自己的表達能力,如果文章中有什麼出錯之處,歡迎讀者在評論中指出方便我及時的修正,以免誤導其他讀者,如果你有什麼更好的建議也歡迎在下方留下你的評論,本人不勝榮幸。轉載此文的朋友請帶上原文的連結:http://blog.csdn.net/d_clock/article/details/43805779好,扯淡的話到此為止,接下來進入正題。
作為一名Android開發人員,一定對AsyncTask這個非同步作業類不會陌生。這是我從大二開始接觸安卓開發以來,最具爭議性的一個類了,我看到網上很多人都在吐槽AsyncTask的效能多差,並發性多不行等等,在我所閱讀過的開源架構的代碼中很多非同步作業也基本不用AsyncTask,都是採用Java的線程池操作,所以以至於有一段時間我也是從來不使用AsyncTask,認為AsyncTask的弊端很多,後來在公司裡的一位大神說了一句“你沒發現,AsyncTask已經封裝好了非同步線程和UI線程之間的互動了嗎?Google提供這個類出來給開發人員使用自然有他的道理”,我仔細一想,大神說的也挺有道理,回想之前看到的架構基本採用線程池來做非同步作業,基本都是因為這些架構裡面都沒有涉及到與UI之間的互動,所以完全可以直接使用線程池來完成。於是便花一些時間好好琢磨一下AsyncTask的原始碼(其實這代碼已經開過好多遍了,每次看總是有新的體會),又瞎扯了一段內容。
日常開發中使用AsyncTask方式就像官方文檔提供出來的這段Demo Code一樣,首先繼承AsyncTask類實現相應的泛型參數,並重寫實現doInBackground抽象方法,這個方法就是用來執行我們需要的非同步作業的。
如何使用AsyncTask來做非同步作業呢,很簡單,同樣來一段官方的Demo Code
只需要new出這個AsyncTask並調用execute參入相應參數,即可以完成相應的非同步作業流程,並最後將結果返回onPostExecute所處的主線程中進行處理,調用起來確實非常的方便就實現了和兩個線程之間的互動。到此,我們已經重溫了一邊AsyncTask的基本使用,如果身為Android程式員你還不熟悉的話,想必應該去廁所面壁思過了。最後漏了補充一下,在使用AsyncTask編程時候需要注意的一些地方:
- AsyncTask對象必須在UI線程建立
- execute方法必須在UI線程調用
- 不要在你的程式中去直接調用onPreExecute(), onPostExecute, doInBackground,
onProgressUpdate方法
- 一個AsyncTask對象只能執行一次,即只能調用一次execute方法,否則會報運行時異常
- AsyncTask不是被設計為處理耗時操作的,耗時上限為幾秒鐘,如果要做長耗時操作,強烈建議你使用Executor,ThreadPoolExecutor以及FutureTask
接下來開始圍觀AsyncTask原始碼,首先圍觀一下建構函式和一些相關的變數
CPU_COUNT:CPU的核心數
CORE_POOL_SIZE :最線程池並發線程數,直接和CPU核心數掛鈎
MAXIMUM_POOL_SIZE:線程池中的最大線程數,直接和CPU核心數掛鈎
KEEP_ALIVE:線程池中線程的最大存活時間
sThreadFactory:用於構造線程池的工廠,重寫了newThread方法為每個線程做了命名,方便調試跟蹤
sPoolWorkQueue:線程池中的工作隊列,最大容量128的鏈式阻塞隊列
THREAD_POOL_EXECUTOR:多任務並發背景工作執行緒池,初始化參數為以上的各大參數
SERIAL_EXECUTOR:串列背景工作執行緒池,實作類別為下面的SerialExecutor
sHandler:AsyncTask類的中用於線程互動的Handler,實作類別為內部類InternalHandler(AsyncTask的實現就是將Thread和Handler進行封裝來達到互動,省去我們變成時候自己封裝的過程)
MESSAGE_POST_RESULT:sHandler返回結果的訊息類型
MESSAGE_POST_PROGRESS:sHandler返回進度的訊息類型
sDefaultExecutor:AsyncTask類的預設線程池,預設使用SERIAL_EXECUTOR串列線程池進行初始化
mWorker:實現Callable介面的類,在建構函式中初始化
mFuture:FutureTask執行個體,和mWorker搭配使用,同樣在下面的建構函式中初始化
mStatus:標記當前AsyncTask的運行狀態,有PENDING, RUNNING, FINISHED三種分別用於表示等待運行,運行中和運行結束這三種狀態
mCancelled:運行任務被取消的標記位
mTaskInvoked:任務已經被調度啟動並執行標記位
到此,我們已經粗略的窺探完一些基礎部分的代碼,先瞭解上面這些才能夠方便理解接下來繼續圍觀的代碼流程,我們知道,要執行一個AsyncTask,只需要在new出來後,調用execute方法就開始了一個非同步任務,接下來就從execute的代碼開始下手,官方文檔上顯示,提供的execute方法有兩個,我們平常經常調用的是下面紅色框框中的那個
接下來就跟進這裡的代碼,接著進行圍觀
大家可以看到execute函數中指調用了executeOnExecutor函數,並將其結果返回而已,而executeOnExecutor究竟用來幹啥,從名字上看,顧名思義應該是線上程池中執行某個線程任務,他傳入的參數有兩個,除了execute本身傳入的變長參數以外,就是AsyncTask類的靜態變數sDefaultExecutor,這個靜態線程池我已經在前面介紹過了,預設是一個單任務串列的線程池,既然調用了executeOnExecutor那接下就圍觀executeOnExecutor裡面的代碼
從這個函數的實現上看,進入這個函數後,首先就開始判斷當前執行的AsyncTask的狀態,如果是PENDDING,說明當前的AsyncTask從未執行過,則直接把mStatus狀態標記為RUNNING狀態,如果AsyncTask已經執行過execute方法,則此處的mStatus將不會再是PENDDING狀態將直接拋出異常,這就可以很好的解釋清楚為什麼一個AsyncTask不能調用兩次execute
因為第一次調用的時候已經將PENDDING狀態置為其他狀態,再次判斷到位非PENDDING狀態調用就直接拋IllegalStateException了。
將mStatus狀態進行設定後,接下來就會調用onPreExecute方法,
這個方法實際上就是一個空方法,由於調用次方的時依舊處於UI線程中,所以AsyncTask的子類可以重寫此方法並在裡面實現一些UI操作,這也是AsyncTask設計的時候留下來的一個擴充方法,可以用來做非同步作業開始前的一些預備工作。
緊跟著,就開始將從execute傳入的變長參數,存放到mWorker的成員變數中,並使用預設串列線程池來進行並行作業
到此,executeOnExecutor函數的講解基本完成,接下來繼續跟入到exec的execute代碼中,又需要重新的回到了SerialExecutor中的代碼和AsyncTask的建構函式中去,在此藉機瀏覽mWorker類的實現代碼
可以看到,WorkerRunnable實際上是一個帶有mParams成員變數的一個抽象靜態內部類而且繼承了Callable,mWorker真是此類的實現執行個體,在executeOnExecutor中可以見到,mWorker中的成員變數mParams正是用來存放我們在execute調用的時候傳入的參數。
在建構函式中可以看到mWorker被執行個體化並實現了Callable介面中的call函數(這個函數就是和線程池中執行Runnable介面中的run函數一樣,只不過Callable可以返回結果狀態),進入執行call函數中的代碼,首先現將標記當前任務被調用執行的標記位設定為true,接下來設定好線程的執行優先順序,就開始就開始進入我們一直最常見的doInBackground函數裡面了,並將參數傳入,怎樣,有木有一步一步的更加明白調用的過程,在doInBackground被執行完了之後就會將執行結果返回作為調用postResult函數的參數,在postResult函數中你會見到我們更見熟悉的Handler,由它將doInBackground中的執行結果返回給我們的UI線程的訊息佇列中,最終由Handler中handleMessage來完成調用AsyncTask中finish方法,而finish方法中最終會判斷doInBackground是正常執行完了,還是中途被取消掉,由此最終來確定是調用onCancelled還是onPostExecute,最後將AsyncTask的執行狀態設定為FINISH來完成我們整個非同步線程和UI線程之間的互動過程。這裡還有另外一個地方需要注意一下,就是mFeture中的done方法,主要是用於處理execute的AsyncTask沒有被執行的情況。
需要注意的是postResult中發送出去的訊息類型是MESSAGE_POST_RESULT,這種訊息的類型是表示著doInBackground被執行完的情況,但是你可以看到上面的InternalHandler類中,其實處理的訊息有兩種,這兩種訊息也在前面介紹成員變數的時候也提到過,另外一種訊息的類型主要是用於向UI線程更新非同步任務中執行任務的進度用的,例如我們最經常接觸到的更新下載進度的例子,實際就是在doInBackground方法中不斷的去調用下面的publishProgress方法,所以你也可以很清楚的看到,實際上也是在使用Handler發送MESSAGE_POST_PROGRESS類型的訊息,最後在onProgressUpdate方法中進行相應的更新操作
而onProgressUpdate方法你也可以看到實際上也是AsyncTask留給我們擴充重寫的方法
到此,我們已經基本瞭解了整個AsyncTask的整個工作的過程,接下來我們再接著看看AsyncTask執行非同步並發性。這回先不急著看代碼,先瞧瞧Google提供的API文檔介面裡面的一些東西。
AsyncTask提供了兩個類型的線程池,前面已經介紹過串列背景工作執行緒池和並發背景工作執行緒池,而執行AsyncTask的函數也有兩個,一個是execute,另一個是executeOnExecutor,我們上面的代碼也見到了,實際上我們調用execute在代碼中也是調用了executeOnExecutor函數,那麼跟大家說這些和AsyncTask執行非同步任務的並發性有神馬關係呢?我們重新回到SerialExecutor中的代碼裡面去
我們所有進行execute的AsyncTask,其本質都是最終被封裝到一個FutureTask裡面,然後放到一個Executor裡面去執行,如果我們不另外指定這個Executor,那麼預設就會用SerialExecutor來執行這些任務,SerialExecutor內部通過一個數組隊列ArrayDeque來組織這些被執行任務,從代碼中可以看到,每執行完一個任務,就會在ArrayDeque中刪除,你只有在執行完了上一個Runnable任務的時候才可以接著執行下一個任務,如果前面的任務一直不執行完,那麼後面任務永遠也不會被執行到,這就很好解釋了前面為什麼說AsyncTask不適合用來做耗時很長的任務,因為一旦有一個任務耗時太長,在預設的AsyncTask執行機制下,後面的任務都將永遠不會得到執行。如果你仔細的觀察,也會看到執行AsyncTask的線程池都是靜態類變數,而不是成員變數,所以如果出現非常耗時的任務,將是涉及到app中所有的AsyncTask的問題。
Google在一開始設計AsyncTask的時候,主要是由於Android裝置的配置當時還比較差,所以不適合太多的任務一起並發執行,考慮此些原因而將AsyncTask做成這種串列執行任務,不過隨著硬體的發展,Google也考慮到AsyncTask所面臨的多任務並發性問題,所以在Android3.0開始也擴充出了另外的THREAD_POOL_EXECUTOR並發線程池,但是從初始化THREAD_POOL_EXECUTOR的參數來看,Google仍然控制著AsyncTask同時執行任務的數目,並且直接跟CPU的核心數掛鈎,這樣做也是在一定程度上考慮了效能的問題,Android3.0拓展出了executeOnExecutor介面,甚至可以通過executeOnExecutor介面在AsyncTask外部定義自己的並發線程池。到此我對AsyncTask的原始碼解析已經差不多要進入尾聲,最後想要再講的一些主要就是AsyncTask的執行狀態,以及取消正在執行的AsyncTask,關於這些主要可以看看下面的幾段代碼
想必大家到此,已經熟悉了整個AsyncTask的基本操作和相關的執行流程。
最後我再做一下個人的囉嗦總結,從我個人目前的開發經驗上來看,AsyncTask這個類確實已經做了很好的封裝,已經將非同步線程和UI線程之間的互動做了很好的處理,所以開發人員可以從這Google提供的類上愉快的進行編程,如果考慮到並發任務數量的問題,也可以使用3.0之後提供的介面來提高並行作業的數量,但是我們仍需要好好的控制任務的最大並發數量,因為數量太大也會造成app的效能問題,這也是為什麼Google提供的多任務線程池並發任務數目要直接和CPU核心數掛上鉤的原因,而追溯到AsyncTask的本質,我們從原始碼中也可以一目瞭然的看出,它其實就是FutureTask和Handler的結合體。
關於AsyncTask的原始碼解析到此結束,希望本文能夠協助你在Android開發中更好的瞭解和使用AsyncTask,為了更好的協助大家理清思路,後續我會另外寫一篇部落格用UML圖表示出AsyncTask的工作過程,這樣將會顯得更加直觀。尊重原創,轉載請註明出處:http://blog.csdn.net/d_clock/article/details/43805779
Android開發:AsyncTask原始碼完全解析