聊聊Android的訊息機制
Android的訊息機制簡單點說就是Handler的執行機制和它所附帶的MessageQueue和Looper的工作過程。
Handler、MessageQueue和Looper這三者其實是一個整體。但是我們要想清楚這個整體的工作流程就需要逐個擊破。
下圖羅列了這次分享說的重點
一、為什麼提供這種機制
系統之所以提供這種機制主要是為了解決在子執行緒不能訪問UI的矛盾。
那麼問題來了…
1.為什麼子執行緒不能訪問ui呢?
因為Android的ui控制元件不是執行緒安全的。如果在多執行緒的情況下併發訪問就會導致ui控制元件處於不可預期的狀態。
2.那麼在這種情況下為什麼不對ui控制元件的訪問加上鎖機制呢?
首先加上鎖的機制會使訪問ui控制元件的邏輯變得複雜,其次因為鎖機制會阻塞某些執行緒的進行,從而降低UI訪問的效率。基於這兩個原因最好的辦法就是使用單執行緒處理UI控制元件。反之對於開發者來說只需要利用Handler切換一下執行緒就可以了,也不是很麻煩
二、MessageQueue(訊息佇列)
Android的訊息佇列是MessageQueue,它主要有兩個功能:插入和讀取。
它的內部實現是並不如它名字那樣是個佇列,而是用一個單鏈表來維護訊息列表,我們知道單鏈表的資料結構實現插入和刪除的效率高。
插入的操作很簡單,本質就是單鏈表的插入操作。
//MessageQueue的插入操作 -enqueueMessage方法 boolean enqueueMessage(Message msg, long when) { ...... synchronized (this) { ...... for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } } ...... }
讀取的操作next方法,通過無限的迴圈來檢查有沒有新訊息,當有新訊息來的時候,next方法會返回這條訊息並將其從單鏈表中移除。
//MessageQueue的讀取操作(伴隨著刪除操作)-next方法 Message next() { ...... synchronized (this) { ...... // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } ..... }
三、ThreadLocal的工作原理
ThreadLocal是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料和讀取資料。
它的特點通俗點說就是,不同的執行緒訪問同一個物件,它們通過ThreadLocal獲得的值是不一樣的。
還可以這麼神奇?
首先它是一個泛型類
public class ThreadLocal<T> {}
其次通過set方法我們可以看到它使用了Map的資料結構,它的key就是執行緒,value就是該物件的值。
//獲取到當前執行緒的該變數的值,如果沒有則初始化一個。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法將執行緒對應的值進行儲存 之後,通過get方法就可以獲取到該物件在這個執行緒中的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
最後丟擲一個問題
為什麼通過ThreadLocal可以在不同的執行緒中維護一套資料的副本並且互不干擾?
因為不同的執行緒訪問同一個ThreadLocal的get方法,ThreadLocal內部會從各自的執行緒中取出一個數組,然後再從陣列中根據當前的ThreadLocal的索引去查詢對應的value值,這樣不同的執行緒中取出的陣列自然是不同的。簡單的說就是它們所對ThreadLocal的讀寫操作僅限於各自的執行緒內部。
四、Looper的工作原理
我們知道,Handler的工作需要Looper,沒有Looper的執行緒就會報錯。
那麼Looper是做什麼的呢?
具體來說,就是不停的從MessageQueue中檢視是否有新的訊息,如果有訊息就會立刻處理,否則就一直阻塞在那裡等待新的訊息。
如何為執行緒建立Looper?
很簡單,通過Looper.prepare就可以為執行緒建立一個Looper,這樣這個執行緒就有了屬於他的Looper。之後呼叫Looper.loop()就可以開啟訊息迴圈。
Looper最重要的就是loop方法,它的工作原理也很簡單,首先它是一個死迴圈,會呼叫MessageQueue的next方法不斷的獲取新的訊息並處理,沒有訊息的時候就會阻塞在那裡。直到Looper呼叫quit或者quitSafely方法。也就是說我們最後是要在合適的時候退出Looper的。否則就會一直迴圈下去。
五、Handler的工作原理
Handler的工作主要包含訊息的傳送和接收。
1.傳送訊息主要通過post和send的一系列方法實現
mHandler.postDelayed(runnable,0);
傳送訊息的過程只是向MessageQueue中插入一條訊息,它的next方法就會把這條訊息返回給Looper,Looper收到訊息後就開始處理了。最終訊息由Looper交由Handler處理。由於Looper裡面使用了ThreadLocal,所以它就能夠將訊息切換到指定的執行緒中執行。
2.處理訊息
Looper將訊息交由Handler處理,就會呼叫Handler的disaptchMessage方法。這時候就進入了處理訊息的階段。
/**
* 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);
}
}
首先,檢查Message的callback是否為null,不為null就通過handleCallback處理訊息。這個callback是一個Runnable物件,也就是我們在post方法裡傳遞的Runnable引數。
其次檢查mCallBack是否為null,不為空就呼叫handleMessage方法。CallBack是一種當我們不想通過派生子類建立Handler的另外一種實現方式。
最後呼叫Handler的handlerMessage來處理訊息
到這裡我們就聊完了Android的訊息機制,它的作用就是很輕鬆的將某個任務或者說是訊息切換到指定的執行緒(Handler所在的執行緒)中執行。本質上來說,它並不是專門用於更新UI,只是常常被用在更新UI上。