談起Android 消息機制,相信各位會首先想到Handler,Handler是Android 提供給給開發者實現線程間通信的工具。Android的消息機制包含四大內容,ThreadLocal保證每個線程都有自己的消息輪詢器Looper,MessageQueue用來存放消息,Looper負責取消息,最後Handler負責消息的發送與消息的處理。
- 先來一張腦圖回顧整體知識
ThreadLocal
- 我們知道,每個Handler 都有其所在線程對應的Looper,查看Handler構造方法
/**Handler 構造方法*/
public Handler(Callback callback, boolean async) {
.......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
......
}
/** Looper 中 sThreadLocal 聲明*/
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/** Looper 中 myLooper方法*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/** Looper 中 prepare方法*/
private static void prepare(boolean quitAllowed) {
....
sThreadLocal.set(new Looper(quitAllowed));
}
- 通過以上源碼,可以知道,Looper.myLooper()獲取不到Looper則會拋異常,所以創建Handler之前都要調用一下Looper.prepare方法,也就是在該方法中新建了Looper並存放到ThreadLocal中。這裏就會產生一個疑問,ThreadLocal能保證每個線程有自己對應的Looper?沒錯,它就真能保證,接下來就看看什麼是ThreadLocal。
什麼是ThreadLocal
ThreadLocal是一個線程內部數據存儲類,但存放數據並不是它實現的,它只是幫助類,真正存放數據的是ThreadLocalMap。
先看一個簡單的例子
public class Test {
static ThreadLocal<String> name =new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
name.set("xiaoming");
System.out.println("---------------"+name.get()+"-------------------");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("---------------"+name.get()+"-------------------");
}
}).start();
}
}
### 運行結果
> Task :Test.main()
---------------xiaoming-------------------
---------------null-------------------
- 上面例子當中,兩個線程訪問的都是一個ThreadLocal對象,但是第二個線程沒有設置初始值,則獲取爲null,也就可以說明每個線程操作的是自己對應的一份數據,雖然都是從ThreadLocal的get方法獲取,但是get方法則是獲取對應線程的ThreadLocal.ThreadLocalMap來獲取值。
ThreadLocal分析
ThreadLocal的set方法
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 通過以上代碼,代碼層面首先獲取當前線程,然後獲取
ThreadLocalMap,如果存在,則獲取當前線程的ThreadLocalMap;如果不存在則根據當前線程和當前需要存入的數據新建ThreadLocalMap來存放線程內部數據,也就是當前ThreadLocal作爲key,而存儲的值最爲value來存儲。
ThreadLocal的get方法
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
- 從以上代碼,ThreadLocal的get方法根據當前線程來獲取對應的ThreadLocalMap,如果獲取不到,說明還沒有創建,由createMap方法來創建ThreadLocalMap,initialValue方法則設置了value的初始值爲null,也呼應前面的例子打印結果。
ThreadLocal原理
Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量threadLocals,如果你瞭解Java內存模型,threadLocals的值都是new出來的話,很容易明白threadLocals是存放在堆內存中的,而每一個線程只是在堆內存中存放了自己的threadLocals,也就是每個線程本地內存(邏輯上),物理上本地內存只是在堆內存中佔有一塊區域,每個線程只玩自己對應的threadLocals,各個線程的對應ThreadLocal互不干擾,這也就實現了各個線程間數據的隔離,也就是每個Handler所在線程都有其對應的Looper對象。
Thread類中 threadLocals 聲明
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
簡單來說就是數據複製很多份存放在堆內存,各個線程獲取自己對應的那份數據。
這個可以舉一個共享汽車的例子,假如剛開始共享汽車試運行,大街上只有一輛,大家都搶着去開,這就會出現問題,而後來發展普及,每輛車複製迅速生產,滿大街都是共享汽車,每個人都可以通過專屬二維碼開對應共享汽車,這裏開車人就對應線程,大家互不干擾,共享汽車就對應ThreadLocals,而大街就相當於堆內存。
ThreadLocalMap
- ThreadLocal中真正存放數據的是ThreadLocalMap,他的內部實現是一個環形數組來存放數據,具體分析可以查看以下文章,這裏就不在進行展開了。
- ThreadLocal源碼解讀
MessageQueue消息隊列工作原理
- MessageQueue字面意思是消息隊列,而他的實現則不是消息隊列,它的內部實現數據結構爲單鏈表,單鏈表在頻繁插入刪除方面是有優勢的,鏈表的插入刪除操作對應消息的存儲和取出,方法分別對應enqueueMessage和next方法。
存放消息enqueueMessage
- 查看Handler的源碼,很容易發現發消息的方法最終都是調用了sendMessageAtTime方法,uptimeMillis爲系統開機時間加上設置消息的延時時間,Handler的enqueueMessage方法將Message的Target設爲當前Handler,存放消息則調用了MessageQueue的enqueueMessage方法。
/** Handler的sendMessageAtTime方法*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
......
return enqueueMessage(queue, msg, uptimeMillis);
}
/** Handler的enqueueMessage方法*/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
.......
return queue.enqueueMessage(msg, uptimeMillis);
}
- 接着存放消息看到MessageQueue的enqueueMessage方法
/** MessageQueue的enqueueMessage方法*/
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 通過以上源碼,enqueueMessage邏輯主要爲單鏈表的插入操作,如果鏈表中沒有消息,或者當前存入消息延時爲零,又或者當前存入消息延時小於鏈表P節點的延時,則將當前消息插入到鏈表的頭節點,否則遍歷鏈表中的每個節點,找延時小於當前消息的節點存入消息。話句話說,單鏈表裏面消息是按Message的觸發時間順序排序的。
取消息 next
- 接着看MessageQueue取消息的方法next
Message next() {
......
//省略部分代碼
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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 {
// Got a message.
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;
}
//省略部分代碼
}
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
- 通過以上代碼,nextPollTimeoutMillis字段是關鍵,它代表next在獲取下一個消息時需要等待的時長,他的取值有三種情況:
- 當nextPollTimeoutMillis小於零,表示消息隊列中無消息,會一直等待下去
- 當nextPollTimeoutMillis等於零,則不會等待,直接出了取出消息
- 當nextPollTimeoutMillis大於零,則等待nextPollTimeoutMillis值的時間,單位是毫秒
- 通過對nextPollTimeoutMillis的瞭解,next方法是如何等待呢?換個詞可能更準確,應該叫阻塞,這裏注意到next方法循環中的nativePollOnce(ptr, nextPollTimeoutMillis)方法,它的實現在native層,可以實現阻塞的功能,具體原理是使用epoll,它是一種linux的I/O事件通知機制,I/O輸入輸出對象使用的是管道(pipe),具體native層分析請看Gityuan大佬的分析文章Android消息機制2-Handler(Native層)
private native static void nativeWake(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
到此,next方法的邏輯就很清晰了,開始nextPollTimeoutMillis的值是等於零的,獲取消息過程就不會受到nativePollOnce方法的阻塞,然後判斷取出的消息是否延時,有延時則計算nextPollTimeoutMillis進入下一循環進入nativePollOnce方法阻塞,否則返回取出的消息,有阻塞肯定就有喚醒,這個喚醒的方法就是nativeWake(long ptr)方法,它的實現也在native層,它的調用在我們前面分析enqueueMessage方法邏輯有出現,當有消息進入消息隊列,如果當前線程正在被阻塞,調用nativeWake方法,nativePollOnce就會立即返回,取消阻塞,這樣循環取到沒有延時的消息,則直接返回消息;如果沒有消息,nextPollTimeoutMillis等於 -1,繼續阻塞狀態。
經過前面的分析,消息插入鏈表是sendMessageAtTime方法觸發的,而接下來就會有一個疑問,那又是誰調用 next() 方法取消息呢?沒錯,就是接下來要了解的Looper
Looper 工作原理
- Looper在Android消息機制中是消息輪詢器的作用,他會不斷到MessageQueue中去取消息,取消息根據前面next 方法分析,如果阻塞,則說明沒有消息
- 先看Looper源碼註釋中有一段示例代碼
/* This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper. */
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
- 由example代碼所示,使用 Handler 之前調用了Looper.prepare(),如下代碼所示,就是在ThreadLocal中存放當前線程的Looper對象,在Looper構造方法中創建了MessageQueue
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 接着創建完Handler之後,又調用Looper.loop()方法,如下
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//省略部分代碼.....
for (;;) {
Message msg = queue.next(); // might block
//省略部分代碼.....
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
//省略部分代碼。。。。
}
//省略部分代碼。。。。
}
}
- 首先看到第一行myLooper(),前面在分析ThreadLocal已經瞭解過,myLooper就是獲取ThreadLocal獲取我們存儲的Looper對象,如果獲取不到就會報異常,提示我們我們沒有調用Looper.prepare(),這也就是子線程使用Handler必須調用Looper.prepare()的原因。是不是有恍然大悟的感覺。然後就是就是根據構造方法創建的MessageQueue來獲取消息queue.next(),該方法經過前面分析在沒有消息或者消息延時時間還沒到是阻塞的;獲取到消息後,根據msg.target.dispatchMessage(msg)調用的便是Handler的dispatchMessage方法(前文分析中msg.target的值爲當前Handler)。
主線程Looper.prepare()
- 經過前面的分析,你也許會有一個疑問,在Android使用Handler怎麼不用調用Looper.prepare()方法?
- 解下來我們看到Android的主線程ActivityThread的main方法,嚴格來說,ActivityThread並不是線程類,但是Android主線程肯定是存在的,只是主線程在ActivityThread的 main 方法中創建,並在該方法調用了Looper.prepareMainLooper() 方法和Looper.loop() 方法,所以我們在Android 主線程就可以直接使用Handler
/**ActivityThread 的 main 方法*/
public static void main(String[] args) {
//省略部分代碼....
Looper.prepareMainLooper();
//省略部分代碼....
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//省略部分代碼....
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
}
//省略部分代碼....
}
/**Looper 的 prepareMainLooper 方法*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
Handler 工作原理
- 前面已經瞭解過Handler發送消息的sendMessageAtTime方法,接着我們來看看Handler的dispatchMessage方法
/**
* 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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
- 這裏邏輯就很簡單了,如果發送的消息設置了Runnable類型的callback對象,則調用他的run方法,沒有則判斷是否設置了Handler.Callback,設置則調用Handler.Callback接口的handleMessage方法,否則調用Handler空實現方法handleMessage。
Looper.loop()死循環,爲什麼不會導致主線程發生ANR?
- 根據前面的分析,Looper.loop()的方法獲取不到數據,則會阻塞,這個阻塞和卡死是兩回事,阻塞是Linux pipe/epoll機制文件讀寫的等待,等待及休眠,則會釋放佔用CPU的資源,而我們開發遇見的卡死一般都是在主線程做了太多耗時操作,Activity 5s,BroadcastReceiver 10s和Service 20s未響應引起的ANR,具體背後分析還請看Gityuan的知乎解答Android中爲什麼主線程不會因爲Looper.loop()裏的死循環卡死?
參考
書籍
- 《Android開發藝術探索》