Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android多線程----異步消息處理機制之Handler

Android多線程----異步消息處理機制之Handler

編輯:關於Android編程

【正文】   雖然是國慶佳節,但也不能停止學習的腳步,我選擇在教研室為祖國母親默默地慶生。   關於Android的多線程知識,請參考本人之前的一篇博客:Android 多線程----AsyncTask異步任務詳解   在Android當中,提供了異步消息處理機制的兩種方式來解決線程之間的通信問題,一種是今天要講的Handler的機制,還有一種就是之前講過的 AsyncTask 機制。   一、handler的引入:   我們都知道,Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程序就有可能會崩潰。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟於心,即創建一個Message對象,然後借助Handler發送出去,之後在Handler的handleMessage()方法中獲得剛才發送的Message對象,然後在這裡進行UI操作就不會再出現崩潰了。具體實現代碼如下:   復制代碼  1 package com.example.androidthreadtest;  2   3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.os.Handler;  6 import android.os.Message;  7 import android.view.View;  8 import android.view.View.OnClickListener;  9 import android.widget.Button; 10 import android.widget.TextView; 11  12 public class MainActivity extends Activity implements OnClickListener { 13  14     public static final int UPDATE_TEXT = 1; 15     private TextView text; 16     private Button changeText; 17     private Handler handler = new Handler() { 18         public void handleMessage(Message msg) { 19             switch (msg.what) { 20             case UPDATE_TEXT: 21                 text.setText("Nice to meet you"); 22                 break; 23             default: 24                 break; 25             } 26         } 27     }; 28  29     @Override 30     protected void onCreate(Bundle savedInstanceState) { 31         super.onCreate(savedInstanceState); 32         setContentView(R.layout.activity_main); 33         text = (TextView) findViewById(R.id.text); 34         changeText = (Button) findViewById(R.id.change_text); 35         changeText.setOnClickListener(this); 36     } 37  38     @Override 39     public void onClick(View v) { 40         switch (v.getId()) { 41         case R.id.change_text: 42             new Thread(new Runnable() { 43                 @Override 44                 public void run() { 45                     Message message = new Message(); 46                     message.what = UPDATE_TEXT; 47                     handler.sendMessage(message); 48                 } 49             }).start(); 50             break; 51         default: 52             break; 53         } 54     } 55 } 復制代碼 上方第45行代碼也可以換成:   Message msg = handler.obtainMessage();  上面的代碼中,我們並沒有在子線程中直接進行UI操作,而是創建了一個Message對象,並將它的what字段的值指定為了一個整形常量UPDATE_TEXT,用於表示更新TextView這個動作。然後調用Handler的sendMessage()方法將這條Message發送出去。很快,Handler就會收到這條Message,並在handleMessage()方法,在這裡對具體的Message進行處理(需要注意的是,此時handleMessage()方法中的代碼是在主線程中運行的)。如果發現Message的what字段的值等於UPDATE_TEXT,就將TextView顯示的內容更新。運行程序後,點擊按鈕,TextView就會顯示出更新的內容。       二、異步消息處理機制:   Handler是Android類庫提供的用於接受、傳遞和處理消息或Runnable對象的處理類,它結合Message、MessageQueue和Looper類以及當前線程實現了一個消息循環機制,用於實現任務的異步加載和處理。整個異步消息處理流程的示意圖如下圖所示:       根據上面的圖片,我們現在來解析一下異步消息處理機制:   Message:消息體,用於裝載需要發送的對象。 handler:它直接繼承自Object。作用是:在子線程中發送Message或者Runnable對象到MessageQueue中;在UI線程中接收、處理從MessageQueue分發出來的Message或者Runnable對象。發送消息一般使用Handler的sendMessage()方法,而發出去的消息經過處理後最終會傳遞到Handler的handlerMessage()方法中。 MessageQueue:用於存放Message或Runnable對象的消息隊列。它由對應的Looper對象創建,並由Looper對象管理。每個線程中都只會有一個MessageQueue對象。 Looper:是每個線程中的MessageQueue的管家,循環不斷地管理MessageQueue接收和分發Message或Runnable的工作。調用Looper的loop()方法後,就會進入到一個無限循環中然後每當發現MessageQueue中存在一條消息,就會將它取出,並調用Handler的handlerMessage()方法。每個線程中也只會有一個Looper對象。 了解這些之後,我們在來看一下他們之間的聯系:   首先要明白的是,Handler和Looper對象是屬於線程內部的數據,不過也提供與外部線程的訪問接口,Handler就是公開給外部線程的接口,用於線程間的通信。Looper是由系統支持的用於創建和管理MessageQueue的依附於一個線程的循環處理對象,而Handler是用於操作線程內部的消息隊列的,所以Handler也必須依附一個線程,而且只能是一個線程。   我們再來對異步消息處理的整個流程梳理一遍:   當應用程序開啟時,系統會自動為UI線程創建一個MessageQueue(消息隊列)和Looper循環處理對象。首先需要在主線程中創建一個Handler對象,並重寫handlerMessage()方法。然後當子線程中需要進行UI操作時,就創建一個Message對象,並通過Handler將這條消息發送出去。之後這條消息就會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,並找到與消息對象對應的Handler對象,然後調用Handler的handleMessage()方法。由於Handler是在主線程中創建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,於是我們在這裡就可以安心地進行UI操作了。   通俗地來講,一般我們在實際的開發過程中用的比較多一種情況的就是主線程的Handler將子線程中處理過的耗時操作的結果封裝成Message(消息),並將該Message(利用主線程裡的MessageQueue和Looper)傳遞到主線程中,最後主線程再根據傳遞過來的結果進行相關的UI元素的更新,從而實現任務的異步加載和處理,並達到線程間的通信。   通過上一小節對Handler的一個初步認識後,我們可以很容易總結出Handler的主要用途,下面是Android官網總結的關於Handler類的兩個主要用途:   (1)線程間的通信:   在執行較為耗時的操作時,Handler負責將子線程中執行的操作的結果傳遞到UI線程,然後UI線程再根據傳遞過來的結果進行相關UI元素的更新。(上面已有說明)   (2)執行定時任務:   指定任務時間,在某個具體時間或某個時間段後執行特定的任務操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久後執行某項操作,比如當當、淘寶、京東和微信等手機客戶端的開啟界面功能,都是通過Handler定時任務來完成的。   我們接下來講一下post。       三、post:   對於Handler的Post方式來說,它會傳遞一個Runnable對象到消息隊列中,在這個Runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在UI線程上的操作。   Post允許把一個Runnable對象入隊到消息隊列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。詳細解釋如下:   boolean post(Runnable r):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,立即執行。 boolean postAtTime(Runnable r,long uptimeMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,在特定的時間執行。 boolean postDelayed(Runnable r,long delayMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,延遲delayMills秒執行 void removeCallbacks(Runnable r):從消息隊列中移除一個Runnable對象。 下面通過一個Demo,講解如何通過Handler的post方式在新啟動的線程中修改UI組件的屬性:   復制代碼  1 package com.example.m03_threadtest01;  2   3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.os.Handler;  6 import android.view.View;  7 import android.widget.Button;  8 import android.widget.TextView;  9  10 public class MainActivity extends Activity { 11     private Button btnMes1,btnMes2; 12     private TextView tvMessage; 13     // 聲明一個Handler對象 14     private static Handler handler=new Handler(); 15      16     @Override 17     protected void onCreate(Bundle savedInstanceState) { 18         super.onCreate(savedInstanceState); 19         setContentView(R.layout.activity_main);         20          21         btnMes1=(Button)findViewById(R.id.button1); 22         btnMes2=(Button)findViewById(R.id.button2); 23         tvMessage=(TextView)findViewById(R.id.TextView1); 24         btnMes1.setOnClickListener(new View.OnClickListener() { 25              26             @Override 27             public void onClick(View v) { 28                 // 新啟動一個子線程 29                 new Thread(new Runnable() {                     30                     @Override 31                     public void run() { 32                         // tvMessage.setText("..."); 33                         // 以上操作會報錯,無法再子線程中訪問UI組件,UI組件的屬性必須在UI線程中訪問 34                         // 使用post方式修改UI組件tvMessage的Text屬性 35                         handler.post(new Runnable() {                     36                             @Override 37                             public void run() { 38                                 tvMessage.setText("使用Handler.post在工作線程中發送一段執行到消息隊列中,在主線程中執行。");                         39                             } 40                         });                                 41                     } 42                 }).start(); 43             } 44         }); 45          46         btnMes2.setOnClickListener(new View.OnClickListener() { 47              48             @Override 49             public void onClick(View v) { 50                 new Thread(new Runnable() {                     51                     @Override 52                     public void run() { 53                         // 使用postDelayed方式修改UI組件tvMessage的Text屬性值 54                         // 並且延遲3S執行 55                         handler.postDelayed(new Runnable() { 56                              57                             @Override 58                             public void run() { 59                                 tvMessage.setText("使用Handler.postDelayed在工作線程中發送一段執行到消息隊列中,在主線程中延遲3S執行。");     60                                  61                             } 62                         }, 3000);                         63                     } 64                 }).start(); 65                  66             } 67         }); 68     } 69      70 } 復制代碼 點擊按鈕,運行結果如下:       有一點值得注意的是,對於Post方式而言,它其中Runnable對象的run()方法的代碼,均執行在UI線程上(雖然是寫在子線程當中的),所以對於這段代碼而言,不能執行在UI線程上的操作,一樣無法使用post方式執行,比如說訪問網絡。        四、Message:   Handler如果使用sendMessage的方式把消息入隊到消息隊列中,需要傳遞一個Message對象,而在Handler中,需要重寫handleMessage()方法,用於獲取工作線程傳遞過來的消息,此方法運行在UI線程上。   對於Message對象,一般並不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()這個靜態的方法或者Handler.obtainMessage()獲取。Message.obtain()會從消息池中獲取一個Message對象,如果消息池中是空的,才會使用構造方法實例化一個新Message,這樣有利於消息資源的利用。並不需要擔心消息池中的消息過多,它是有上限的,上限為10個。Handler.obtainMessage()具有多個重載方法,如果查看源碼,會發現其實Handler.obtainMessage()在內部也是調用的Message.obtain()。   Handler中,與Message發送消息相關的方法有:   Message obtainMessage():獲取一個Message對象。 boolean sendMessage():發送一個Message對象到消息隊列中,並在UI線程取到消息後,立即執行。 boolean sendMessageDelayed():發送一個Message對象到消息隊列中,在UI線程取到消息後,延遲執行。 boolean sendEmptyMessage(int what):發送一個空的Message對象到隊列中,並在UI線程取到消息後,立即執行。 boolean sendEmptyMessageDelayed(int what,long delayMillis):發送一個空Message對象到消息隊列中,在UI線程取到消息後,延遲執行。 void removeMessage():從消息隊列中移除一個未響應的消息。     五、通過Handler實現線程間通信:   1、在Worker Thread發送消息,在MainThread中接收消息:   【實例】點擊反扭,將下方的TextView的內容修改為“從網絡中獲取的數據”       【實際意義】點擊按鈕時,程序訪問服務器,服務器接到請求之後,會返回字符串結果,然後更新到程序。 完整版代碼如下: XML布局文件代碼如下: 復制代碼 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context=".MainActivity" >       <TextView       android:id="@+id/TextViewId"       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:text="數據" />          <Button         android:id="@+id/ButtonId"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:text="發送消息"          android:layout_below="@id/TextViewId"/>   </RelativeLayout>  復制代碼 MainActivity.java代碼如下:   復制代碼  1 package com.example.test0207_handler;  2   3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.os.Handler;  6 import android.os.Message;  7 import android.view.Menu;  8 import android.view.View;  9 import android.view.View.OnClickListener; 10 import android.widget.Button; 11 import android.widget.TextView; 12  13 public class MainActivity extends Activity { 14  15     private TextView textView ;  16     private Button button ; 17     private Handler handler ; 18     @Override 19     protected void onCreate(Bundle savedInstanceState) { 20         super.onCreate(savedInstanceState); 21         setContentView(R.layout.activity_main); 22          23         textView = (TextView)findViewById(R.id.TextViewId) ; 24         button = (Button)findViewById(R.id.ButtonId) ;     25          26         handler = new MyHandler() ; 27          28         button.setOnClickListener(new ButtonListener()) ; 29          30     } 31     //在MainAthread線程中接收數據,從而修改TextView的值 32     class MyHandler extends Handler { 33         @Override 34         public void handleMessage(Message msg) { 35             System.out.println("handleMessage--->"+Thread.currentThread().getName()) ;//得到當前線程的名字 36             String s = (String)msg.obj ; 37             textView.setText(s) ; 38         } 39          40     } 41     //生成線程對象,讓NetworkThread線程啟動 42     class ButtonListener implements OnClickListener { 43         @Override         44         public void onClick(View arg0) { 45             Thread t = new NetworkThread() ; 46             t.start(); 47         } 48          49     } 50      51     //在Worker Thread線程中發送數據 52     class NetworkThread extends Thread { 53         @Override  54         public void run(){ 55              56             System.out.println("network--->"+Thread.currentThread().getName()) ;//得到當前線程的名字 57              58             //模擬訪問網絡:當線程運行時,首先休眠2秒鐘 59             try { 60                 Thread.sleep(2*1000) ; 61             } catch (InterruptedException e) { 62                 e.printStackTrace(); 63             } 64             //變量s的值,模擬從網絡當中獲取的數據 65             String s = "從網絡中獲取的數據" ; 66             //textView.setText(s) ; //這種做法是錯誤的,只有在Mainthread中才能操作UI             67              68             //開始發送消息 69             Message msg = handler.obtainMessage() ;     70             msg.obj = s ; 71             handler.sendMessage(msg) ;//sendMessage()方法,在主線程或者Worker Thread線程中發送,都是可以的,都可以被取到 72         } 73     }     74      75     @Override 76     public boolean onCreateOptionsMenu(Menu menu) { 77         // Inflate the menu; this adds items to the action bar if it is present. 78         getMenuInflater().inflate(R.menu.main, menu); 79         return true; 80     } 81      82 } 復制代碼  這段代碼的結構,和最上面的第一章節是一樣的。   上方代碼中,我們在子線程中休眠2秒來模擬訪問網絡的操作。   65行:用字符串s表示從網絡中獲取的數據;70行:然後我們把這個字符串放在Message的obj屬性當中發送出去,並在主線程中接收(36行)。   運行後結果如下:       點擊按鈕後結果如下:     點擊按鈕後,後台輸出結果如下:       可以看到,子線程的名字是:Thread-1118,主線程的名字是:main。       2、在MainThread中發送消息,在Worker Thread中接收消息:   【實例】點擊按鈕,在在MainThread中發送消息,在Worker Thread中接收消息,並在後台打印輸出。   【代碼】完整版代碼如下: XML布局文件代碼如下: 復制代碼 <RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context=".MainActivity" >       <Button         android:id="@+id/ButtonId"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:text="在主線程中發送消息" />   </RelativeLayout>  復制代碼 MainActivity.java代碼如下: 復制代碼  1 package com.example.m03_handle01;  2   3 import android.app.Activity;  4 import android.os.Bundle;  5 import android.os.Handler;  6 import android.os.Looper;  7 import android.os.Message;  8 import android.util.Log;  9 import android.view.Menu; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 public class MainActivity extends Activity { 14     private Button button ; 15     private Handler handler ; 16     @Override 17     protected void onCreate(Bundle savedInstanceState) { 18         super.onCreate(savedInstanceState); 19         setContentView(R.layout.activity_main); 20          21         button = (Button)findViewById(R.id.ButtonId) ; 22          23         //當用戶點擊按鈕時,發送Message的對象msg 24         button.setOnClickListener(new OnClickListener() {  //使用匿名內部類為button綁定監聽器 25              26             @Override 27             public void onClick(View v) { 28                 Log.i("onClick:", Thread.currentThread().getName()); 29                 Message msg = handler.obtainMessage() ; 30                 handler.sendMessage(msg) ; 31             }             32         }) ; 33          34         WorkerThread wt = new WorkerThread() ; 35         wt.start() ; 36     } 37  38     //在WorkerThread生成handler 39     class WorkerThread extends  Thread { 40         @Override 41         public void run() { 42             //准備Looper對象 43             Looper.prepare() ; 44             //在WorkerThread當中生成一個Handler對象 45             handler = new Handler() { 46                 @Override 47                 public void handleMessage(Message msg) { 48                     Log.i("handleMessage:", Thread.currentThread().getName()); 49                     Log.i("後台輸出", "收到了消息對象"); 50                 } 51             }; 52             //調用Looper的loop()方法之後,Looper對象將不斷地從消息隊列當中取出對象,然後調用handler的handleMessage()方法,處理該消息對象 53             //如果消息隊列中沒有對象,則該線程阻塞 54             Looper.loop() ;   //通過Looper對象將消息取出來 55         } 56          57     } 58      59      60     @Override 61     public boolean onCreateOptionsMenu(Menu menu) { 62         // Inflate the menu; this adds items to the action bar if it is present. 63         getMenuInflater().inflate(R.menu.main, menu); 64         return true; 65     } 66      67 }  復制代碼 上方的第42行至54行代碼:這是MainThread中發送消息,在Worker Thread中接收消息的固定寫法。上面的三個步驟再重復一下: 准備Looper對象 在WorkerThread當中生成一個Handler對象 調用Looper的loop()方法之後,Looper對象將不斷地從消息隊列當中取出對象,然後調用handler的handleMessage()方法,處理該消息對象;如果消息隊列中沒有對象,則該線程阻塞 注意,此時handleMessage()方法是在Worker Thread中運行的。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved