Android中UI線程與後臺線程交互設計的5種方法

 

轉載地址: http://www.cr173.com/html/19165_1.htmlhtml

 

我想關於這個話題已經有不少前輩討論過了。今天算是一次學習總結吧。android

在android的設計思想中,爲了確保用戶順滑的操做體驗。一些耗時的任務不可以在UI線程中運行,像訪問網絡就屬於這類任務。所以咱們必需要從新開啓一個後臺線程運行這些任務。然而,每每這些任務最終又會直接或者間接的須要訪問和控制UI控件。例如訪問網絡獲取數據,而後須要將這些數據處理顯示出來。就出現了上面所說的狀況。本來這是在正常不過的現象了,可是android規定除了UI線程外,其餘線程都不能夠對那些UI控件訪問和操控。爲了解決這個問題,因而就引出了咱們今天的話題。Android中後臺線程如何與UI線程交互。數組

據我所知android提供瞭如下幾種方法,用於實現後臺線程與UI線程的交互。網絡

一、handleride

二、Activity.runOnUIThread(Runnable)函數

三、View.Post(Runnable)工具

四、View.PostDelayed(Runnabe,long)oop

五、AsyncTaskpost

方法一: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線程,咱們須要作的就是

一、在B線程中調用Looper.prepare和Looper.loop。(主線程不須要)

二、 編寫Handler類,重寫其中的handleMessage方法。

三、建立Handler類的實例,並綁定looper

四、調用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)

 這個方法至關簡單,咱們要作的只是如下幾步

一、編寫後臺線程,這回你能夠直接調用UI控件

二、建立後臺線程的實例

三、調用UI線程對應的Activity的runOnUIThread方法,將後臺線程實例做爲參數傳入其中。

注意:無需調用後臺線程的start方法

方法三:View.Post(Runnable)

 該方法和方法二基本相同,只是在後臺線程中能操控的UI控件被限制了,只能是指定的UI控件View。方法以下

一、編寫後臺線程,這回你能夠直接調用UI控件,可是該UI控件只能是View

二、建立後臺線程的實例

三、調用UI控件View的post方法,將後臺線程實例做爲參數傳入其中。

方法四:View.PostDelayed(Runnabe,long)

該方法是方法三的補充,long參數用於制定多少時間後運行後臺進程 

方法五:AsyncTask

AsyncTask是一個專門用來處理後臺進程與UI線程的工具。經過AsyncTask,咱們能夠很是方便的進行後臺線程和UI線程之間的交流。

那麼AsyncTask是如何工做的哪。

AsyncTask擁有3個重要參數

一、Params 

二、Progress

三、Result

Params是後臺線程所需的參數。在後臺線程進行做業的時候,他須要外界爲其提供必要的參數,就好像是一個用於下載圖片的後臺進程,他須要的參數就是圖片的下載地址。

Progress是後臺線程處理做業的進度。依舊上面的例子說,就是下載圖片這個任務完成了多少,是20%仍是60%。這個數字是由Progress提供。

Result是後臺線程運行的結果,也就是須要提交給UI線程的信息。按照上面的例子來講,就是下載完成的圖片。

AsyncTask還擁有4個重要的回調方法。

一、onPreExecute

二、doInBackground

三、onProgressUpdate

四、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個方法,你要作的就是

一、編寫一個繼承AsyncTask的類,並聲明3個參數的類型,編寫4個回調方法的內容。

二、而後在UI線程中建立該類(必須在UI線程中建立)。

三、最後調用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。

相關文章
相關標籤/搜索