TShopping

 找回密碼
 註冊
搜索
查看: 178|回復: 0

[教學] Android中的線程相關類及用法

[複製鏈接]
發表於 2016-5-11 01:02:14 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk Push to Twitter 
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()
步驟就可以實現多線程操作了,然而一個類只能繼承一個父類,這是此種方法的的局限。下面看例子:
  1. class MyThread extends Thread{
  2.     private String name;
  3.     public MyThread(String name) {
  4.         super();
  5.         this.name = name;
  6.     }
  7.     public void run() {
  8.         for(int i=0;i<10;i++) {
  9.         System.out.println("線程開端:"+this.name+",i="+i);
  10.         }
  11.     }
  12. }
  13. public class ThreadDemo01 {
  14.         public static void main(String[] args) {
  15.                 MyThread mt1=new MyThread("線程a");
  16.                 MyThread mt2=new MyThread("線程b");
  17.                 mt1.start();
  18.                 mt2.start();
  19.         }
  20. }
複製代碼



2) Runnable接口

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


例子:
  1. class MyThread implements Runnable{
  2.         private String name;
  3.         public MyThread(String name) {
  4.                 this.name = name;
  5.         }
  6.         public void run(){
  7.                 for(int i=0;i<100;i++){
  8.                         System.out.println("線程開端:"+this.name+",i="+i);
  9.                 }
  10.         }
  11. };
複製代碼



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


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

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

    以賣票程序為例,通過Thread類實現:
  1. class MyThread extends Thread {
  2.         private int ticket=10;
  3.         public void run(){
  4.                 for(int i=0;i<20;i++) {
  5.                         if(this.ticket>0){
  6.                                 System.out.println("賣票:ticket"+this.ticket--);
  7.                         }
  8.                 }
  9.         }
  10. };
複製代碼

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


假如用Runnable就可以實現資源共享,下面看例子:
  1. class MyThread implements Runnable{
  2.         private int ticket=10;
  3.         public void run(){
  4.                 for(int i=0;i<20;i++){
  5.                         if(this.ticket>0){
  6.                                 System.out.println("賣票:ticket"+this.ticket--);
  7.                         }
  8.                 }
  9.         }
  10. }
  11. public class RunnableTicket {
  12. public static void main(String[] args) {
  13.         MyThread mt=new MyThread();
  14.         new Thread(mt).start();//同一個mt
  15.         new Thread(mt).start();
  16.         new Thread(mt).start();
  17. }
  18. };
複製代碼




現在程序中有三個線程,然而一共賣了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方法處理.
     以上的述說太麻煩,下面看一下源代碼中的實現吧, 加深一下印象:
  1. public void dispatchMessage(Message msg) {
  2.         if (msg.callback != null) {
  3.             handleCallback(msg);
  4.         } else {
  5.             if (mCallback != null) {
  6.                 if (mCallback.handleMessage(msg)) {
  7.                     return;
  8.                 }
  9.             }
  10.             handleMessage(msg);
  11.         }
  12. }
複製代碼

7) Handler的用法例子

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


a. 在onCreate中創建Handler
  1. public class HandlerTestApp extends Activity {
  2.        Handler mHandler;
  3.        TextView mText;
  4.       @Override
  5.       public void onCreate(Bundle savedInstanceState) {
  6.           super.onCreate(savedInstanceState);
  7.           setContentView(R.layout.main);
  8.           mHandler = new Handler();//創建Handler
  9.           mText = (TextView) findViewById(R.id.text0);//一個TextView
  10.       }
複製代碼

      

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

  1. Runnable mRunnable0 = new Runnable()
  2.    {
  3.                @Override
  4.                public void run() {
  5.                        // TODO Auto-generated method stub
  6.                        mText.setText("This is Update from ohter thread, Mouse DOWN");
  7.                }
  8.    };
複製代碼

               
                       



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



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

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

   a.實現自己的Handler,對消息進行處理
   
  1. private class MyHandler extends Handler
  2.    {
  3.        @Override
  4.        public void handleMessage(Message msg) {
  5.            // TODO Auto-generated method stub
  6.            super.handleMessage(msg);
  7.            switch(msg.what)
  8.            {
  9.            case UPDATE://在收到消息時,對界面進行更新   
  10.                mText.setText("This update by message");
  11.                break;
  12.            }
  13.        }
  14.    }
複製代碼


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

  1. private void updateByMessage()
  2.    {
  3.       //匿名對象
  4.         new Thread()
  5.         {
  6.                public void run()
  7.                {
  8.                    //mText.setText("This is Update from ohter thread, Mouse DOWN");
  9.                    //UPDATE是一個自己定義的整數,代表了消息ID
  10.                    Message msg = mHandler.obtainMessage(UPDATE);
  11.                    mHandler.sendMessage(msg);
  12.                }
  13.         }.start();
  14.    }
複製代碼
     

8) HandlerThread

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

  2.                 public TestHandlerThread(String name) {
  3.                         super(name);
  4.                         // TODO Auto-generated constructor stub
  5.                 }

  6.                 @Override
  7.                 public void run() {
  8.                         // TODO Auto-generated method stub
  9.                         super.run();
  10.                         
  11.                         Message msg = mTestHandler1.obtainMessage();              
  12.                         Bundle b = new Bundle();
  13.                         b.putString("color", "red");
  14.                         //給handler發送的數據,放入message中
  15.                         msg.setData(b);
  16.                         msg.what = MESSAGE_ID;
  17.                         
  18.                         Looper looper = this.getLooper();
  19.                         new Handler(looper){

  20.                                 @Override
  21.                                 public void handleMessage(Message msg) {
  22.                                         // TODO Auto-generated method stub
  23.                                         super.handleMessage(msg);
  24.                                         Log.d("HandlerDemo", "TestHandlerThread---------");
  25.                                 }
  26.                                 
  27.                         }.sendMessage(msg);
  28.                 }
  29.         }
  30.         


  31. // (3)new HandlerThread
  32. TestHandlerThread testHandlerThread = new TestHandlerThread("testHandlerThread");
  33. testHandlerThread.start();
複製代碼

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

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

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


文章來源:http://zzqhost.com/?post=57

 

臉書網友討論
您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2016-12-12 00:11 , Processed in 0.057386 second(s), 22 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表