Android 的消息機制主要指 Handler 的運行機制,先來看下 Handler 的一張運行架構圖來對 Handler 有個大概的瞭解。java
Handler 消息機制圖:
git
Handler 類圖:
github
以上圖的解釋:
面試
這裏從圖中能夠看到 Android 中 Handler 消息機制最重要的四個對象分別爲 Handler 、Message 、MessageQueue 、Looper。數組
ThreadLocal 是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據, 數據存儲之後,只有再指定線程中能夠獲取到存儲的數據,對於其它線程來講則是沒法獲取到存儲的對象。下面就是咱們驗證 ThreadLocal 存取是不是按照剛剛那樣所說。架構
子線程中存,子線程中取ide
// 代碼測試
new Thread("thread-1"){
@Override
public void run() {
ThreadLocal<String> mThread_A = new ThreadLocal();
mThread_A.set("thread-1");
System.out.println("mThread_A :"+mThread_A.get());
}
}.start();
//打印結果
mThread_A :thread-1
複製代碼
主線程中存,子線程取函數
//主線程中存,子線程取
final ThreadLocal<String> mThread_B = new ThreadLocal();
mThread_B.set("thread_B");
new Thread(){
@Override
public void run() {
System.out.println("mThread_B :"+mThread_B.get());
}
}.start();
//打印結果
mThread_B :null
複製代碼
主線程存,主線程取oop
//主線程存,主線程取
ThreadLocal<String> mThread_C = new ThreadLocal();
mThread_C.set("thread_C");
System.out.println("mThread_C :"+mThread_C.get());
//打印結果
mThread_C :thread_C
複製代碼
結果是否是跟上面咱們所說的答案同樣,那麼爲何會是這樣勒?如今咱們帶着問題去看下 ThreadLocal
源碼到底作了什麼?源碼分析
從上圖能夠 ThreadLocal 主要函數組成部分,這裏咱們用到了 set , get 那麼就從 set , get 入手吧。
ThreadLocal set(T):
(圖 1)
(圖 2)
(圖 3)
(圖 四)
從 (圖一) 得知 set 函數裏面獲取了當前線程,這裏咱們主要看下 getMap(currentThread) 主要幹什麼了?
從 (圖二) 中咱們得知 getMap 主要是從當前線程拿到 ThreadLocalMap 這個實例對象,若是當前線程的 ThreadLocalMap 爲 NULL ,那麼就 createMap ,這裏的 ThreadLocalMap 能夠暫時理解爲一個集合對象就好了,它 (圖四) 底層是一個數組實現的添加數據。
ThreadLocal T get():
這裏的 get() 函數其實已經可以說明爲何在不一樣線程存儲的數據拿不到了。由於存儲是在當前線程存儲的,取數據也是在當前所在的線程取得,因此不可能拿到的。帶着問題咱們找到了答案。是否是有點小激動呀?(^▽^)
這裏咱們就直接看源碼,一下是我看源碼的流程。
建立全局惟一的 Looper 對象和全局惟一 MessageQueue 消息對象。
Activity 中建立 Handler。
Handler sendMessage 發送一個消息的走向。
Handler 消息處理。
Looper 的阻塞主要是靠 MessageQueue 來實現的,在 MessageQueue -> next() nativePollOnce(ptr, nextPollTimeoutMillis) 進行阻塞 , 在 MessageQueue -> enqueueMessage() -> nativeWake(mPtr) 進行喚醒。主要依賴 native 層的 looper epoll 進制進行的。
阻塞和延時,主要是 next() 的 nativePollOnce(ptr , nextPollTimeoutMillis) 調用 native 方法來操做管道,由 nextPollTimeoutMillis 決定是否須要阻塞 , nextPollTimeoutMilis 爲 0 的時候表示不阻塞 , 爲 -1 的時候表示一直阻塞直到被喚醒,其它時間表示延時。
主要是指 enqueueMessage () @MessageQueue 進行喚醒。
簡單的理解阻塞和喚醒就是在主線程的 MessageQueue 沒有消息時,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法裏面,此時主線程會釋放 CPU 資源進入休眠狀態,直到下一個消息到達或者有消息的時候才觸發,經過往 pipe 管道寫端寫入數據來喚醒主線程工做。
這裏採用的 epoll 機制,是一種 IO 多路複用機制,能夠同時監控多個描述符,當某個描述符就緒 (讀或寫就緒) , 則馬上通知相應程序進行讀或者寫操做,本質同步 I/O , 即讀寫是阻塞的。因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量的 CPU 資源。
主要指 enqueueMessage() 消息入隊列(Message 單鏈表),上圖代碼對 message 對象池從新排序,遵循規則 ( when 從小到大) 。
此處 for 死循環退出狀況分爲兩種
好了,到了這裏 Handler 源碼分析算是告一段落了,下面咱們來看下面試中容易被問起的問題。
mThread 是主線程,這裏會檢查當前線程是不是主線程,那麼爲何沒有在 onCreate 裏面沒有進行這個檢查呢?這個問題緣由出如今 Activity 的生命週期中 , 在 onCreate 方法中, UI 處於建立過程,對用戶來講界面還不可見,直到 onStart 方法後界面可見了,再到 onResume 方法後頁面能夠交互,從某種程度來說, 在 onCreate 方法中不能算是更新 UI,只能說是配置 UI,或者是設置 UI 屬性。 這個時候不會調用到 ViewRootImpl.checkThread () , 由於 ViewRootImpl 沒有建立。 而在 onResume 方法後, ViewRootImpl 才被建立。 這個時候去交戶界面纔算是更新 UI。
setContentView 知識創建了 View 樹,並無進行渲染工做 (其實真正的渲染工做實在 onResume 以後)。也正是創建了 View 樹,所以咱們能夠經過 findViewById() 來獲取到 View 對象,可是因爲並無進行渲染視圖的工做,也就是沒有執行 ViewRootImpl.performTransversal。一樣 View 中也不會執行 onMeasure (), 若是在 onResume() 方法裏直接獲取 View.getHeight() / View.getWidth () 獲得的結果老是 0。
簡單來講就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往 pipe 管道寫入數據來喚醒主線程工做。這裏採用的是 epoll 機制,是一種 IO 多路複用機制。
若是在 Handler 構造方法裏面直接 new Looper(), 多是沒法保證 Looper 惟一,只有用 Looper.prepare() 才能保證惟一性,具體能夠看 prepare 方法。
由於一個線程只綁定一個 Looper ,因此在 Looper 構造方法裏面初始化就能夠保證 mQueue 也是惟一的 Thread 對應一個 Looper 對應一個 mQueue。
由 Looper 所在線程決定的。邏輯是在 Looper.loop() 方法中,從 MessageQueue 中拿出 message ,而且執行其邏輯,這裏在 Looper 中執行的,所以有 Looper 所在線程決定。
能夠參考 消息阻塞和延時 -> 喚醒
屬於 Runnable 接口。
經過下面代碼形式調用。
private static Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return true;
}
});
複製代碼
若是第一步,第二部都不知足直接走下面 handlerMessage 參考下面代碼實現方式
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
複製代碼
也能夠經過 debug 方式來具體看 dispatchMessage 執行狀態。
主要實現測試代碼