android中有主線程、子線程、服務(wù)。我認(rèn)為這三個(gè)概念是不應(yīng)該被分開來看的。它們之間總有著千絲萬縷的聯(lián)系。而我也曾一度在三個(gè)概念見徘徊不定。把費(fèi)時(shí)操作最開始寫在主線程中,然后想寫在服務(wù)中,最后發(fā)現(xiàn)服務(wù)也是在主線程中,又開子線程。如果剛開始就搞清楚它們之間的使用場(chǎng)景,也許就不會(huì)那么糾結(jié)了。
一、基礎(chǔ)知識(shí):
Message:消息,其中包含了消息ID,消息處理對(duì)象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊(duì),終由Handler處理。
Handler:處理者,負(fù)責(zé)Message的發(fā)送及處理。使用Handler時(shí),需要實(shí)現(xiàn)handleMessage(Message msg)方法來對(duì)特定的Message進(jìn)行處理,例如更新UI等。
MessageQueue:消息隊(duì)列,用來存放Handler發(fā)送過來的消息,并按照FIFO規(guī)則執(zhí)行。當(dāng)然,存放Message并非實(shí)際意義的保存,而是將Message以鏈表的方式串聯(lián)起來的,等待Looper的抽取。
Looper:消息泵,不斷地從MessageQueue中抽取Message執(zhí)行。因此,一個(gè)MessageQueue需要一個(gè)Looper。
Thread:線程,負(fù)責(zé)調(diào)度整個(gè)消息循環(huán),即消息循環(huán)的執(zhí)行場(chǎng)所。
Android系統(tǒng)的消息隊(duì)列和消息循環(huán)都是針對(duì)具體線程的,一個(gè)線程可以存在(當(dāng)然也可以不存在)一個(gè)消息隊(duì)列和一個(gè)消 息循環(huán)(Looper),特定線程的消息只能分發(fā)給本線程,不能進(jìn)行跨線程,跨進(jìn)程通訊。但是創(chuàng)建的工作線程默認(rèn)是沒有消息循環(huán)和消息隊(duì)列的,如果想讓該 線程具有消息隊(duì)列和消息循環(huán),需要在線程中首先調(diào)用Looper.prepare()來創(chuàng)建消息隊(duì)列,然后調(diào)用Looper.loop()進(jìn)入消息循環(huán)。
Handler類
Handler類可以對(duì)運(yùn)行在不同線程中的多個(gè)任務(wù)進(jìn)行排隊(duì),并使用Message和Runnable對(duì)象安排這些任務(wù)。Handler對(duì)象的四個(gè)構(gòu)造方法:
1)Handler()
這個(gè)handler將自動(dòng)與當(dāng)前運(yùn)行線程相關(guān)聯(lián),即這個(gè)handler與當(dāng)前運(yùn)行的線程使用同一個(gè)消息隊(duì)列,并可以處理該隊(duì)列中的消息。直接利用handler對(duì)象post一個(gè)runnable對(duì)象,相當(dāng)于直接調(diào)用runnable對(duì)象的run函數(shù),不會(huì)創(chuàng)建一個(gè)新線程,而是在原線程中直接調(diào)用run方法,runnalbe對(duì)象和主用戶界面線程的ID是相同實(shí)例。一般在handleMessage中處理更新UI界面的操作。
2)Handler(Looper looper)
這個(gè)handler對(duì)象將與參數(shù)所表示的Looper相關(guān)聯(lián)。但此時(shí)線程應(yīng)該為一個(gè)特殊類HandlerThread類(一個(gè)有Looper的Thread類)
3) Handler (Handler.Callback callback)
這個(gè)handler和當(dāng)前所在線程關(guān)聯(lián)并且返回一個(gè)可以處理messages的返回接口。但是如果當(dāng)前線程沒有l(wèi)ooper,則handler不能接收messages并拋出異常。
4)Handler (Looper looper, Handler.Callback callback)
異步任務(wù)
AsyncTask主要用來更新UI線程,比較耗時(shí)的操作可以在AsyncTask中使用。
AsyncTask是個(gè)抽象類,使用時(shí)需要繼承這個(gè)類,然后調(diào)用execute()方法。注意繼承時(shí)需要設(shè)定三個(gè)泛型Params,Progress和Result的類型,如AsyncTask<Void,Inetger,Void>:
Params是指調(diào)用execute()方法時(shí)傳入的參數(shù)類型和doInBackgound()的參數(shù)類型
Progress是指更新進(jìn)度時(shí)傳遞的參數(shù)類型,即publishProgress()和onProgressUpdate()的參數(shù)類型
Result是指doInBackground()的返回值類型
上面的說明涉及到幾個(gè)方法:doInBackgound() 這個(gè)方法是繼承AsyncTask必須要實(shí)現(xiàn)的,運(yùn)行于后臺(tái),耗時(shí)的操作可以在這里做
publishProgress() 更新進(jìn)度,給onProgressUpdate()傳遞進(jìn)度參數(shù)
onProgressUpdate() 在publishProgress()調(diào)用完被調(diào)用,更新進(jìn)度
wait() 和 notify機(jī)制
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}
synchronized(obj) {
condition = true;
obj.notify();
}
需要注意的概念是:
對(duì)象x不能是基本類型,應(yīng)該為可引用類型或者javabean
# 調(diào)用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) {…} 代碼段內(nèi)。
# 調(diào)用obj.wait()后,線程A就釋放了obj的鎖,否則線程B無法獲得obj鎖,也就無法在synchronized(obj) {…} 代碼段內(nèi)喚醒A。
# 當(dāng)obj.wait()方法返回后,線程A需要再次獲得obj鎖,才能繼續(xù)執(zhí)行。
# 如果A1,A2,A3都在obj.wait(),則B調(diào)用obj.notify()只能喚醒A1,A2,A3中的一個(gè)(具體哪一個(gè)由JVM決定)。
# obj.notifyAll()則能全部喚醒A1,A2,A3,但是要繼續(xù)執(zhí)行obj.wait()的下一條語句,必須獲得obj鎖,因此,A1,A2,A3只有一個(gè)有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行,例如A1,其余的需要等待A1釋放obj鎖之后才能繼續(xù)執(zhí)行。
# 當(dāng)B調(diào)用obj.notify/notifyAll的時(shí)候,B正持有obj鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖后,A1,A2,A3中的一個(gè)才有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行。
二、實(shí)際使用遇到的問題
1、在子線程中使用runOnUiThread時(shí),其中若要使用子線程中定義的數(shù)據(jù)時(shí),eclipse都會(huì)提示將該變量的定義為final;而如果在主線程中調(diào)用,不會(huì)報(bào)錯(cuò),則發(fā)現(xiàn)其值會(huì)變?yōu)?.0。(runOnUiThread方法是一種比較簡單的在主線程中工作的地方)