前言
Android的消息機制主要是指Handler的運行機制,Handler的運行需要底層的MessageQueue和Looper的支撐。Handler 是Android 消息機制的上層接口,這使得在開發過程中只需要和Handler交互即可。通過Handler 可以將一個任務切換到Handler所在的線程中去執行。
機制組成部分
分析Handler機制原理必然涉及ActivityThread,Handler、MessageQueue,Looper,Message等重要類。對這些類作簡要介紹:
- ActivityThread
程序的啓動入口,這就是主線程(UI線程)。ActivityThread被創建時就會初始化Looper,這也就是在主線程中默認可以使用Handler的原因。注意:線程默認沒有Looper的,如果需要使用Handler就必須爲線程創建Looper。 - MessageQueue
消息隊列,內部儲存了一組消息,以隊列的形式對外提供插入和刪除的工作。內部存儲結構不是真正的隊列,採用單鏈表的數組結構來存儲消息列表。 - Looper
消息循環,由於MessageQueue只是一個消息的存儲單元,它不能去處理消息,而Looper會以無限循環的形式去查找是否有新消息,如果有就處理消息,否則就一直等待。 - ThreadLocal
它不是線程,作用是可以在每個線程中存儲數據。ThreadLocal可以在不同的線程中互不干擾地存儲並提供數據,通過ThreadLocal可以獲取每個線程的Looper。
在Android消息機制中,Handler各個部分運行關係,如下圖:
ActivityThread
應用程序的入口是在ActivityThread的main方法中,下面是源碼分析
public static void main(String[] args) {
......
//1 創建Looper 和 MessageQueue
Looper.prepareMainLooper();
//2 建立與AMS的通信
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
//3 無限循環,不斷取出消息,向Handler分發
Looper.loop();
//可以看出來主線程也是在無限的循環的,
//異常退出循環的時候會報錯.
throw new RuntimeException("Main thread loop
unexpectedly exited");
}
我們應該知道,如果程序沒有死循環的話,執行完main函數以後就會立馬退出了。之所以我們的APP能夠一直運行着,就是因爲Looper.loop()裏面是一個死循環。
Looper 和 ThreadLocal
1.Looper 類分析
public final class Looper {
// 每個線程都有一個ThreadLocal,用來保存Looper對象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
// 保存消息隊列
final MessageQueue mQueue;
// 保存線程
final Thread mThread;
......
public static void prepare() {
prepare(true);
}
//prepare 函數
private static void prepare(boolean quitAllowed) {
//判斷sThreadLocal.get()是否爲空,如果不爲空說明已經爲該線程設Looper,不能重複設置。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//如果sThreadLocal.get()爲空,說明還沒有爲該線程設置Looper,那麼創建Looper並設置
sThreadLocal.set(new Looper(quitAllowed));
}
//ActivityThread 調用Looper.prepareMainLooper();該函數調用prepare(false);
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
// 獲取當前線程Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
......
public static void loop() {
//得到Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//得到MessageQueue
final MessageQueue queue = me.mQueue;
......
for (;;) {//無限循環
Message msg = queue.next(); // 取下一個Message 可能阻塞在這裏
if (msg == null) {
//如果隊列爲空直接return直接結束了該方法,即循環結束
return;
}
......
try {
//分發message (target指handler)
msg.target.dispatchMessage(msg);
......
} finally {
}
......
}
}
從源碼中可知,在Looper.loop() 方法中會取出內部的MessageQueue,並且迭代消息隊列裏面的消息,根據消息的target分發消息(會到Handler類中handleMessage方法中)。同時,源碼中for()循環是死循環,爲什麼不會導致應用卡死?
對於線程即是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是採用死循環的方法,通過循環方式不同與Binder驅動進行讀寫操作,當然並非簡單地死循環,無消息時會休眠。
但這裏可能又引發了另一個問題,既然是死循環又如何去處理其他事務呢?
通過創建新線程的方式。真正會卡死主線程的操作是在回調方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。
主線程的死循環一直運行是不是特別消耗CPU資源呢?
其實不然,這裏就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裏,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。這裏採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。
2.ThreadLocal 類分析
ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據。
public class ThreadLocal<T> {
....
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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的set和get方法可以看出,它們所操作對象都是當前線程的localValues對象的table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,他們對ThreadLocal所做的讀寫操作僅限於各自線程的內部,這就是ThreadLocal可以在多個線程中互不干擾地存儲和修改數據。
3.Thread 、ThreadLocal 和Looper 關係
Looper 類源碼
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// prepare() 方法中Looper 對象存儲到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
-----------------------------------------------------------------------------------
ThreadLocal 類源碼
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//這是ThreadLocal的getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
小結: 從以上源碼分析可知,map.set(this, value)通過把自身(ThreadLocal)以及值(Looper)放到了一個Map裏面,如果再放一個的話,就會覆蓋,因爲map不允許鍵值對中的鍵是重複的。所以,ThreadLocal通過get()和set()方法就可以綁定Thread和Looper。
MessageQueue
MessageQueue主要包含2個操作:插入和讀取。插入和讀取對應的方法分別爲enqueueMessage和next,其中enqueueMessage的作用是往消息隊列中插入一條消息,而next的作用是從消息隊列中取出一條消息並將其從消息隊列中移除。消息隊列的內部實現是通過一個單鏈表的數據結構來維護消息列表,單鏈表在插入和刪除上比較有優勢。
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.when = when;
Message p = mMessages;
//檢測當前頭指針是否爲空(隊列爲空)或者沒有設置when 或者設置的when比頭指針的when要前
if (p == null || when == 0 || when < p.when) {
//插入隊列頭部,並且喚醒線程處理msg
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 幾種情況要喚醒線程處理消息:1)隊列是堵塞的 2)barrier,頭部結點無target 3)當前msg是堵塞的
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 將當前msg插入第一個比其when值大的結點前。
prev.next = msg;
}
//調用Native方法進行底層操作,在這裏把那個沉睡的主線程喚醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Message next() {
.....
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
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());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取消息,並把消息隊列移動到下一條消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
}
Handler 分析
handler的工作主要包含消息的發送和接收過程。post的一系列方法最終是通過send的一系列方法來實現。handler發消息的過程是向消息隊列中插入了一條消息。MessageQueue的next方法就會返回這條消息給Looper,Looper收到消息後就開始處理了,最終消息由Looper交由Handler處理,即Handler 的dispathchMessage方法會被調用,這時handler就進入了處理消息的階段。
public void dispathchMessage(Message msg){
// 檢查Message的callback是否爲null,不爲null就通過handleCallback來處理消息
// Message的callback是一個Runnable對象,實際上就是handler的post方法所傳遞Runnable參數
if(msg.callback!=null){
handleCallback(msg);
}else{
// 檢查Handler 的mCallback是否爲null,不爲null就調用mCallback的
// handleMessage
if(mCallback!=null){
if(mCallback.handleMessage(msg))
return;
}
handleMessage(msg);
}
}
總結
通過handler的post方法將一個Runnable投遞到handler 內部的Looper 中去處理,也可以通過handler的send一個消息,這個消息同樣會在Looper中去處理。其實post 方法最終也是通過send方法來完成的。當handler的send方法被調用時,它會調用MessageQueue的enqueueMessage 方法將這個消息放入消息隊列中,然後Looper發現有新消息到來時,就會處理這個消息,最終消息中的Runnable或者handler的handleMessage 方法就會被調用。注意Looper 是運行在創建Handler 所在的線程中的,這樣一來handler中的業務邏輯就被切換到創建handler所在的線程中去執行了。
參考
Android 源碼分析之旅3.1–消息機制源碼分析
Android 消息機制——你真的瞭解Handler?
一步一步分析Android的Handler機制