1. 程式人生 > >Handler post 和 sendmessage的解析

Handler post 和 sendmessage的解析

一.一個問題

有這樣一個問題值得我們思考,若把一些類似於下載的功能(既耗時且不一定有結果)寫在Activity(主執行緒)裡,會導致Activity阻塞,長時間無響應,直至頁面假死(如果5秒鐘還沒有完成的話,會收到Android系統的一個錯誤提示 "強制關閉")。因此,我們需要把這些耗時的操作放在單獨的子執行緒中操作。這就是Handler的使命。Handler提供非同步處理的功能,傳送和接收不是同時的(Activity的主執行緒和執行緒佇列裡的執行緒是不同的執行緒,並行進行,互不影響)。

二.Handler簡介

Handler 為Android作業系統中的執行緒通訊工具,它主要由兩個作用:(1)安排訊息或Runnable 在某個主執行緒中某個地方執行(2)安排一個動作在另外的執行緒中執行。每個Handler物件維護兩個佇列(FIFO),訊息佇列和Runnable佇列, 都是有Android作業系統提供的。Handler可以通過這兩個佇列來分別:

  1. 傳送、接受、處理訊息–訊息佇列;
  2. 啟動、結束、休眠執行緒–Runnable佇列;

Handler的使用方法大體分為3個步驟:1.建立Handler物件。2.建立Runnable和訊息。3.呼叫post以及sendMessage方法將Runnable和訊息新增到佇列。

通訊通過新執行緒呼叫 Handler的post()方法和sendMessage()方法實現,分別對應功能:

  1. post()  將一個執行緒加入執行緒佇列(與Handler繫結的執行緒,即new Handler(looper)中傳入looper所在的執行緒);
  2. sendMessage() 傳送一個訊息物件到訊息佇列;

三.Runnable佇列

1.java中的執行緒

在java中,執行緒的建立有兩種方法:繼承Thread類和實現Runnable介面。而這最重要的都是要複寫run方法來實現執行緒的功能。當執行緒的時間片到了,開始執行時,就執行run()函式,執行完畢,就進入死亡狀態。

舉個建立執行緒的例子:

  1. Runnable r=new Runnable(){ 
  2. @Override
  3. publicvoid run() { 
  4. // TODO Auto-generated method stub
  5. System.out.println("thread"); 
  6. handler.postDelayed(thread, 3000); 
  7. }; 

2.關於Runnable佇列

(1)原理

Android的執行緒非同步處理機制:Handler物件維護一個執行緒佇列,有新的Runnable送來(post())的時候,把它放在隊尾,而處理 Runnable的時候,從隊頭取出Runnable執行。當向佇列傳送一個Runnable後,立即就返回,並不理會Runnable是否被執行,執行 是否成功等。而具體的執行則是當排隊排到該Runnable後系統拿來執行的。這就好比郵局的例子。寄信者將信寫好後放入郵筒就回家了,他並不知道郵件何 時被郵局分發,何時寄到,對方怎樣讀取這些事。這樣,就實現了Android的非同步處理機制。

(2)具體操作

向佇列新增執行緒:

handler.post(Runnable );將Runnable直接新增入佇列

handler.postDelayed(Runnable, long)延遲一定時間後,將Runnable新增入佇列

handler.postAtTime(Runnable,long)定時將Runnable新增入佇列

終止執行緒:

handler.removeCallbacks(thread);將Runnable從Runnable佇列中取出

四.訊息佇列

1.訊息物件

(1)Message物件

Message物件攜帶資料,通常它用arg1,arg2來傳遞訊息,當然它還可以有obj引數,可以攜帶Bundle資料。它的特點是系統性能消耗非常少。

初始化: Message msg=handler.obtainMessage();

(2)Bundle物件

Bundle是Android提供的類,可以把它看做是特殊的Map,即鍵值對的包。而它特殊在鍵和值都必須要是基本資料型別或是基本資料型別的陣列(Map的鍵值要求都是物件),特別的,鍵要求都是String型別。用Message來攜帶Bundle資料:

放入:msg.setData(Bundle bundle);

取出:msg.getData();

2.關於訊息佇列

(1)原理

Android的訊息非同步處理機制:Handler物件維護一個訊息佇列,有新的訊息送來(sendMessage())的時候,把它放在隊尾,之後排隊 到處理該訊息的時候,由主執行緒的Handler物件處理(handleMessage())。整個過程也是非同步的,和Runnable佇列的原理相同。

(2)具體操作:

向佇列新增Runnable:handler.sendMessage(Message);

將訊息傳送到訊息佇列msg.sendToTarget();

延遲一定時間後,將訊息傳送到訊息佇列 handler.sendMessageDelayed(Message,long);

定時將訊息傳送到訊息佇列 handler.sendMessageAtTime(Message,long)

處理訊息:

訊息的具體處理過程,需要在new Handler物件時使用匿名內部類重寫Handler的handleMessage(Message msg)方法,如下:

  1. Handler handler=new Handler(){ 
  2. @Override
  3. publicvoid handleMessage(Message msg) { 
  4. // TODO Auto-generated method stub
  5. 。。。。。。 
  6. 。。。。。。 
  7. }; 

五.Handler的兩個作用

1.安排訊息或Runnable 在某個主執行緒中某個地方執行

程式碼示例:

  1. publicclass HandlerTestActivity extends Activity { 
  2. private Button start; 
  3. @Override
  4. protectedvoid onCreate(Bundle savedInstanceState) { 
  5. // TODO Auto-generated method stub
  6. super.onCreate(savedInstanceState); 
  7. setContentView(R.layout.handlertest); 
  8. start=(Button) findViewById(R.id.start); 
  9. start.setOnClickListener(new startListener()); 
  10. System.out.println("Activity Thread:"+Thread.currentThread().getId()); 
  11. Handler handler=new Handler(); 
  12. Runnable thread=new Runnable(){ 
  13. @Override
  14. publicvoid run() { 
  15. // TODO Auto-generated method stub
  16. System.out.println("HandlerThread:"+Thread.currentThread().getId()); 
  17. }; 
  18. class startListener implements OnClickListener{ 
  19. @Override
  20. publicvoid onClick(View v) { 
  21. // TODO Auto-generated method stub
  22. handler.post(thread); 

這個小程式中,首先程式啟動,進入onCreate(),打印出當前執行緒(即主執行緒)的ID,之後點選按鈕start,會將執行緒thread新增到執行緒隊 列,執行執行緒thread,thread的作用就是打印出當前執行緒的ID。在這個程式中,我們可以看到通過Handler我們可以實現安排 Runnable 在某個主執行緒中某個地方執行,即作用(1)。

不過這裡有個小小的陷阱,你發現了嗎?這個程式看上去似乎實現了Handler的非同步機制, handler.post(thread)似乎實現了新啟執行緒的作用,不過通過執行我們發現,兩個執行緒的ID相同!也就是說,實際上thread還是原來 的主執行緒,由此可見,handler.post()方法並未真正新建執行緒,只是在原執行緒上執行而已,我們並未實現非同步機制。

2.安排一個動作在另外的執行緒中執行。

(1)java中標準的建立執行緒的方法

第一步:

  1.  Runnable r=new Runnable(){ 
  2. @Override
  3. publicvoid run() { 
  4. // TODO Auto-generated method stub
  5. System.out.println("thread"); 
  6. handler.postDelayed(thread, 3000); 
  7. }; 

第二步:

  1. Thread t=new Thread (r); 

第三步:

  1. t.start(); 

若把上面示例程式中的handler.post(thread);語句改成以上形式,通過列印我們可以看到,兩個ID是不同的,新的執行緒啟動了!

(2)關於Looper

Looper類用來為執行緒開啟一個訊息迴圈,作用是可以迴圈的從訊息佇列讀取訊息,所以Looper實際上就是訊息佇列+訊息迴圈的封裝。每個執行緒只能對應一個Looper,除主執行緒外,Android中的執行緒預設是沒有開啟Looper的。

通過Handler與Looper互動,Handler可以看做是Looper的介面,用來向指定的Looper傳送訊息以及定義處理方法。預設情況下Handler會與其所線上程的Looper繫結,即:

Handler handler=new Handler();等價於Handler handler=new Handler(Looper.myLooper());

Looper有兩個主要方法:

Looper.prepare();啟用Looper
Looper.loop(); 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。

注意:寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。

(3)Handler非同步機制的實現

Handler是通過HandlerThread 使得子執行緒與主執行緒分屬不同執行緒的。實際上,HandlerThread 是一個特殊的執行緒,它是一個封裝好Looper的執行緒,

程式碼示例1:

  1. //建立一個名叫handler_hread的HandlerThread 物件
  2. HandlerThread handlerThread=new HandlerThread("handler_hread"); 
  3. //開啟handlerThread,在使用handlerThread.getLooper()之前必須先呼叫start方法,否則取出的是空
  4. handlerThread.start(); 
  5. //將handler繫結在handlerThread的Looper上,即這個handler是執行在handlerThread執行緒中的
  6. myHandler handler=new myHandler(handlerThread.getLooper()); 
  7. class myHandler extends Handler{ 
  8. public myHandler(){} 
  9. public myHandler(Looper looper){ 
  10. super(looper); 
  11. @Override
  12. publicvoid handleMessage(Message msg) { 
  13. // TODO Auto-generated method stub
  14. System.out.println("Activity Thread:"+Thread.currentThread().getId()); 
  15. }

程式碼示例2:

  1. //建立一個名叫handler_hread的HandlerThread 物件
  2. HandlerThread handlerThread=new HandlerThread("handler_hread"); 
  3. //開啟handlerThread,在使用handlerThread.getLooper()之前必須先呼叫start方法,否則取出的是空
  4. handlerThread.start(); 
  5. //將handler繫結在handlerThread的Looper上,即這個handler是執行在handlerThread執行緒中的
  6. Handler handler=new Handler(handlerThread.getLooper()); 
  7. handler.post(r);//在handlerThread中執行run
  8. Runnable r=new Runnable(){ 
  9. @Override
  10. publicvoid run() { 
  11. // TODO Auto-generated method stub
  12. System.out.println("HandlerThread:"+Thread.currentThread().getId()); 
  13. }; 
  14. }

這樣,就實現了handler的非同步處理機制,在呼叫handler.post()方法,通過列印執行緒ID可以得知,子執行緒與主執行緒是分屬不同執行緒的。