woff 發表於 2016-5-11 01:02:14

Android中的線程相關類及用法

1. 本文的講述內容和範圍
    本文講述的是Android中線程及創建線程的方法,涉及到的內容包括: Runable, Thread, Handler, Looper, HandlerThread, AsyncTask。將要詳細講述以下幾個方面的內容:
    2) Runable和Thread的關係
    3) Handler, Thread, Looper, HandlerThread之間的關係
    4) Runable的一點說明
    5) Android中的便利類(AsyncTask等)

2. Runable和Thread的關係
在java中可有兩種方法實現多線程,一種是繼承Thread類,一種是實現Runnable接口;
   1)繼承Thread類的方式

Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的run()
步驟就可以實現多線程操作了,然而一個類只能繼承一個父類,這是此種方法的的局限。下面看例子:
class MyThread extends Thread{
    private String name;
    public MyThread(String name) {
      super();
      this.name = name;
    }
    public void run() {
      for(int i=0;i<10;i++) {
      System.out.println("線程開端:"+this.name+",i="+i);
      }
    }
}
public class ThreadDemo01 {
      public static void main(String[] args) {
                MyThread mt1=new MyThread("線程a");
                MyThread mt2=new MyThread("線程b");
                mt1.start();
                mt2.start();
      }
}


2) Runnable接口

在實際開闢中一個多線程的操作很少使用Thread類,而是通過Runnable接口實現。<div>public interface Runnable{</div><div>   public void run(); </div><div>}</div>

例子:
class MyThread implements Runnable{
      private String name;
      public MyThread(String name) {
                this.name = name;
      }
      public void run(){
                for(int i=0;i<100;i++){
                        System.out.println("線程開端:"+this.name+",i="+i);
                }
      }
};


然而在Runnable的子類中沒有start() 方法,只有Thread類中才有。此時視察Thread類,有一個構造函數:public Thread(Runnable targer) 此構造函數接受Runnable的子類實例,也就是說可以通過Thread類來啟動Runnable實現多線程。(start() 可以協調系統的資源):
public class ThreadDemo01 {
      public static void main(String[] args) {
                MyThread mt1=new MyThread("線程a");
                MyThread mt2=new MyThread("線程b");
                new Thread(mt1).start();
                new Thread(mt2).start();
      }
}

3)實際中如何應用這兩種實現模式

    在程序實現多線程應優先以實現Runnable接口為主,由於實現Runnable接口相比繼承Thread類有如下好處:
• 避免點繼承的局限,一個類可以繼承多個接口。
• 利於資源的共享。

    以賣票程序為例,通過Thread類實現:
class MyThread extends Thread {
      private int ticket=10;
      public void run(){
                for(int i=0;i<20;i++) {
                        if(this.ticket>0){
                              System.out.println("賣票:ticket"+this.ticket--);
                        }
                }
      }
};

下面通過三個線程對象,同時賣票:
public class ThreadTicket {
      public static void main(String[] args) {
                MyThread mt1=new MyThread();
                MyThread mt2=new MyThread();
                MyThread mt3=new MyThread();
                mt1.start();//每個線程都各賣了10張,共賣了30張票
                mt2.start(); //但實際只有10張票,每個線程都賣自己的票
                mt3.start();//沒有達到資源共享
      }
}

假如用Runnable就可以實現資源共享,下面看例子:
class MyThread implements Runnable{
      private int ticket=10;
      public void run(){
                for(int i=0;i<20;i++){
                        if(this.ticket>0){
                              System.out.println("賣票:ticket"+this.ticket--);
                        }
                }
      }
}
public class RunnableTicket {
public static void main(String[] args) {
      MyThread mt=new MyThread();
      new Thread(mt).start();//同一個mt
      new Thread(mt).start();
      new Thread(mt).start();
}
};




現在程序中有三個線程,然而一共賣了10張票,也就是說使用Runnable實現多線程可以達到資源共享的目的。

4)Runnable接口和Thread的關係總結

      (1)說白了就是類和接口的區別。Thread是一個類,java中是不允許繼承多個父類的,這就是Thread的一個局限性。而使用Runnable就不同了,可以implements多個接口,同時繼承一個父類,這樣會更加靈活。
      (2)當多個線程需要共享資源時,用Thread很難達到目的,但是用Runnable接口就容易許多了。
      (3)二者的聯繫:看源碼可以發現,Thread其實就是繼承了Runnable接口的子類。

3. Handler, Thread, Looper之間的關係
1)概述

      在Android中,線程分為有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個是android的新概念。我們的主線程(UI線程)就是一個有消息循環的線程。
      針對這種消息循環的機制,我們引入一個新的機制--Handler,
      我們有消息循環,就要往消息循環裡面發送相應的消息,自定義消息一般都會有自己對應的處理,消息的發送和清除、消息的的處理,把這些都封裝在Handler裡面,注意Handler只是針對那些有Looper的線程,不管是UI線程還是子線程,只要你有Looper,我就可以往你的消息隊列裡面添加東西,並做相應的處理。
      一個線程對應一個或者零個Looper和MessageQueue。
   
2)各自職責

      (1) Message:消息,其中包含了消息ID,消息處理對像以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。
      (2) MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message以鍊錶的方式串聯起來的,等待Looper的抽取。
      (3) Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。
      (4) Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。
      (5) Thread:線程,負責調度整個消息循環,即消息循環的執行場所。

      (6) Looper.myLooper()獲得新線程的Looper,Looper.getMainLooper()是獲得主線程的Looper
      (7)通過new MyHandler(Looper)有參構造函數來讓Looper和Handler進行溝通
            無參的構造函數,默認獲取的是當前線程的Looper
            Message message = mHandler.obtainMessage(1, 1, 1, msg);
            mHandler.sendMessage(message); //發送消息
            定義一個類繼承自Handler,改寫方法handleMessage(Message msg)接收並處理消息
3) Handler

Handler在android裡負責發送和處理消息。它的主要用途:
  1)按計劃發送消息或執行某個Runnanble(使用POST方法),類似定時器;
  2)從其他線程中發送來的消息放入消息隊列中,避免線程衝突(常見於更新UI線程);
   默認情況下,Handler接受的是當前線程下的消息循環實例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定線程),同時一個消息隊列(MessageQueue和Looper封裝)可以被當前線程中的多個對象進行分發、處理(在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理)。
      在實例化Handler的時候,Looper可以是任意線程的,只要有Handler的指針,任何線程也都可以sendMessage。
      Handler對於Message的處理不是並發的。一個Looper 只有處理完一條Message才會讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法裡不應該有耗時操作,可以將耗時操作放在其他線程執行,操作完後發送Message(通過sendMessges方法),然後由handleMessage()更新UI)。

4)Looper

      Looper類用來創建消息隊列. 每個線程最多只能有一個消息隊列, android中UI線程默認具有消息隊列, 但非UI線程在默認情況下是不具備消息隊列的. 如果需要在非UI線程中開啟消息隊列, 需要調用Looper.prepare()方法, 在該方法的執行過程中會創建一個Looper對象, 而Looper的構造函數中會創建一個MessageQueue instance(Looper的構造函數是私有的, 在Looper類之外無法創建其對象). 此後再為該線程綁定一個Handler instance, 然後調用Looper.loop()方法, 就可以不斷的從消息隊列中取出消息和處理消息了. Looper.myLoop()方法可以得到線程的Looper對象, 如果為null, 說明此時該線程尚未開啟消息隊列.
      創建一個Looper對象時,會同時創建一個MessageQueue對象(一個looper對應一個MessageQueue)。除了 主線程有默認的Looper ,其他線程默認是沒有MessageQueue對象的,所以,不能接受Message 。如需要接受,自己定義一個Looper對象(通過prepare函數),這樣該線程就有了自己的Looper對象和MessageQueue數據結構了。
      Looper從MessageQueue中取出Message然後,交由Handler的handleMessage進行處理。處理完成後,調用Message.recycle()將其放入Message Pool中。

5) Message

      Message :消息對象, Message Queue 中的存放的對象。一個Message Queue 中包含多個Message 。
   Message實例對象的取得,通常使用Message類裡的靜態方法obtain(),該方法有多個重載版本可供選擇;它的創建並不一定是直接創建一個新的實例,而是先從Message Pool (消息池)中看有沒有可用的Message實例,存在則直接取出返回這個實例。如果Message Pool中沒有可用的Message實例,則才用給定的參數創建一個Message對象。調用removeMessages()時,將Message從Message Queue中刪除,同時放入到Message Pool中。除了 上面這種方式,也可以通過Handler對象的obtainMessage()獲取一個Message實例。
      Message類用於表示消息. Message對象可以通過arg1, arg2, obj字段和setData()攜帶數據,此外還具有很多字段.
when字段決定Message應該何時出對處理,;
target字段用來表示將由哪個Handler對象處理這個消息;
next字段表示在消息隊列中排在這個Message之後的下一個Message;
callback字段如果不為null表示這個Message包裝了一個runnable對象;
what字段表示code, 即這個消息具體是什麼類型的消息. 每個what都在其handler的namespace中, 我們只需要確保將由同一個handler處理的消息的what屬性不重複就可以.

6) MessageQueue

      MessageQueue類用於表示消息隊列. 隊列中的每一個Message都有一個when字段, 這個字段用來決定Message應該何時出隊處理. 消息隊列中的每一個Message根據when字段的大小由小到大排列, 排在最前面的消息會首先得到處理, 因此可以說消息隊列並不是一個嚴格的先進先出的隊列。主線程創建時,會創建一個默認的Looper 對象,而Looper 對象的創建,將自動創建一個Message Queue 。其他非主線程,不會自動創建Looper ,要需要的時候,通過調用prepare 函數來實現。
將消息壓入消息隊列

      Message對象的target字段關聯了哪個線程的消息隊列, 這個消息就會被壓入哪個線程的消息隊列中.
      a 調用Handler類中以send開頭的方法可以將Message對象壓入消息隊列中;
      b 調用Handler類中以post開頭的方法可以將一個runnable對象包裝在一個Message對像中, 然後再壓入消息隊列, 此時入隊的Message其callback字段不為null, 值就是這個runnable對象. 調用Handler對象的這些方法入隊的Message, 其target屬性會被賦值為這個handler對象.
      c 調用Message對象的sendToTarget()方法可以將其本身壓入與其target字段(即handler對象)所關聯的消息隊列中.
從消息隊列中取出消息並處理消息

      所有在消息隊列中的消息, 都具有target字段. 消息是在target所關聯的線程上被取出和處理的.
      1. 如果取出的Message對象的callback字段不為null, 那麼就調用callback字段的run()方法(callback字段的類型是runnable). 注意此時並不開啟一個新的線程運行run()方法, 而是直接在handler對象(即Message的target字段)所關聯的線程上運行.
      2. 如果取出的Message對象的callback字段為null, 且Handler對像中的callback字段也為null, 那麼這個消息將由Handler對象的handleMessage(msg)方法處理. 注意Message對象的callback字段是Runnable類型的而Handler對象的callback字段是Callback類型的, Handler對象的callback字段是在創建Handler instance的時候指定的, 如果沒有指定則這個字段為null, 詳見Handler類的四個構造方法.
      3. 如果取出的Message對象的callback字段為null, 且Handler對像中的callback字段不為null, 那麼這個消息將由Handler對像中的callback字段的handleMessage方法處理.
   以上的述說太麻煩,下面看一下源代碼中的實現吧, 加深一下印象:
public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
            handleCallback(msg);
      } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                  return;
                }
            }
            handleMessage(msg);
      }
}
7) Handler的用法例子

有了以上的敘述, 線程間的通信也就好理解了. 假如一個handler關聯了A線程上的消息隊列, 那麼我們可以在B線程上調用handler的相關方法向A線程上的消息隊列壓入一個Message, 這個Message將在A線程上得到處理. 下面列舉幾個常用的例子:
通過Runnable在子線程中更新界面的例子


a. 在onCreate中創建Handler
public class HandlerTestApp extends Activity {
       Handler mHandler;
       TextView mText;
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.main);
          mHandler = new Handler();//創建Handler
          mText = (TextView) findViewById(R.id.text0);//一個TextView
      }
      

b. 構建Runnable對象,在runnable中更新界面,此處,我們修改了TextView的文字.此處需要說明的是,Runnable對象可以再主線程中創建,也可以再子線程中創建。我們此處是在子線程中創建的。

Runnable mRunnable0 = new Runnable()
   {
               @Override
               public void run() {
                     // TODO Auto-generated method stub
                     mText.setText("This is Update from ohter thread, Mouse DOWN");
               }
   };
               
                     



c. 創建子線程,在線程的run函數中,我們向主線程的消息隊列發送了一個runnable來更新界面。
private void updateUIByRunnable(){
         new Thread()
      {
            //Message msg = mHandler.obtainMessage();
             public void run()
            {
                  //mText.setText("This is Update from ohter thread, Mouse DOWN");/這句將拋出異常
                  mHandler.post(mRunnable0);
            }
      }.start();
    }


用Message在子線程中來更新界面

用Message更新界面與Runnable更新界麵類似,只是需要修改幾個地方。

   a.實現自己的Handler,對消息進行處理
   
private class MyHandler extends Handler
   {
       @Override
       public void handleMessage(Message msg) {
         // TODO Auto-generated method stub
         super.handleMessage(msg);
         switch(msg.what)
         {
         case UPDATE://在收到消息時,對界面進行更新   
               mText.setText("This update by message");
               break;
         }
       }
   }

b. 在新的線程中發送消息   

private void updateByMessage()
   {
      //匿名對象
      new Thread()
      {
               public void run()
               {
                   //mText.setText("This is Update from ohter thread, Mouse DOWN");
                   //UPDATE是一個自己定義的整數,代表了消息ID
                   Message msg = mHandler.obtainMessage(UPDATE);
                   mHandler.sendMessage(msg);
               }
      }.start();
   }      

8) HandlerThread

      上一節講到,UI線程都是帶Looper的線程,可以進行消息循環,而自己新建的子線程或線程都是沒有Looper的,不能接收消息進行處理,如果想要往自己的線程中發送消息或post一個runable,那麼必須在自己的線程中去調用Looper.prepare()。
      如果自己要實現Thread並手工建立Loop的話,則需要注意線程的同步等問題。具體參考《深入理解Android 卷一》
      但是,哈哈,Android是一個優秀易用的系統,對於這樣的問題,它早就想到了,並提供了解決方案,那就是HandlerThread。大家直接用它就行。下面給一個例子:
private class TestHandlerThread extends HandlerThread{// 注5

                public TestHandlerThread(String name) {
                        super(name);
                        // TODO Auto-generated constructor stub
                }

                @Override
                public void run() {
                        // TODO Auto-generated method stub
                        super.run();
                        
                        Message msg = mTestHandler1.obtainMessage();            
                        Bundle b = new Bundle();
                        b.putString("color", "red");
                        //給handler發送的數據,放入message中
                        msg.setData(b);
                        msg.what = MESSAGE_ID;
                        
                        Looper looper = this.getLooper();
                        new Handler(looper){

                              @Override
                              public void handleMessage(Message msg) {
                                        // TODO Auto-generated method stub
                                        super.handleMessage(msg);
                                        Log.d("HandlerDemo", "TestHandlerThread---------");
                              }
                              
                        }.sendMessage(msg);
                }
      }
      


// (3)new HandlerThread
TestHandlerThread testHandlerThread = new TestHandlerThread("testHandlerThread");
testHandlerThread.start();

上面的例子,是在自己的線程中new Handler並給自己發消息, 當然你也可以在主線程或其它線程中創建Handler, 給它發消息,讓它進行處理。

4. Runable的一點說明
    大家如果能看到這裡,那恭喜你,來點輕鬆的,哈哈。
    Runable在上面總共說到了兩種用途。還記得嗎,請看:
      1) 實現一個Runable接口的子類,把它當實例傳給Thread的構造函數,來創建一個新線程。
      2) 實現一個Runable接口的子類,用Handler把它post到相關聯的線程中。

不用疑惑,上面的兩個用法沒有一點點關係,把它當成兩類來看就是了。原因就是Runable是一個接口,只定義了一個run()方法,具體用法可以多樣化,呵呵~~


文章來源:http://zzqhost.com/?post=57
頁: [1]
查看完整版本: Android中的線程相關類及用法