UI線程與后臺(tái)線程交互設(shè)計(jì)的5種方法

UI線程與后臺(tái)線程交互設(shè)計(jì)的5種方法

在android的設(shè)計(jì)思想中,為了確保用戶順滑的操作體 驗(yàn)。一些耗時(shí)的任務(wù)不能夠在UI線程中運(yùn)行,像訪問(wèn)網(wǎng)絡(luò)就屬于這類任務(wù)。因此我們必須要重新開(kāi)啟一個(gè)后臺(tái)線程運(yùn)行這些任務(wù)。然而,往往這些任務(wù)最終又會(huì)直 接或者間接的需要訪問(wèn)和控制UI控件。例如訪問(wèn)網(wǎng)絡(luò)獲取數(shù)據(jù),然后需要將這些數(shù)據(jù)處理顯示出來(lái)。就出現(xiàn)了上面所說(shuō)的情況。原本這是在正常不過(guò)的現(xiàn)象了,但 是android規(guī)定除了UI線程外,其他線程都不可以對(duì)那些UI控件訪問(wèn)和操控。為了解決這個(gè)問(wèn)題,于是就引出了我們今天的話題。Android中后臺(tái) 線程如何與UI線程交互。

據(jù)我所知android提供了以下幾種方法,用于實(shí)現(xiàn)后臺(tái)線程與UI線程的交互。

  • handler
  • Activity.runOnUIThread(Runnable)
  • View.Post(Runnable)
  • View.PostDelayed(Runnabe,long)
  • AsyncTask

handler

handler是android中專門(mén)用來(lái)在線程之間傳遞信息類的工具。

要講明handler的用法非常簡(jiǎn)單,但是我在這里會(huì)少許深入的講一下handler的運(yùn)行機(jī)制。

為了能夠讓handler在線程間傳遞消息,我們還需要用到幾個(gè)類。他們是looper,messageQueue,message。

這 里說(shuō)的looper可不是前段時(shí)間的好萊塢大片環(huán)形使者,他的主要功能是為特定單一線程運(yùn)行一個(gè)消息環(huán)。一個(gè)線程對(duì)應(yīng)一個(gè)looper。同樣一個(gè) looper對(duì)應(yīng)一個(gè)線程。這就是所謂的特定單一。一般情況下,在一個(gè)線程創(chuàng)建時(shí)他本身是不會(huì)生產(chǎn)他特定單一的looper的(主線程是個(gè)特例)。因此我 們需要手動(dòng)的把一個(gè)looper與線程相關(guān)聯(lián)。其方法只需在需要關(guān)聯(lián)的looper的線程中調(diào)用Looper.prepare。之后我們?cè)僬{(diào)用 Looper.loop啟動(dòng)looper。

說(shuō)了這么多l(xiāng)ooper的事情,到底這個(gè)looper有什么用哪。其實(shí)之前我們已經(jīng)說(shuō)到了,他是 為線程運(yùn)行一個(gè)消息環(huán)。具體的說(shuō),在我們將特定單一looper與線程關(guān)聯(lián)的時(shí)候,looper會(huì)同時(shí)生產(chǎn)一個(gè)messageQueue。他是一個(gè)消息隊(duì) 列,looper會(huì)不停的從messageQuee中取出消息,也就是message。然后線程就會(huì)根據(jù)message中的內(nèi)容進(jìn)行相應(yīng)的操作。

那 么messageQueue中的message是從哪里來(lái)的哪?那就要提到handler了。在我們創(chuàng)建handler的時(shí)候,我們需要與特定的 looper綁定。這樣通過(guò)handler我們就可以把message傳遞給特定的looper,繼而傳遞給特定的線程。在這里,looper和 handler并非一一對(duì)應(yīng)的。一個(gè)looper可以對(duì)應(yīng)多個(gè)handler,而一個(gè)handler只能對(duì)應(yīng)一個(gè)looper(突然想起了一夫多妻制,呵 呵)。這里補(bǔ)充一下,handler和looper的綁定,是在構(gòu)建handler的時(shí)候?qū)崿F(xiàn)的,具體查詢handler的構(gòu)造函數(shù)。

在我 們創(chuàng)建handler并與相應(yīng)looper綁定之后,我們就可以傳遞message了。我們只需要調(diào)用handler的sendMessage函數(shù),將 message作為參數(shù)傳遞給相應(yīng)線程。之后這個(gè)message就會(huì)被塞進(jìn)looper的messageQueue。然后再被looper取出來(lái)交給線程 處理。

Handle圖解

這 里要補(bǔ)充說(shuō)一下message,雖然我們可以自己創(chuàng)建一個(gè)新的message,但是更加推薦的是調(diào)用handler的obtainMessage方法來(lái)獲 取一個(gè)message。這個(gè)方法的作用是從系統(tǒng)的消息池中取出一個(gè)message,這樣就可以避免message創(chuàng)建和銷(xiāo)毀帶來(lái)的資源浪費(fèi)了(這也就是算 得上重復(fù)利用的綠色之舉了吧)。

突然發(fā)現(xiàn)有一點(diǎn)很重要的地方?jīng)]有講到,那就是線程從looper收到message之后他是如何做出響應(yīng)的 嘞。其實(shí)原來(lái)線程所需要做出何種響應(yīng)需要我們?cè)谖覀冏远x的handler類中的handleMessage重構(gòu)方法中編寫(xiě)。之后才是之前說(shuō)的創(chuàng)建 handler并綁定looper。

好吧說(shuō)的可能喲點(diǎn)亂,總結(jié)一下利用handler傳遞信息的方法。

假設(shè)A線程要傳遞信息給B線程,我們需要做的就是

1、在B線程中調(diào)用Looper.prepare和Looper.loop。(主線程不需要)

2、 編寫(xiě)Handler類,重寫(xiě)其中的handleMessage方法。

3、創(chuàng)建Handler類的實(shí)例,并綁定looper

4、調(diào)用handler的sentMessage方法發(fā)送消息。

到這里,我們想handler的運(yùn)行機(jī)制我應(yīng)該是闡述的差不多了吧,最后再附上一段代碼,供大家參考。

public class MyHandlerActivity extends Activity {
       TextView textView;
       MyHandler myHandler;
   
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.handlertest);
   
           //實(shí)現(xiàn)創(chuàng)建handler并與looper綁定。這里沒(méi)有涉及l(fā)ooper與
            //線程的關(guān)聯(lián)是因?yàn)橹骶€程在創(chuàng)建之初就已有l(wèi)ooper
          myHandler=MyHandler(MyHandlerActivitythis.getMainLooper());
          textView = (textView) findViewById(R.id.textView);
         
          MyThread m = new MyThread();
         new Thread(m).start();
      }
  
  
      class MyHandler extends Handler {
          public MyHandler() {
          }
  
          public MyHandler(Looper L) {
              super(L);
          }
  
          // 必須重寫(xiě)這個(gè)方法,用于處理message
          @Override
          public void handleMessage(Message msg) {
              // 這里用于更新UI
              Bundle b = msg.getData();
              String color = b.getString("color");
              MyHandlerActivity.this.textView.setText(color);
          }
      }
  
      class MyThread implements Runnable {
          public void run() {
              //從消息池中取出一個(gè)message
              Message msg = myHandler.obtainMessage();
              //Bundle是message中的數(shù)據(jù)
              Bundle b = new Bundle();
              b.putString("color", "我的");
              msg.setData(b);
              //傳遞數(shù)據(jù)
              myHandler.sendMessage(msg); // 向Handler發(fā)送消息,更新UI
          }
      }

Activity.runOnUIThread(Runnable)

這個(gè)方法相當(dāng)簡(jiǎn)單,我們要做的只是以下幾步

  • 1、編寫(xiě)后臺(tái)線程,這回你可以直接調(diào)用UI控件
  • 2、創(chuàng)建后臺(tái)線程的實(shí)例
  • 3、調(diào)用UI線程對(duì)應(yīng)的Activity的runOnUIThread方法,將后臺(tái)線程實(shí)例作為參數(shù)傳入其中。

注意:無(wú)需調(diào)用后臺(tái)線程的start方法

View.Post(Runnable)

該方法和方法二基本相同,只是在后臺(tái)線程中能操控的UI控件被限制了,只能是指定的UI控件View。方法如下

  • 1、編寫(xiě)后臺(tái)線程,這回你可以直接調(diào)用UI控件,但是該UI控件只能是View

  • 2、創(chuàng)建后臺(tái)線程的實(shí)例

  • 3、調(diào)用UI控件View的post方法,將后臺(tái)線程實(shí)例作為參數(shù)傳入其中。

View.PostDelayed(Runnabe,long)

該方法是方法三的補(bǔ)充,long參數(shù)用于制定多少時(shí)間后運(yùn)行后臺(tái)進(jìn)程

AsyncTask

AsyncTask是一個(gè)專門(mén)用來(lái)處理后臺(tái)進(jìn)程與UI線程的工具。通過(guò)AsyncTask,我們可以非常方便的進(jìn)行后臺(tái)線程和UI線程之間的交流。

那么AsyncTask是如何工作的哪。

AsyncTask擁有3個(gè)重要參數(shù)

  • 1、Params
  • 2、Progress
  • 3、Result

Params是后臺(tái)線程所需的參數(shù)。在后臺(tái)線程進(jìn)行作業(yè)的時(shí)候,他需要外界為其提供必要的參數(shù),就好像是一個(gè)用于下載圖片的后臺(tái)進(jìn)程,他需要的參數(shù)就是圖片的下載地址。

Progress是后臺(tái)線程處理作業(yè)的進(jìn)度。依舊上面的例子說(shuō),就是下載圖片這個(gè)任務(wù)完成了多少,是20%還是60%。這個(gè)數(shù)字是由Progress提供。

Result是后臺(tái)線程運(yùn)行的結(jié)果,也就是需要提交給UI線程的信息。按照上面的例子來(lái)說(shuō),就是下載完成的圖片。

AsyncTask還擁有4個(gè)重要的回調(diào)方法。

  • 1、onPreExecute
  • 2、doInBackground
  • 3、onProgressUpdate
  • 4、onPostExecute

onPreExecute運(yùn)行在UI線程,主要目的是為后臺(tái)線程的運(yùn)行做準(zhǔn)備。當(dāng)他運(yùn)行完成后,他會(huì)調(diào)用doInBackground方法。

doInBackground 運(yùn)行在后臺(tái)線程,他用來(lái)負(fù)責(zé)運(yùn)行任務(wù)。他擁有參數(shù)Params,并且返回Result。在后臺(tái)線程的運(yùn)行當(dāng)中,為了能夠更新作業(yè)完成的進(jìn)度,需要在 doInbackground方法中調(diào)用PublishProgress方法。該方法擁有參數(shù)Progress。通過(guò)該方法可以更新Progress的數(shù) 據(jù)。然后當(dāng)調(diào)用完P(guān)ublishProgress方法,他會(huì)調(diào)用onProgressUpdate方法用于更新進(jìn)度。

onProgressUpdate運(yùn)行在UI線程,主要目的是用來(lái)更新UI線程中顯示進(jìn)度的UI控件。他擁有Progress參數(shù)。在doInBackground中調(diào)用PublishProgress之后,就會(huì)自動(dòng)調(diào)onProgressUpdate方法

onPostExecute運(yùn)行在UI線程,當(dāng)doInBackground方法運(yùn)行完后,他會(huì)調(diào)用onPostExecute方法,并傳入Result。在onPostExecute方法中,就可以將Result更新到UI控件上。

明白了上面的3個(gè)參數(shù)和4個(gè)方法,你要做的就是

  • 1、編寫(xiě)一個(gè)繼承AsyncTask的類,并聲明3個(gè)參數(shù)的類型,編寫(xiě)4個(gè)回調(diào)方法的內(nèi)容。
  • 2、然后在UI線程中創(chuàng)建該類(必須在UI線程中創(chuàng)建)。
  • 3、最后調(diào)用AsyncTask的execute方法,傳入Parmas參數(shù)(同樣必須在UI線程中調(diào)用)。

這樣就大功告成了。

另外值得注意的2點(diǎn)就是,千萬(wàn)不要直接調(diào)用那四個(gè)回調(diào)方法。還有就是一個(gè)AsyncTask實(shí)例只能執(zhí)行一次,否則就出錯(cuò)哦。

以上是AsyncTask的基本用法,更加詳細(xì)的內(nèi)容請(qǐng)參考android官方文檔。最后附上一段代碼,供大家參考。

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> 
  //在這里聲明了Params、Progress、Result參數(shù)的類型
  {
      //因?yàn)檫@里不需要使用onPreExecute回調(diào)方法,所以就沒(méi)有加入該方法
      
      //后臺(tái)線程的目的是更具URL下載數(shù)據(jù)
       protected Long doInBackground(URL... urls) {
           int count = urls.length;//urls是數(shù)組,不止一個(gè)下載鏈接
           long totalSize = 0;//下載的數(shù)據(jù)
          for (int i = 0; i < count; i++) {
              //Download是用于下載的一個(gè)類,和AsyncTask無(wú)關(guān),大家可以忽略他的實(shí)現(xiàn)
              totalSize += Downloader.downloadFile(urls[i]);
              publishProgress((int) ((i / (float) count) * 100));//更新下載的進(jìn)度
              // Escape early if cancel() is called
              if (isCancelled()) break;
          }
          return totalSize;
      }
 
      //更新下載進(jìn)度
      protected void onProgressUpdate(Integer... progress) {
          setProgressPercent(progress[0]);
      }
 
      //將下載的數(shù)據(jù)更新到UI線程
      protected void onPostExecute(Long result) {
          showDialog("Downloaded " + result + " bytes");
      }
  }

有了上面的這個(gè)類,接下你要做的就是在UI線程中創(chuàng)建實(shí)例,并調(diào)用execute方法,傳入U(xiǎn)Rl參數(shù)就可以了。

這上面的5種方法各有優(yōu)點(diǎn)。但是究其根本,其實(shí)后面四種方法都是基于handler方法的包裝。在一般的情形下后面四種似乎更值得推薦。但是當(dāng)情形比較復(fù)雜,還是推薦使用handler。

最后補(bǔ)充一下,這是我的第一篇博客。存在很多問(wèn)題請(qǐng)大家多多指教。尤其是文中涉及到內(nèi)容,有嚴(yán)重的技術(shù)問(wèn)題,大家一定要給我指明啊。拜托各位了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 美圖欣賞 Java、Android知識(shí)點(diǎn)匯集 Java集合類 ** Java集合相關(guān)的博客** java面試相關(guān) ...
    ElvenShi閱讀 1,763評(píng)論 0 2
  • Activity是什么 Activity是四大組件之一,它提供一個(gè)界面讓用戶點(diǎn)擊和各種滑動(dòng)操作 Activity棧...
    叫我吹神閱讀 2,661評(píng)論 0 4
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,813評(píng)論 25 708
  • 我的畢業(yè)好像很冷清,沒(méi)有狂歡的晚會(huì),沒(méi)有隆重的畢業(yè)典禮,就連一寢室人最后的聚餐也是因?yàn)槌允裁炊鵂?zhēng)吵過(guò),大家六個(gè)人平...
    梨籽Laura閱讀 163評(píng)論 0 0
  • 你有多久沒(méi)做動(dòng)效了?平時(shí)的工作主要是終端視覺(jué)設(shè)計(jì),功能需求加運(yùn)營(yíng)需求,靜態(tài)稿件加動(dòng)效設(shè)計(jì),從比例來(lái)看,動(dòng)效設(shè)計(jì)其實(shí)...
    2888bf845e80閱讀 83,661評(píng)論 24 158