Android中UI執行緒與後臺執行緒互動設計的5種方法
在android的設計思想中,為了確保使用者順滑的操作體驗。一些耗時的任務不能夠在UI執行緒中執行,像訪問網路就屬於這類任務。因此我們必須要重新開啟一個後臺執行緒執行這些任務。然而,往往這些任務最終又會直接或者間接的需要訪問和控制UI控制元件。例如訪問網路獲取資料,然後需要將這些資料處理顯示出來。就出現了上面所說的情況。原本這是在正常不過的現象了,但是android規定除了UI執行緒外,其他執行緒都不可以對那些UI控制元件訪問和操控。為了解決這個問題,於是就引出了我們今天的話題。Android中後臺執行緒如何與UI執行緒互動。
據我所知android提供了以下幾種方法,用於實現後臺執行緒與UI執行緒的互動。
1、handler
2、Activity.runOnUIThread(Runnable)
3、View.Post(Runnable)
4、View.PostDelayed(Runnabe,long)
5、AsyncTask
方法一:handler
handler是android中專門用來線上程之間傳遞資訊類的工具。
要講明handler的用法非常簡單,但是我在這裡會少許深入的講一下handler的執行機制。
為了能夠讓handler線上程間傳遞訊息,我們還需要用到幾個類。他們是looper,messageQueue,message。
這裡說的looper可不是前段時間的好萊塢大片環形使者,他的主要功能是為特定單一執行緒執行一個訊息環。一個執行緒對應一個looper。同樣一個looper對應一個執行緒。這就是所謂的特定單一。一般情況下,在一個執行緒建立時他本身是不會生產他特定單一的looper的(主執行緒是個特例)。因此我們需要手動的把一個looper與執行緒相關聯。其方法只需在需要關聯的looper的執行緒中呼叫Looper.prepare。之後我們再呼叫Looper.loop啟動looper。
說了這麼多looper的事情,到底這個looper有什麼用哪。其實之前我們已經說到了,他是為執行緒執行一個訊息環。具體的說,在我們將特定單一looper與執行緒關聯的時候,looper會同時生產一個messageQueue。他是一個訊息佇列,looper會不停的從messageQuee中取出訊息,也就是message。然後執行緒就會根據message中的內容進行相應的操作。
那麼messageQueue中的message是從哪裡來的哪?那就要提到handler了。在我們建立handler的時候,我們需要與特定的looper繫結。這樣通過handler我們就可以把message傳遞給特定的looper,繼而傳遞給特定的執行緒。在這裡,looper和handler並非一一對應的。一個looper可以對應多個handler,而一個handler只能對應一個looper(突然想起了一夫多妻制,呵呵)。這裡補充一下,handler和looper的繫結,是在構建handler的時候實現的,具體查詢handler的建構函式。
在我們建立handler並與相應looper繫結之後,我們就可以傳遞message了。我們只需要呼叫handler的sendMessage函式,將message作為引數傳遞給相應執行緒。之後這個message就會被塞進looper的messageQueue。然後再被looper取出來交給執行緒處理。
這裡要補充說一下message,雖然我們可以自己建立一個新的message,但是更加推薦的是呼叫handler的obtainMessage方法來獲取一個message。這個方法的作用是從系統的訊息池中取出一個message,這樣就可以避免message建立和銷燬帶來的資源浪費了(這也就是算得上重複利用的綠色之舉了吧)。
突然發現有一點很重要的地方沒有講到,那就是執行緒從looper收到message之後他是如何做出響應的嘞。其實原來執行緒所需要做出何種響應需要我們在我們自定義的handler類中的handleMessage重構方法中編寫。之後才是之前說的建立handler並繫結looper。
好吧說的可能喲點亂,總結一下利用handler傳遞資訊的方法。
假設A執行緒要傳遞資訊給B執行緒,我們需要做的就是
1、在B執行緒中呼叫Looper.prepare和Looper.loop。(主執行緒不需要)
2、 編寫Handler類,重寫其中的handleMessage方法。
3、建立Handler類的例項,並繫結looper
4、呼叫handler的sentMessage方法傳送訊息。
到這裡,我們想handler的執行機制我應該是闡述的差不多了吧,最後再附上一段程式碼,供大家參考。
1 public class MyHandlerActivity extends Activity { 2 TextView textView; 3 MyHandler myHandler; 4 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.handlertest); 8 9 //實現建立handler並與looper繫結。這裡沒有涉及looper與 //執行緒的關聯是因為主執行緒在建立之初就已有looper 10 myHandler=MyHandler(MyHandlerActivitythis.getMainLooper()); 11 textView = (textView) findViewById(R.id.textView); 12 13 MyThread m = new MyThread(); 14 new Thread(m).start(); 15 } 16 17 18 class MyHandler extends Handler { 19 public MyHandler() { 20 } 21 22 public MyHandler(Looper L) { 23 super(L); 24 } 25 26 // 必須重寫這個方法,用於處理message 27 @Override 28 public void handleMessage(Message msg) { 29 // 這裡用於更新UI 30 Bundle b = msg.getData(); 31 String color = b.getString("color"); 32 MyHandlerActivity.this.textView.setText(color); 33 } 34 } 35 36 class MyThread implements Runnable { 37 public void run() { 38 //從訊息池中取出一個message 39 Message msg = myHandler.obtainMessage(); 40 //Bundle是message中的資料 41 Bundle b = new Bundle(); 42 b.putString("color", "我的"); 43 msg.setData(b); 44 //傳遞資料 45 myHandler.sendMessage(msg); // 向Handler傳送訊息,更新UI 46 } 47 }
方法二:Activity.runOnUIThread(Runnable)
這個方法相當簡單,我們要做的只是以下幾步
1、編寫後臺執行緒,這回你可以直接呼叫UI控制元件
2、建立後臺執行緒的例項
3、呼叫UI執行緒對應的Activity的runOnUIThread方法,將後臺執行緒例項作為引數傳入其中。
注意:無需呼叫後臺執行緒的start方法
方法三:View.Post(Runnable)
該方法和方法二基本相同,只是在後臺執行緒中能操控的UI控制元件被限制了,只能是指定的UI控制元件View。方法如下
1、編寫後臺執行緒,這回你可以直接呼叫UI控制元件,但是該UI控制元件只能是View
2、建立後臺執行緒的例項
3、呼叫UI控制元件View的post方法,將後臺執行緒例項作為引數傳入其中。
方法四:View.PostDelayed(Runnabe,long)
該方法是方法三的補充,long引數用於制定多少時間後執行後臺程式
方法五:AsyncTask
AsyncTask是一個專門用來處理後臺程式與UI執行緒的工具。通過AsyncTask,我們可以非常方便的進行後臺執行緒和UI執行緒之間的交流。
那麼AsyncTask是如何工作的哪。
AsyncTask擁有3個重要引數
1、Params
2、Progress
3、Result
Params是後臺執行緒所需的引數。在後臺執行緒進行作業的時候,他需要外界為其提供必要的引數,就好像是一個用於下載圖片的後臺程式,他需要的引數就是圖片的下載地址。
Progress是後臺執行緒處理作業的進度。依舊上面的例子說,就是下載圖片這個任務完成了多少,是20%還是60%。這個數字是由Progress提供。
Result是後臺執行緒執行的結果,也就是需要提交給UI執行緒的資訊。按照上面的例子來說,就是下載完成的圖片。
AsyncTask還擁有4個重要的回撥方法。
1、onPreExecute
2、doInBackground
3、onProgressUpdate
4、onPostExecute
onPreExecute執行在UI執行緒,主要目的是為後臺執行緒的執行做準備。當他執行完成後,他會呼叫doInBackground方法。
doInBackground執行在後臺執行緒,他用來負責執行任務。他擁有引數Params,並且返回Result。在後臺執行緒的執行當中,為了能夠更新作業完成的進度,需要在doInbackground方法中呼叫PublishProgress方法。該方法擁有引數Progress。通過該方法可以更新Progress的資料。然後當呼叫完PublishProgress方法,他會呼叫onProgressUpdate方法用於更新進度。
onProgressUpdate執行在UI執行緒,主要目的是用來更新UI執行緒中顯示進度的UI控制元件。他擁有Progress引數。在doInBackground中呼叫PublishProgress之後,就會自動調onProgressUpdate方法
onPostExecute執行在UI執行緒,當doInBackground方法執行完後,他會呼叫onPostExecute方法,並傳入Result。在onPostExecute方法中,就可以將Result更新到UI控制元件上。
明白了上面的3個引數和4個方法,你要做的就是
1、編寫一個繼承AsyncTask的類,並宣告3個引數的型別,編寫4個回撥方法的內容。
2、然後在UI執行緒中建立該類(必須在UI執行緒中建立)。
3、最後呼叫AsyncTask的execute方法,傳入Parmas引數(同樣必須在UI執行緒中呼叫)。
這樣就大功告成了。
另外值得注意的2點就是,千萬不要直接呼叫那四個回撥方法。還有就是一個AsyncTask例項只能執行一次,否則就出錯哦。
以上是AsyncTask的基本用法,更加詳細的內容請參考android官方文件。最後附上一段程式碼,供大家參考。
1 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> 2 //在這裡宣告瞭Params、Progress、Result引數的型別 3 { 4 //因為這裡不需要使用onPreExecute回撥方法,所以就沒有加入該方法 5 6 //後臺執行緒的目的是更具URL下載資料 7 protected Long doInBackground(URL... urls) { 8 int count = urls.length;//urls是陣列,不止一個下載連結 9 long totalSize = 0;//下載的資料 10 for (int i = 0; i < count; i++) { 11 //Download是用於下載的一個類,和AsyncTask無關,大家可以忽略他的實現 12 totalSize += Downloader.downloadFile(urls[i]); 13 publishProgress((int) ((i / (float) count) * 100));//更新下載的進度 14 // Escape early if cancel() is called 15 if (isCancelled()) break; 16 } 17 return totalSize; 18 } 19 20 //更新下載進度 21 protected void onProgressUpdate(Integer... progress) { 22 setProgressPercent(progress[0]); 23 } 24 25 //將下載的資料更新到UI執行緒 26 protected void onPostExecute(Long result) { 27 showDialog("Downloaded " + result + " bytes"); 28 } 29 } 30
有了上面的這個類,接下你要做的就是在UI執行緒中建立例項,並呼叫execute方法,傳入URl引數就可以了。
這上面的5種方法各有優點。但是究其根本,其實後面四種方法都是基於handler方法的包裝。在一般的情形下後面四種似乎更值得推薦。但是當情形比較複雜,還是推薦使用handler。
最後補充一下,這是我的第一篇部落格。存在很多問題請大家多多指教。尤其是文中涉及到內容,有嚴重的技術問題,大家一定要給我指明啊。拜託各位了。
相關文章
- 執行緒、開啟執行緒的兩種方式、執行緒下的Join方法、守護執行緒執行緒
- Android程式框架:執行緒與執行緒池Android框架執行緒
- 子執行緒與UI執行緒的通訊(委託)執行緒UI
- [短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全執行緒程式設計
- Android中後臺的服務和多執行緒Android執行緒
- python中5種執行緒鎖Python執行緒
- 建立執行緒的4種方法 and 執行緒的生命週期執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- 執行緒與多執行緒執行緒
- 後臺執行緒(daemon)執行緒
- Android多執行緒之執行緒池Android執行緒
- Android中的執行緒池Android執行緒
- Thread 中的 join() 方法的作用是呼叫執行緒等待該執行緒執行完後,再繼續執行thread執行緒
- 多執行緒(五)---執行緒的Yield方法執行緒
- java 多執行緒之使用 interrupt 停止執行緒的幾種方法Java執行緒
- java執行緒執行緒休眠,sleep方法Java執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- 在netty3.x中存在兩種執行緒:boss執行緒和worker執行緒。Netty執行緒
- 主執行緒等待所有其他執行緒執行完畢,然後再繼續執行主執行緒的邏輯,有以下幾種方法可以實現:執行緒
- 執行緒、執行緒與程式、ULT與KLT執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- java中執行緒池的生命週期與執行緒中斷Java執行緒
- Android中的執行緒通訊Android執行緒
- Android JNI 中的執行緒操作Android執行緒
- 多執行緒程式設計基礎(一)-- 執行緒的使用執行緒程式設計
- Java 執行緒的5種狀態Java執行緒
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- java多執行緒5:執行緒間的通訊Java執行緒
- 走進Java Android 的執行緒世界(二)執行緒池JavaAndroid執行緒
- Android執行緒池Android執行緒
- 解惑Android的post()方法究竟執行在哪個執行緒中Android執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- Java執行緒與併發程式設計實踐----額外的執行緒能力Java執行緒程式設計
- 【 Thread】建立執行緒的2種方法thread執行緒
- 多執行緒程式設計基礎(二)-- 執行緒池的使用執行緒程式設計
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- mysql後臺執行緒詳解MySql執行緒
- Java多執行緒學習(1)建立執行緒與執行緒的生命週期Java執行緒