Android 的訊息機制(Handler訊息傳遞機制)
出於效能最佳化考慮,android的UI操作並不是安全執行緒的,這意味著意味著如果有多個線程並行作業UI組件,可能導致安全執行緒問題,未解決此問題,
從開發的角度來說, Handler 是 Android 訊息機制的上層介面, 這使得在開發過程中只需要和 Handler 互動即可。
Handler 的使用過程很簡單,通過它可以輕鬆地將一個任務切換到 Handler 所在的線程中去執行。很多人認為
Handler的作用是更新 UI, 這的確沒錯,但是更新 UI 僅僅是 Handler 的一個特殊的使用情境.具體來說是這樣的:
有時候需要在子線程中進行耗時的 I/O 操作,可能是讀取檔案或者訪問網路等, 當耗時操作完成以後可能需要在 UI 上做一些改變, 由於
Android 開發規範的限制,我們並不能在子線程中訪問 UI 控制項,否則就會觸發程式異常, 這個時候通過Handler 就可 以將更新
UI 的操作切換到主線程中執行。 因此,本質上來說, Handler 並是專門用於更新 UI 的,它只是常被開發人員用來更新 UI。
Android 的訊息機制主要是指 Handler 的運行機制, Handler 的運行需要底層的MessageQueue 和
Looper 的支撐。 MessageQueue 的中文翻譯是訊息佇列,顧名思義,
它的內部儲存了一組訊息,以隊列的形式對外提供插入和刪除的工作。 雖然叫訊息佇列,但是它的內部儲存結構並不是真正的隊列,
而是採用單鏈表的資料結構來儲存訊息列表。 Looper的中文翻譯為迴圈, 在這裡可以理解為訊息迴圈。由於 MessageQueue
只是一個訊息的儲存單元,它不能去處理訊息,而 Looper 就填補了這個功能, Looper
會以無限迴圈的形式去尋找是否有新訊息,如果有的話就處理訊息,否則就一直等待著。 Looper 中還有一個特殊的概念,那就是
ThreadLocal, ThreadLocal 並不是線程,它的作用是可以在每個線程中儲存資料。我們知道, Handler
建立的時候會採用當前線程的 Looper 來構造訊息迴圈系統,那麼 Handler 內部如何擷取到當前線程的 Looper 呢?
這就要使用 ThreadLocal 了,ThreadLocal可以在不同的線程中互不干擾地儲存並提供資料,通過 ThreadLocal
可以輕鬆擷取每個線程的 Looper。當然需要注意的是,線程是預設沒有 Looper 的,如果需要使用 Handler 就必須為線程建立
Looper。我們經常提到的主線程,也叫 UI 線程,它就是ActivityThread,ActivityThread被建立時就會初始化
Looper,這也是在主線程中預設可以使用 Handler 的原因。 上面這段話是任玉剛老師書中對於android訊息傳遞機制的描述我覺得描述的非常清晰。
android定下規則:只允許UI線程(主線程)修改Activity裡的UI組件
但是應用中有時需要操作UI如擷取簡訊驗證碼,有個倒計時,為此需要藉助Handler訊息傳遞機制
Handler類簡介
【1】作用:
1在新啟動的線程中發送訊息
2在主線程中擷取處理訊息
主要方法
方法簽名
public void handleMessage (Message msg)
子類對象通過該方法接收資訊
public final boolean sendEmptyMessage (int what)
發送一個只含有what值的訊息
public final boolean sendMessage (Message msg)
發送訊息到Handler,通過handleMessage方法接收
public final boolean hasMessages (int what)
監測訊息佇列中是否還有what值的訊息
public final boolean hasMessages (int what,Objectobject)
監測訊息佇列中是否還有what值的訊息且object屬性為制定對象的訊息
public final boolean post (Runnable r)
將一個線程添加到訊息佇列
下面一個倒計時例子
public class MainActivity extends Activity { private Button button; private int count; private Message message; private static final int TIME_DESC=0; private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (message.what){ case TIME_DESC: String time= (String) message.obj; button.setText(time); break; } } } ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button= (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count=60; new Thread(new Runnable() { @Override public void run() { while(count>0){ count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } message=new Message(); message.obj=count+秒; message.what=TIME_DESC; handler.sendMessage(message); } } }).start(); } }); }}
精簡一下上面的代碼如下
public class MainActivity2 extends Activity { private Button button; private int count; private Message message; private static final int TIME_DESC=0; private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (message.what){ case TIME_DESC: count--; button.setText(count+秒); if(count>0){ handler.sendEmptyMessage(TIME_DESC); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } break; } } } ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button= (Button) findViewById(R.id.button); //message初始化 message=new Message(); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count=60; handler.sendEmptyMessage(TIME_DESC); } }); }}
Looper簡介
Android中的Looper類,是用來封裝訊息迴圈和訊息佇列的一個類,用於在android線程中進行訊息處理。handler其實可以看做是一個工具類,用來向訊息佇列中插入訊息的。
(1) Looper類用來為一個線程開啟一個訊息迴圈。
預設情況下android中新誕生的線程是沒有開啟訊息迴圈的。(主線程除外,主線程系統會自動為其建立Looper對象,開啟訊息迴圈。)
Looper對象通過MessageQueue來存放訊息和事件。一個線程只能有一個Looper,對應一個MessageQueue。
(2) 通常是通過Handler對象來與Looper進行互動的。Handler可看做是Looper的一個介面,用來向指定的Looper發送訊息及定義處理方法。
預設情況下Handler會與其被定義時所線上程的Looper綁定,比如,Handler在主線程中定義,那麼它是與主線程的Looper綁定。
mainHandler = new Handler() 等價於new Handler(Looper.myLooper()).
Looper.myLooper():擷取當前進程的looper對象,類似的 Looper.getMainLooper() 用於擷取主線程的Looper對象。
(3) 在非主線程中直接new Handler() 會報如下的錯誤:
E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception
E/AndroidRuntime( 6173): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
原因是非主線程中預設沒有建立Looper對象,需要先調用Looper.prepare()啟用Looper。
(4) Looper.loop(); 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。
注意:寫在Looper.loop()之後的代碼不會被執行,這個函數內部應該是一個迴圈,當調用mHandler.getLooper().quit()後,loop才會中止,其後的代碼才能得以運行。
(5) 基於以上知識,可實現主線程給子線程(非主線程)發送訊息。
public class MainActivity3 extends Activity { private Button button_send; private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.mainthreadsend); handler=new Handler(); button_send = (Button) findViewById(R.id.button_send); button_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MyThread thread = new MyThread(); thread.start(); handler.sendEmptyMessage(0); } }); } class MyThread extends Thread { @Override public void run() { Looper.prepare(); handler = new Handler() { @Override public void handleMessage(Message msg) { Log.d(handle, 收到主線程發送的訊息); } }; Looper.loop(); } }}
結果