【聲明】 html
歡迎轉載,但請保留文章原始出處→_→ java
生命壹號:http://www.cnblogs.com/smyhvae/android
文章來源:http://www.cnblogs.com/smyhvae/p/4003922.html安全
聯繫方式:smyhvae@163.com 服務器
【正文】微信
雖然是國慶佳節,但也不能中止學習的腳步,我選擇在教研室爲祖國母親默默地慶生。網絡
關於Android的多線程知識,請參考本人以前的一篇博客:Android 多線程----AsyncTask異步任務詳解多線程
在Android當中,提供了異步消息處理機制的兩種方式來解決線程之間的通訊問題,一種是今天要講的Handler的機制,還有一種就是以前講過的 AsyncTask 機制。app
1、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的建立和銷燬)
Message msg = handler.obtainMessage();
或者寫成:
Message msg = Message.obtain();
上面的代碼中,咱們並無在子線程中直接進行UI操做,而是建立了一個Message對象,並將它的what字段的值指定爲了一個整形常量UPDATE_TEXT,用於表示更新TextView這個動做。而後調用Handler的sendMessage()方法將這條Message發送出去。很快,Handler就會收到這條Message,並在handleMessage()方法,在這裏對具體的Message進行處理(須要注意的是,此時handleMessage()方法中的代碼是在主線程中運行的)。若是發現Message的what字段的值等於UPDATE_TEXT,就將TextView顯示的內容更新。運行程序後,點擊按鈕,TextView就會顯示出更新的內容。
注:若是從源碼的角度理解,粗略的描述是這樣的:
先是調用了handler的obtainMessage()方法獲得Message對象。在obtainMessage()方法裏作的事情是:調用了Message.obtain(this)方法,把handler做爲對象傳進來。在Message.obtain(this)方法裏作的事情是:生成message對象,把handler做爲參數賦值給message的target屬性。總的來講,一個Handler對應一個Looper對象,一個Looper對應一個MessageQueue對象,使用Handler生成Message,所生成的Message對象的Target屬性,就是該對象。而一個Handler能夠生成多個Message,因此說,Handler和Message是一對多的關係。
2、異步消息處理機制:
Handler是Android類庫提供的用於接受、傳遞和處理消息或Runnable對象的處理類,它結合Message、MessageQueue和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提供的postDelayed(Runnable r,long delayMillis)方法指定在多久後執行某項操做,好比噹噹、淘寶、京東和微信等手機客戶端的開啓界面功能,都是經過Handler定時任務來完成的。
咱們接下來說一下post。
(2)線程間的通訊:
在執行較爲耗時的操做時,Handler負責將子線程中執行的操做的結果傳遞到UI線程,而後UI線程再根據傳遞過來的結果進行相關UI元素的更新。(上面已有說明)
3、post:
對於Handler的Post方式來講,它會傳遞一個Runnable對象到消息隊列中(這句話稍後會進行詳細解釋),在這個Runnable對象中,重寫run()方法。通常在這個run()方法中寫入須要在UI線程上的操做。
Post容許把一個Runnable對象入隊到消息隊列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。詳細解釋以下:
下面經過一個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()方法的代碼(37行至39行或者58至61行),均運行在主線程上(雖然看上去是寫在子線程當中的),若是咱們在這段代碼裏打印日誌輸出線程的名字,會發現輸出的是Main Thread的名字。因此對於這段代碼而言,不能執行在UI線程上的操做,同樣沒法使用post方式執行,好比說訪問網絡。
咱們如今來解釋一下上面藍色字體的那句話:
這個Runnable對象被放到了消息隊列當中去了,而後主線程中的Looper(由於Handler是在主線程中生成的,因此Looper也在主線程中)將這個Runnable對象從消息隊列中取出來,取出來以後,作了些什麼呢?爲何在執行Pos的Runnable對象的run()方法時,不是從新開啓一個線程呢?要了解這個過程,只能求助Android的源代碼:
打開源碼的目錄sdk\sources\android-19\android\os,並找到Handler.java這個文件。找到post方法:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
上方的代碼中, 能夠看到,post方法其實就一行代碼(326行),裏面調用了sendMessageDelayed()這個方法,裏面有兩個參數。先看一下第一個參數getPostMessage(r):(719行)
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
上方的代碼中,將Runnable對象賦值給Message的callback屬性。注:經過查看Message.java文件的源代碼發現,callback屬性是一個Runnable對象:(91行)
/*package*/ Runnable callback;
咱們再來分析一下上方getPostMessage()這個方法,該方法完成了兩個操做:
一是生成了一個Message對象,二是將r對象複製給Message對象的callback屬性。返回的是一個Message對象。
再回到326行:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
這行代碼至關於:
public final boolean post(Runnable r) { Message msg = getPostMessage(r); return sendMessage(msg);// //若是須要延時的話,這一行能夠改成return sendMessageDelayed(msg,0);其中第二個參數改成具體的延時時間 }
如今應該好理解了:
第一個問題,如何把一個Runnable對象放到消息隊列中:其實是生成了一個Message對象,並將r賦值給Message對象的callback屬性,而後再將Message對象放置到消息隊列當中。
咱們再看看一下Looper作了什麼。打開Looper.java的dispatchMessage的方法:(136行)
//一個Handler對應一個Looper對象,一個Looper對應一個MessageQueue對象, //使用Handler生成Message,所生成的Message對象的Target屬性,就是該對象 //Message msg = handler.obtainMessage(); //發送一個message對象 //handler.sendMessage(msg); msg.target.dispatchMessage(msg);
這裏面調用了dispatchMessage()方法,打開Handler.java的dispatchMessage()方法:(93至104行)
1 /** 2 * Handle system messages here. 3 */ 4 public void dispatchMessage(Message msg) { 5 if (msg.callback != null) { 6 handleCallback(msg); 7 } else { 8 if (mCallback != null) { 9 if (mCallback.handleMessage(msg)) { 10 return; 11 } 12 } 13 handleMessage(msg); 14 } 15 }
上方第5行代碼:由於此次已經給Message的callback屬性賦值了,因此就不爲空,直接執行這行代碼。即執行handleCallBack()這個方法。打開handleCallBack()方法的源碼:(732至734行)
private static void handleCallback(Message message) { message.callback.run(); }
看到這個方法,就真相大白了:message的callback屬性直接調用了run()方法,而不是開啓一個新的子線程。
如今能夠明白了:
第二個問題: Looper取出了攜帶有r對象的Message對象之後,作的事情是:取出Message對象以後,調用了dispatchMessage()方法,而後判斷Message的callback屬性是否爲空,此時的callback屬性是有值的,因此執行了handleCallback(Message message),在該方法中執行了 message.callback.run()。根據Java的線程知識,咱們能夠知道,若是直接調用Thread對象或者Runnable對象的run()方法,是不會開闢新線程的,而是在原有的線程中執行。
由於Looper是在主線程當中的,因此dispatchMessage()方法和handleMessage()方法也都是在主線程當中運行。因此post()裏面的run方法也天然是在主線程當中運行的。 使用Post()方法的好處在於:避免了在主線程和子線程中將數據傳來傳去的麻煩。
4、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發送消息相關的方法有:
5、經過Handler實現線程間通訊:
一、在Worker Thread發送消息,在MainThread中接收消息:
【實例】點擊反扭,將下方的TextView的內容修改成「從網絡中獲取的數據」
<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。
二、在MainThread中發送消息,在Worker Thread中接收消息:
【實例】點擊按鈕,在在MainThread中發送消息,在Worker Thread中接收消息,並在後臺打印輸出。
<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>
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 }