在Android中談到Handler,我們首先來講一下Handler的機制和原理
1、Handler機制原理
Handler:主要是用來處理髮送和接收訊息的,作用是把訊息加入特定的(Looper)訊息佇列中,並分發和處理該訊息佇列中的訊息。構造Handler的時候可以指定一個Looper對象,通過Handler對象我們可以封裝Message對象,然後通過sendMessage(msg)把Message對象添加MessageQueue中;當MessageQueue迴圈到該Message時,就會調用該Message對象對應的handler對象的handleMessage()方法對其進行處理。
在主線程通過Handler handler = new Handler();即使用預設建構函式構造Handler時,是預設使用主線程的Looper對象,通過這種方式構造的Handler是屬於主線程的,也就是Handler和Looper是綁定在一起的,如果構造Handler時指定一個新線程的Looper對象,則該Handler對象是屬於該子線程的。
每個Android應用程式都運行在一個dalvik虛擬機器進程中,進程開始的時候會啟動一個主線程(MainThread),主線程負責處理和ui相關的事件,因此主線程通常又叫UI線程。而由於Android採用UI單執行緒模式,所以只能在主線程中對UI元素進行操作。如果在非UI線程直接對UI進行了操作,則會報錯,Handler是需要在主線程中進行初始化的。為什麼要這麼說呢,因為你在自己new一個新線程中去簡單建立一個Handler,程式執行是會報錯的:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() atandroid.os.Handler.<init>(Handler.java:121)at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86) at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86
為什麼在主線程中不會報錯,而在自己建立的線程中就會報這個錯誤呢?很簡單,因為主線程它已經建立了Looper,因為主線程預設有一個自己的Looper對象和Message對象,非主線程預設是沒有自己的Looper,需要自己去建立:通過Looper.prepare建立一個Looper對象,以及
Looper.loop()建立訊息迴圈隊列;在新線程(非主線程)中建立Handler對象,有兩種方法實現:
(1)建立Handler之前通過 Looper.prepare()方法建立Looper對象
class MyThread extends Thread { public void run() { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]-- run...", Thread .currentThread().getName())); //非主線程中建立一個handler Looper.prepare();// 建立該線程的Looper對象,用於接收訊息,在非主線程中是沒有looper的所以在建立handler前一定要使用prepare()建立一個Looper Handler myThreadHandler = new Handler() { public void handleMessage(android.os.Message msg) { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler handleMessage run...", Thread .currentThread().getName())); } Looper.myLooper().loop();//建立一個訊息迴圈,該線程不會退出 }; } }
(2)使用主線程的Looper對象,因為每個主線程預設已經建立了自己的Looper對象
class MyThread extends Thread { public void run() { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]-- run...", Thread .currentThread().getName())); Handler threadMainLoopHandler = new Handler(Looper.getMainLooper()) { //該handleMessage方法將在mainthread中執行 public void handleMessage(android.os.Message msg) { Log.d(Constant.Tag,MessageFormat.format("Thread[{0}]--threadMainLoopHandler handleMessage run...", Thread.currentThread().getName())); } }; } }
(3)小結
(1)和(2)的最主要區別在於:(1)的Handler對象屬於子線程,其handleMessage是在子線程中執行,而(2)的Handler是屬於主線程的,因為它和主線程的Looper.getMainLooper()) 對象綁定在一起的,所以在handleMessage方法中不要做複雜耗時的操作如網路、IO操作等,否則會阻塞UI線程,造成ANR現象。
熟悉了Handler、Looper機制的原理之後,接下來對於如何通過Handler來實現非同步處理就更容易理解了,首先我們看一個通過Handler和Runnable來實現一個類似的計算機功能:
2、Handler+runable(可以實作類別似一個計數器的功能,注意與下面3的區別,是同步還是非同步方式呢?)
Handler對象在進行初始化的時候,會預設的自動綁定訊息佇列。利用類post方法,可以將Runnable對象發送到訊息佇列中,按照隊列的機制按順序執行不同的Runnable對象中的run方法。
1.public class HandlerActivity extends Activity { 2. /** Called when the activity is first created. */ 3. //聲明兩個按鈕控制項 4. private Button startButton = null; 5. private Button endButton = null; 6. @Override 7. public void onCreate(Bundle savedInstanceState) { 8. super.onCreate(savedInstanceState); 9. setContentView(R.layout.main); 10. //根據控制項的ID得到代表控制項的對象,並未這兩個按鈕設定相應的監聽器 11. startButton = (Button)findViewById(R.id.startButton); 12. startButton.setOnClickListener(new StartButtonListener()); 13. endButton = (Button)findViewById(R.id.endButton); 14. endButton.setOnClickListener(new EndButtonListener()); 15. 16. } 17. class StartButtonListener implements OnClickListener{ 18. 19. @Override 20. public void onClick(View v) { 21. //調用Handler的post方法,將要執行的線程對象添加到隊列當中 22. handler.post(updateThread); 23. } 24. 25. } 26. 27. class EndButtonListener implements OnClickListener{ 28. 29. @Override 30. public void onClick(View v) { 31. handler.removeCallbacks(updateThread); 32. } 33. 34. } 35. //建立一個Handler對象 36. Handler handler = new Handler(); 37. //將要執行的操作寫線上程對象的run方法當中 38. Runnable updateThread = new Runnable(){ 39. 40. @Override 41. public void run() { 42. System.out.println("UpdateThread"); 43. //在run方法內部,執行postDelayed或者是post方法 44. handler.postDelayed(updateThread, 3000); 45. } 46. 47. }; 48.} 3、通過Handler來實現非同步處理的2種方式 3.1 採用Thread+ handler Message 方式
Thread+ handler Message 方式 即 :線程開啟,採用Message傳遞後台(子)線程和UI主線程之間的資訊,UI線程收到資訊之後,進行更新UI的操作。
public class handlerTestActivity extends Activity{ private ProgressBar bar = null; private boolean isRunning = false; /* 我們為這個Acivity建立一個用於和背景程式通訊的handler,簡單地,只要一收到message,就將progressbar進度增加5。*/ /* 步驟1:建立Handler,並通過handleMessage()給出當收到訊息是UI需要進行如何處理,例子簡單不對msg的內容進行分析*/ Handler handler= new Handler(){ public void handleMessage(Message msg) { bar.incrementProgressBy(5); } }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.chapter_15_test1); bar=(ProgressBar)findViewById(R.id.c15_progress); } /*on Start是UI初始化並顯示時調用*/ protected void onStart() { super.onStart(); bar.setProgress(0); /*步驟2:建立後台線程處理,採用Thread,其中run()的內容,就是線程平行處理的內容,Thread是Runnable的implements*/ Thread background = new Thread(new Runnable(){ public void run() { try{ for(int i = 0; i < 20 && isRunning; i ++){ Thread.sleep(1000); /* 步驟2.1:發送Message到隊列中,參數中的obtainMessage()是用於給出一個新Message,本例無參數,對應的在handler在隊列中收到這條訊息時,則通過handleMessage()進行處理*/ handler.sendMessage(handler.obtainMessage()); } }catch(Throwable t){ //jest end the thread } } }); isRunning = true; /*步驟3:啟動線程*/ background.start(); } /*onStop是UI停止顯示時調用,例如我們按了返回鍵*/ protected void onStop() { super.onStop(); isRunning = false; } }3.2 Handler 和多執行緒方式,採用 Handler Post 、Runnable來實現(注意和2的區別)
Handler Post方法雖然發送的是一個實現了Runnable介面的類對象,但是它並非建立了一個新線程,而是執行了該對象中的run方法。也就是說,整個run中的操作和主線程處於同一個線程。
這樣對於那些簡單的操作,似乎並不會影響。但是對於耗時較長的操作,當它被加入到訊息佇列中之後執行會佔用很長的時間,以至於處於同一線程的其他動作無法繼續執行,就會出現“假死”。為瞭解決這個問題,就需要使得handler綁定到一個新開啟線程的訊息佇列上,在這個處於另外線程的上的訊息佇列中處理傳過來的Runnable對象和訊息。SDK文檔中也提供了相關說明:
1.public class HandlerTest2 extends Activity { 2. 3. @Override 4. protected void onCreate(Bundle savedInstanceState) { 5. // TODO Auto-generated method stub 6. super.onCreate(savedInstanceState); 7. setContentView(R.layout.main); 8. //列印了當前線程的ID 9. System.out.println("Activity-->" + Thread.currentThread().getId()); 10.11. //產生一個HandlerThread對象 12. HandlerThread handlerThread = new HandlerThread("handler_thread"); 13. //在使用HandlerThread的getLooper()方法之前,必須先調用該類的start(),同時開啟一個新線程; 14. handlerThread.start();15. //將由HandlerThread擷取的Looper傳遞給Handler對象,即由處於另外線程的Looper代替handler初始化時預設綁定的訊息佇列來處理訊息。 16. //自訂Handler17. MyHandler myHandler = new MyHandler(handlerThread.getLooper()); 18. Message msg = myHandler.obtainMessage(); 19. //將msg發送到目標對象,所謂的目標對象,就是產生該msg對象的handler對象 20. Bundle b = new Bundle(); 21. b.putInt("age", 20); 22. b.putString("name", "Jhon"); 23. msg.setData(b); 24. msg.sendToTarget();//將msg發送到myHandler25. } 26. 27. //自訂Handler類28. class MyHandler extends Handler{ 29. public MyHandler(){ 30. 31. } 32. 33. public MyHandler(Looper looper){ 34. super(looper); 35. } 36. //這裡的handleMessage將在子線程handlerThread中執行而不是在主線程中執行37. @Override 38. public void handleMessage(Message msg) { 39. Bundle b = msg.getData(); 40. int age = b.getInt("age"); 41. String name = b.getString("name"); 42. System.out.println("age is " + age + ", name is" + name); 43.System.out.println("Handler--->" + Thread.currentThread().getId()); System.out.println("handlerMessage"); 44. } 45. } 46.}