Android的訊息機制
Android的訊息機制主要是指Handler的執行機制,Handler的執行需要底層的MessageQueue和Looper的支撐。MessageQueue的內部採用單鏈表的資料結構來儲存訊息列表。Looper會以無限迴圈的形式去查詢是否有新訊息,如果有的話就處理訊息,否則就一直等待。Looper中有一個特殊的概念,就是ThreadLocal,ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。Handler建立的時候會使用當前執行緒的Looper來構造訊息迴圈系統,Handler在內部就是通過使用ThreadLocal來獲取每個執行緒的Looper。
執行緒預設是沒有Looper的,如果需要使用Handler就必須為執行緒建立Looper。在主執行緒中可以直接使用Handler是因為主執行緒就是ActivityThread,ActivityThread被建立時就會初始化Looper。
Handler訊息機制概述
Handler的主要作用是 將一個任務切換到某個指定的執行緒中去執行 。
問:為什麼Android不允許在子執行緒中訪問UI呢?
答:Android的UI控制元件不是執行緒安全的,如果在多執行緒中併發訪問可能會導致UI控制元件處於不可預期的狀態。
問:為什麼Android系統不給UI控制元件的訪問加上鎖機制呢?
答:1.加上鎖機制會讓UI訪問的邏輯變得複雜。2.鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些執行緒的執行。鑑於這兩個缺點,最簡單高效的方法就是採用單執行緒模型來處理UI操作,對於開發者來說只需要通過Handler切換一下UI訪問的執行執行緒即可。
ThreadLocal的工作原理
ThreadLocal是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定執行緒中可以獲取到儲存的資料,對於其他執行緒來說則無法獲取到資料。ThreadLocal另一個使用場景是複雜邏輯下的物件傳遞。下面通過列子程式碼來說明。
package com.tom.threadlocaldemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class ThreadLocalDemo extends AppCompatActivity { private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread_local_demo); mBooleanThreadLocal.set(true); Log.d("ThreadLocal","主執行緒 :" + mBooleanThreadLocal.get()); new Thread("子執行緒 1"){ @Override public void run() { mBooleanThreadLocal.set(false); Log.d("ThreadLocal","子執行緒 1 :" + mBooleanThreadLocal.get()); } }.start(); new Thread("子執行緒 2"){ @Override public void run() { Log.d("ThreadLocal","子執行緒 2 :" + mBooleanThreadLocal.get()); } }.start(); } }
執行並檢視輸入日誌,結果如下圖:

ThreadLocal執行結果
在主執行緒中設定了mBooleanThreadLocal的值為true,在子執行緒1中設定為false,在子執行緒2中沒有設定,通過get方法,分別獲取到的值為true,false,null,符合預期結果。
ThreadLocal的值在table陣列中的儲存位置總是為ThreadLocal的reference欄位所標識的物件的下一個位置,例如ThreadLocal的reference物件在table陣列中的索引為index,那麼ThreadLocal的值在table陣列中的索引就是index + 1。最終ThreadLocal的值將會被儲存在table陣列中:table[index +1] = value。
關於ThreadLocal的原始碼解析,可以檢視任老師的書。
訊息佇列的工作原理
訊息佇列在Android中指的是MessageQueue,MessageQueue主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作,插入的方法為enqueueMessage,往訊息佇列中插入一條資訊,讀取的方法為next,從訊息佇列中取出一條訊息並將其從訊息佇列中刪除。MessageQueue是通過一個單鏈表的資料結構來維護的。
Looper的工作原理
Handler的工作需要Looper,沒有Looper的執行緒就會報錯,通過Looper.prepare()可以為當前執行緒建立一個Looper,接著通過Looper.loop()來開啟訊息迴圈。
new Thread("子執行緒"){ @Override public void run() { Looper.prepare(); Handler handler = new Handler(); Looper.loop(); } }.start();
Looper除了prepare方法外,還提供了prepareMainLooper方法,這個方法主要是給主執行緒也就是ActivityThread建立Looper使用的,其本質也是通過prepare方法來實現的。由於主執行緒的Looper比較特殊,所以Looper提供了一個getMainLooper方法,通過它可以在任何地方獲取到主執行緒的Looper。Looper提供了quit和quitSafely來退出一個Looper,quit會直接退出Looper,而quitSafely只是設定一個退出標記,然後把訊息佇列中的已有訊息處理完畢後才安全的退出。在執行quit或者quitSafely方法後,Handler的sendMessage方法會返回false。在子執行緒中,如果手動為其建立了Looper,在所有事情完成以後應該呼叫quit方法來終止訊息迴圈,否則這個子執行緒就會一直處於等待的狀態,而如果退出Looper以後,這個執行緒就會立刻終止,因此建議不需要的時候終止Looper。
原始碼中quit和quitSafely方法的區別,二者都是呼叫了MessageQueue的quit方法,只是引數傳遞不同,傳true的時候呼叫removeAllFutureMessagesLocked()方法,false的時候呼叫removeAllMessagesLocked(),具體的可以在原始碼中檢視。
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); }
loop方法是一個死迴圈,唯一跳出迴圈的方式是MessageQueue的next方法返回了null。當Looper的quit方法被呼叫時,Looper就會呼叫MessageQueue的quit或者quitSafely方法來通知訊息佇列退出,當訊息佇列被標記為退出狀態時,它的next方法就會返回null。
Handler的工作原理
Handler的工作主要包括訊息的傳送和接收過程。訊息的傳送可以通過post的一系列方法以及send的一系列方法來實現,post的一系列方法最終是通過send的一系列方法來實現的。
Handler傳送訊息的過程僅僅是向訊息佇列中插入了一條訊息,MessageQueue的next方法就會返回這條訊息給Looper,Looper收到訊息後就開始處理了,最終訊息由Looper交由Handler處理,即Handler的dispatchMessage方法會被呼叫,這時Handler就進入了處理訊息的階段。
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }