安卓多線程------AsyncTask工作原理

須菩提。于意云何。如來有所說法不愧怜。須菩提白佛言。世尊看蚜。如來無所說叫搁。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-----佛說

一??AsyncTask 使用

第一步: 首先我們會寫一個自己的類繼承自AsyncTask?? ,然后重寫doInBackground方法供炎,在繼承AsyncTask的時候我們需要傳遞三個泛型參數(shù)渴逻,分別是Params, Progress, Result,首先我們詳細(xì)說明一下這三個參數(shù)是什么意思音诫。

Params :這個參數(shù)可以傳遞任意類型惨奕,換言之,也就是只要你在doInBackground方法中執(zhí)行耗時任務(wù)的時候需要用到外部對象來調(diào)用該對象的某個耗時方法或者當(dāng)前異步操作需要依賴某個對象的值竭钝,這個時候Params 參數(shù)就傳遞你要用到對象的所屬類型梨撞,因為doInBackground 可變長類型參數(shù)里面的類型是由Params 所決定的雹洗,所以Params 泛型參數(shù)你傳遞什么類型doInBackground 方法的參數(shù)就是什么類型,以上只說明了doInBackground形參的類型卧波,那實參是怎么傳遞進(jìn)去的呢时肿?Params類型對應(yīng)的實際參數(shù)是在執(zhí)行.execute()方法以后傳遞進(jìn)去的,后面會用一個示例說明港粱,?參數(shù)的傳遞不僅僅只傳遞一個參數(shù)螃成,?因為doInBackground里面的是個可邊長參數(shù),具體類型為Params... params .

Progress:這個參數(shù)也可以傳遞任意類型查坪,什么時候會用到這個參數(shù)呢寸宏?當(dāng)子線程執(zhí)行異步操作的時候,主線程需要實時的知道子線程的執(zhí)行情況偿曙,這個時候就要用到Progress參數(shù)氮凝,典型應(yīng)用場景網(wǎng)絡(luò)文件下載或者文件上傳進(jìn)度顯示,下載或者長傳文件是耗時操作需要在子線程執(zhí)行望忆,刷新頁面UI需要在主線程執(zhí)行罩阵,這個時候在doInBackground方法中調(diào)用publishProgress方法將進(jìn)度值轉(zhuǎn)遞進(jìn)去,然后會回調(diào)onProgressUpdate方法炭臭,在onProgressUpdate進(jìn)行UI操作永脓,為什么在子線程中調(diào)用publishProgress后回調(diào)的onProgressUpdate卻運行在主線程中,后面源碼分析我會詳細(xì)說明鞋仍。publishProgress和onProgressUpdate方法共享一個類型的參數(shù)常摧,換言之就是Progress類型,這個泛型你指定什么如上兩個方法的參數(shù)類型就是什么威创。實參可以傳遞多個因為是可變長參數(shù)類型落午。

Result:?這個參數(shù)也可以傳遞任意類型,但和其他兩個參數(shù)不同的是Result不是可變參數(shù)肚豺,只能返回一個結(jié)果溃斋,使用時機(jī),就是當(dāng)我們doInBackground的方法的耗時任務(wù)執(zhí)行完畢以后吸申,需要拿到子線程的執(zhí)行結(jié)果的時候梗劫,這個時候需要在doInBackground 方法中return 一個Result。這個Result要么成功截碴,要么失敗梳侨,分別由會調(diào)用不同的回調(diào),執(zhí)行成功return? 的Result可以在onPostExecute方法中拿到結(jié)果日丹,執(zhí)行失敗直接調(diào)用onCancelled走哺,失敗了沒有結(jié)果,如果非想要個結(jié)果哲虾,那就重寫帶參數(shù)的onCancelled(Result result)方法丙躏。onPostExecute調(diào)用時機(jī)择示,onCancelled調(diào)用時機(jī)以及為什么在主線程調(diào)用。源碼分析會做詳細(xì)說明晒旅。

第二步:針對Params, Progress, Result這三個參數(shù)我們用一個例子來演示栅盲,例子很簡單,就三個學(xué)生和一個老師敢朱,具體流程是剪菱,老師布置作業(yè)讓學(xué)生去做摩瞎,在學(xué)生做作業(yè)的過程中拴签,老師需要實時的知道當(dāng)前是哪個同學(xué)在做題目以及這位同學(xué)做到了哪個題目,等所有學(xué)生做完作業(yè)以后旗们,反饋老師學(xué)生做題這件事情完成蚓哩。

創(chuàng)建學(xué)生類Student

圖1

創(chuàng)建老師類 Teacher


圖2


創(chuàng)建任務(wù)執(zhí)行類?WorkAsyncTask


圖3


圖4

示例運行結(jié)果


圖5

使用總結(jié)

通過以上一個簡單的小案例演示了AsyncTask的基本使用流程,以及doInBackground方法執(zhí)行完畢以后對執(zhí)行結(jié)果的返回上渴,本案例中通過調(diào)用isCancelled()方法判斷了在doInBackground方法中所執(zhí)行的任務(wù)有沒有執(zhí)行成功岸梨,執(zhí)行成功返回true調(diào)用onPostExecute方法,執(zhí)行失敗返回false調(diào)用onCancelled()的方法稠氮,以上案例只演示了成功的情況,未成功的情況沒有演示,那什么時候會執(zhí)行未成功呢舶吗?當(dāng)在執(zhí)行doInBackground方法的過程中如果執(zhí)行的耗時任務(wù)拋出了異常鸠姨,這個時候會執(zhí)行onCancelled方法。

二? AsyncTask 源碼分析

1 構(gòu)建AsyncTask實例的時候做了哪些事情奢米?

通過第一小部分對AsyncTask的使用抓韩,我們知道在使用AsyncTask的時候首先要自己定義一個類然后繼承AsyncTask,重寫doInBackground鬓长,那當(dāng)我們自己定義一個類在使用的時候也就是new的時候谒拴,AsyncTask構(gòu)造函數(shù)都做了那些事情呢?看下面源碼


圖6
圖7
圖8

分析:

通過源碼可以看出我們在new?AsyncTask的時候有三個構(gòu)造函數(shù)涉波,第一個無參英上,后兩個有參,但是我們在使用的時候只能使用無參的構(gòu)造啤覆,因為其他兩個構(gòu)造函數(shù)是@hide標(biāo)注的所以我們無法調(diào)用苍日,如果非要調(diào)用我們可以通過反射機(jī)制去調(diào)用(API級別28之前,隱藏的方法仍然可以通過java反射訪問城侧,但是沒那個必要易遣,真沒必要!O佑印)豆茫,new?AsyncTask 以后的執(zhí)行流程:

第一步創(chuàng)建主線程唯一一個mHandler

AsyncTask()==========>this((Looper) null)====AsyncTask(@Nullable Looper callbackLooper),然后通過mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper);這句構(gòu)建一個主線程的mHandler 侨歉,這個主線程的mHandler 在后續(xù)回調(diào)執(zhí)行的onProgressUpdate,onPostExecute揩魂,onCancelled發(fā)揮了重要的作用幽邓。這個mHandler=getMainHandler() ,緊接著我們看getMainHandler() 的源碼

圖9

在getMainHandler() 里面創(chuàng)建了一個sHandler, sHandler=new InternalHandler(Looper.getMainLooper());接下來我們繼續(xù)跟蹤代碼看InternalHandler時什么

圖10

由上面的代碼可以看出InternalHandler繼承自Handler 并且重寫了handleMessage方法,在handleMessage方法里面處理子UI相關(guān)的操縱火脉。至于handleMessage方法什么時候執(zhí)行調(diào)用下面我會詳細(xì)說明

第二步創(chuàng)建WorkerRunnable 實例

WorkerRunnable 是什么牵舵?WorkerRunnable是一個抽象類但是實現(xiàn)了Callable接口,在異步操作過程中如果我們需要獲取到某個線程的執(zhí)行結(jié)果倦挂,這個時候我們需要使用Callable +?FutureTask來完成畸颅,接下來我們看看Callable的call方法做了什么首先會執(zhí)行mTaskInvoked.set(true);、mTaskInvoked是什么方援?mTaskInvoked是一個AtomicBoolean的實例對象(全局唯一没炒,用final修飾僅此一個),關(guān)于AtomicBoolean由于篇幅原因后期會單獨拉一篇來詳細(xì)說明犯戏,這里不做過多解釋送火,mTaskInvoked.set(true)以后會將AtomicBoolean的?value屬性設(shè)置成1 這個value屬性是volatile的保證了線程的可見性,緊接著 Result result =null; 這個Result就是我們傳遞進(jìn)去作為返回結(jié)果的泛型參數(shù)先匪,關(guān)于這個參數(shù)的說明在AsyncTask 使用第一部分已經(jīng)做了詳細(xì)的說明种吸,接下來又執(zhí)行了Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);這句是做什么的?首先普及一個概念.線程調(diào)度機(jī)制呀非,線程的調(diào)度機(jī)制有兩種坚俗,

1 分時調(diào)度模型:所有的線程輪流獲取CPU的使用權(quán),平均分配每個線程占用的CPU時間姜钳。

2 搶占式調(diào)度模型:優(yōu)先讓可運行池中優(yōu)先級高的線程占用CPU坦冠,優(yōu)先級相同的隨機(jī)選擇一個線程。


Android線程調(diào)用模型

?1?Adroid里面采用搶占式調(diào)度模型哥桥,可以通過android.os.Process.setThreadPriority(int)設(shè)置線程優(yōu)先級辙浑,參數(shù)范圍-20----24,數(shù)值越小優(yōu)先級越高,0為默認(rèn)優(yōu)先級拟糕。

2 線程優(yōu)先級 默認(rèn)情況下判呕,新創(chuàng)建的線程與母線程的優(yōu)先級保持一致。

舉例:比如我當(dāng)前有AB兩個線程送滞,A線程為主線程侠草,B線程為在主線程創(chuàng)建的子線程,因為安卓是搶占式調(diào)度模型犁嗅,為了保證UI能夠及時響應(yīng)边涕,這個時候可以把子線程的優(yōu)先級降低。

通過以上說明,call方法里面的這句Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);就是為了設(shè)置線程的優(yōu)先級功蜓。優(yōu)先級設(shè)置完畢以后緊接著就開始執(zhí)行我們都很熟悉的doInBackground();方法并將doInBackground方法執(zhí)行的結(jié)果用result接收园爷。最后調(diào)用了Binder.flushPendingCommands();這句的作用將當(dāng)前線程中所有待處理的Binder命令刷新到內(nèi)核驅(qū)動程序。 在執(zhí)行可能長時間阻塞的操作之前調(diào)用此方法很有用式撼,以確保釋放了所有待處理的對象引用童社,以防止該過程將對象保留的時間超過所需時間。如果在執(zhí)行doInBackground的過程中出現(xiàn)異常會怎么辦著隆?我們緊接著看看catch里面做了啥扰楼,首先會將mCancelled.set(true);mCancelled也是AtomicBoolean類型的,在前面的第一部分說AsyncTask 使用的過程中在?doInBackground方法中調(diào)用了 ? ?if (isCancelled())return false else return true;來判斷當(dāng)先任務(wù)是否執(zhí)行成功美浦,主要的一句就是根據(jù)mCancelled.get()方法弦赖,get到的是false 還是ture來進(jìn)行判斷當(dāng)前任務(wù)有沒執(zhí)行成功,如果get到的是true則說明任務(wù)執(zhí)行失敗反之則成功抵代,最火在finally代碼塊里面調(diào)用了?postResult(result);方法將doInBackground方法執(zhí)行的結(jié)果傳遞進(jìn)去腾节,?從而更新UI,接下來我們看看postResult()的源碼

圖11

可以看到postResult中出現(xiàn)了我們熟悉的異步消息機(jī)制荤牍,傳遞了一個消息message, message.what為MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result);? ? ?getHandler=mHandler前面講過庆冕。緊接著我們看看AsyncTaskResult時什么

圖12

AsyncTaskResult就是一個簡單的攜帶參數(shù)的對象康吵。這里的泛型Data=我們使用AsyncTask的時候第三個參數(shù)Result 也就是doInBackground方法里面返回的Result。那么當(dāng)postResult方法執(zhí)行以后通過mHandler將消息發(fā)出去以后會做什么事情呢访递?收先看圖11的代碼也就是說當(dāng)postResult方法執(zhí)行以后圖11?handleMessage方法會執(zhí)行晦嵌,然后會執(zhí)行case語句里面的MESSAGE_POST_RESULT,也就是會執(zhí)行這句話result.mTask.finish(result.mData[0]);拷姿,首先result.mTask拿到AsyncTask對象惭载,然后調(diào)用AsyncTask的finish方法并將doInBackground的返回結(jié)果傳遞進(jìn)去,緊接著我們看看在finish方法里面做了什么响巢。

圖13
圖14
圖15
圖16
圖17

由上面的代碼我們可以看出描滔,當(dāng)finish方法執(zhí)行以后,首先會調(diào)用isCancelled()進(jìn)行任務(wù)執(zhí)行校驗踪古,如果在執(zhí)行doInBackground的過程中拋出了異常含长,會將mCancelled.set(true);這個前面已經(jīng)說過,isCancelled()任務(wù)校驗完畢以后伏穆,如果返回false則說明任務(wù)執(zhí)行成功拘泞,然后回調(diào)onPostExecute方法,這下終于明白了onPostExecute方法為什么運行在主線程枕扫,為什么在doInBackground方法里面的返回結(jié)果會在onPostExecute方法里面拿到了吧陪腌,如過isCancelled()返回true則說明任務(wù)執(zhí)行失敗回調(diào)onCancelled方法。至此整個new?WorkerRunnable 的初始化工作以及執(zhí)行流程分析已接近尾聲,但是我們貌似還遺漏了一個東西诗鸭,那就是當(dāng)我們在doInBackground方法里面手動調(diào)用publishProgress()方法以后商叹,onProgressUpdate方法會回調(diào),并且能實時的和主線程交互只泼,接下來我們分析一下調(diào)用publishProgress后做了那些事情剖笙,請看源碼

圖18

通過源碼我們可以看出也是通過mHandler發(fā)送一個消息,然后回調(diào)InternalHandler 的handleMessage方法并且會執(zhí)行case分支里面的MESSAGE_POST_PROGRESS分支请唱,然后回調(diào)AsyncTask的onProgressUpdate方法并將從publishProgress傳遞過來的值傳遞過去弥咪。到此整個WorkerRunnable的call方法執(zhí)行流程分析完畢。

mWoker看完了十绑,應(yīng)該到我們的mFuture了聚至,依然是在構(gòu)造方法中完成mFuture的初始化,將mWorker作為參數(shù)本橙,復(fù)寫了其done方法扳躬。看源碼

圖19

可以看到當(dāng)mWorker的call方法執(zhí)行完畢以后甚亭,會回調(diào)FutureTask的done方法贷币,done方法執(zhí)行后、會調(diào)用:postResultIfNotInvoked(get());get()表示獲取mWorker的call的返回值亏狰,即Result.然后看postResultIfNotInvoked方法

圖20

如果mTaskInvoked不為true役纹,則執(zhí)行postResult(執(zhí)行postResult和上面分析的在call中調(diào)用的postResult的流程是一模一樣的,因為調(diào)用的是同一個方法暇唾,這里再不啰嗦)促脉;但是在mWorker初始化時就已經(jīng)將mTaskInvoked為true,所以一般這個postResult執(zhí)行不到策州。好了瘸味,到了這里,整個AsyncTask的初始化做的事情已經(jīng)全部分析完畢够挂,不過這里一直是初始化這兩個對象的代碼旁仿,并沒有真正的執(zhí)行。下面我們看真正調(diào)用執(zhí)行的地方下硕。

2 .execute()的時候做了哪些事情丁逝?看源碼


圖21
圖22
圖23
圖24

分析 :當(dāng)我們.execute()以后首先會執(zhí)行executeOnExecutor()方法,執(zhí)行的時候需要一個Executor梭姓,也就是sDefaultExecutor霜幼,而sDefaultExecutor = SERIAL_EXECUTOR,那么SERIAL_EXECUTOR是誰誉尖?由代碼可以看出來SERIAL_EXECUTOR就是SerialExecutor罪既,緊接著看SerialExecutor里面是什么,跟隨代碼我們可以看到首先里面有一個ArrayDeque的隊列,然后有個Runnable琢感,緊接我們會發(fā)現(xiàn)首先會offer一個任務(wù)進(jìn)去丢间,然后調(diào)用scheduleNext();開始執(zhí)行任務(wù),因為ArrayDeque 是Java里面的一個雙端隊列的線性實現(xiàn)驹针,由此可以得出AsyncTask只使用于執(zhí)行立即需要啟動并且異步執(zhí)行的生命周期短暫的使用場景烘挫。那么線程池時在哪里被開啟的?緊接著我們看executeOnExecutor()內(nèi)部實現(xiàn)源碼柬甥。

圖26

當(dāng)execute()調(diào)用以后緊接著調(diào)用executeOnExecutor()

1當(dāng)?executeOnExecutor()執(zhí)行以后首先?設(shè)置當(dāng)前AsyncTask的狀態(tài)為RUNNING饮六,上面的switch也可以看出,每個異步任務(wù)在完成前只能執(zhí)行一次苛蒲。

2執(zhí)行了onPreExecute()卤橄,當(dāng)前依然在UI線程,所以我們可以在其中做一些準(zhǔn)備工作臂外。

3?將我們傳入的參數(shù)賦值給了mWorker.mParams

4?exec.execute(mFuture) 窟扑,當(dāng)這句被調(diào)用以后,圖24里面的run方法會執(zhí)行漏健,run方法執(zhí)行以后嚎货,r.run()開始執(zhí)行,這里的r.run其實調(diào)用的就是FutureTask里面的run方法漾肮,當(dāng)FutureTask里面的run方法開始執(zhí)行以后厂抖,Callable的call方法就會執(zhí)行,因為FutureTask實現(xiàn)了?RunnableFuture接口克懊,而RunnableFuture接口繼承了Runnable和Future接口,再FutureTask里面的run方法里面調(diào)用了Callable的call方法七蜘,call方法執(zhí)行以后谭溉,我們整個AsyncTask就開始運轉(zhuǎn)起來了。到此整個AsyncTask的分析流程全部結(jié)束橡卤。

總結(jié)

1 本文以new?AsyncTask的構(gòu)造方法講述了AsyncTask的工作流程扮念,以及.execute()以后線程池時如何開啟兩部分對源碼進(jìn)行了分析。

2?new?AsyncTask()流程回顧碧库,首先創(chuàng)建主線程的mHandler 柜与,然后初始化mWork 和 mFuture

3?.execute()的流程回顧,開啟線程池嵌灰,執(zhí)行?run方法弄匕,并且讓 mFuture 的 run執(zhí)行進(jìn)而執(zhí)行Callable的call方法,讓AsyncTask運轉(zhuǎn)起來沽瞭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迁匠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌城丧,老刑警劉巖延曙,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亡哄,居然都是意外死亡枝缔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蚊惯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愿卸,“玉大人,你說我怎么就攤上這事拣挪〔磷茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵菠劝,是天一觀的道長赊舶。 經(jīng)常有香客問我,道長赶诊,這世上最難降的妖魔是什么笼平? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮舔痪,結(jié)果婚禮上寓调,老公的妹妹穿的比我還像新娘。我一直安慰自己锄码,他們只是感情好夺英,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滋捶,像睡著了一般痛悯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上重窟,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天载萌,我揣著相機(jī)與錄音,去河邊找鬼巡扇。 笑死扭仁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厅翔。 我是一名探鬼主播乖坠,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼知给!你這毒婦竟也來了瓤帚?” 一聲冷哼從身側(cè)響起描姚,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戈次,沒想到半個月后轩勘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡怯邪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年绊寻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悬秉。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡澄步,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出和泌,到底是詐尸還是另有隱情村缸,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布武氓,位于F島的核電站梯皿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏县恕。R本人自食惡果不足惜东羹,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望忠烛。 院中可真熱鬧属提,春花似錦、人聲如沸美尸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽师坎。三九已至求类,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屹耐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工椿猎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留惶岭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓犯眠,卻偏偏與公主長得像按灶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子筐咧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355