移動架構 (二) Android 中 Handler 架構分析,並實現本身簡易版本 Handler 框架

Android 中消息機制

Android 的消息機制主要指 Handler 的運行機制,先來看下 Handler 的一張運行架構圖來對 Handler 有個大概的瞭解。java

Handler 消息機制圖:git

Handler-.png

Handler 類圖:github

Handler.png

以上圖的解釋:面試

  1. 以 Handler 的 sendMessage () 函數爲例,當發送一個 message 後,會將此消息加入消息隊列 MessageQueue 中。
  2. Looper 負責去遍歷消息隊列而且將隊列中的消息分發非對應的 Handler 進行處理。
  3. 在 Handler 的 handlerMessage 方法中處理該消息,這就完成了一個消息的發送和處理過程。

這裏從圖中能夠看到 Android 中 Handler 消息機制最重要的四個對象分別爲 Handler 、Message 、MessageQueue 、Looper。數組

ThreadLocal 的工做原理

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-.jpg

從上圖能夠 ThreadLocal 主要函數組成部分,這裏咱們用到了 set , get 那麼就從 set , get 入手吧。

ThreadLocal set(T):

ThreadLocal-set.jpg

​ (圖 1)

ThreadLocal-getMap.jpg

​ (圖 2)

ThreadLocal-createMap.jpg

​ (圖 3)

ThreadLocal-ThreadLocalMap-createMap-set.jpg

​ (圖 四)

從 (圖一) 得知 set 函數裏面獲取了當前線程,這裏咱們主要看下 getMap(currentThread) 主要幹什麼了?

從 (圖二) 中咱們得知 getMap 主要是從當前線程拿到 ThreadLocalMap 這個實例對象,若是當前線程的 ThreadLocalMap 爲 NULL ,那麼就 createMap ,這裏的 ThreadLocalMap 能夠暫時理解爲一個集合對象就好了,它 (圖四) 底層是一個數組實現的添加數據。

ThreadLocal T get():

ThreadLocal-get.jpg

這裏的 get() 函數其實已經可以說明爲何在不一樣線程存儲的數據拿不到了。由於存儲是在當前線程存儲的,取數據也是在當前所在的線程取得,因此不可能拿到的。帶着問題咱們找到了答案。是否是有點小激動呀?(^▽^)

Android 消息機制源碼分析

這裏咱們就直接看源碼,一下是我看源碼的流程。

  1. 建立全局惟一的 Looper 對象和全局惟一 MessageQueue 消息對象。

    Handler--Looper-MessageQueue.png

  2. Activity 中建立 Handler。

    Handler-Activity-create.png

  3. Handler sendMessage 發送一個消息的走向。

    Handler-message-.png

  4. Handler 消息處理。

    Handler-06c719af736b41fb.png

消息阻塞和延時

阻塞和延時

Looper 的阻塞主要是靠 MessageQueue 來實現的,在 MessageQueue -> next() nativePollOnce(ptr, nextPollTimeoutMillis) 進行阻塞 , 在 MessageQueue -> enqueueMessage() -> nativeWake(mPtr) 進行喚醒。主要依賴 native 層的 looper epoll 進制進行的。

f3da65a44123337f1b5b586a02aad8eb.png

阻塞和延時,主要是 next() 的 nativePollOnce(ptr , nextPollTimeoutMillis) 調用 native 方法來操做管道,由 nextPollTimeoutMillis 決定是否須要阻塞 , nextPollTimeoutMilis 爲 0 的時候表示不阻塞 , 爲 -1 的時候表示一直阻塞直到被喚醒,其它時間表示延時。

喚醒

主要是指 enqueueMessage () @MessageQueue 進行喚醒。

Handler-.jpg

阻塞 -> 喚醒 消息切換

Handler-f6fe406dad09b444.jpg

總結

簡單的理解阻塞和喚醒就是在主線程的 MessageQueue 沒有消息時,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法裏面,此時主線程會釋放 CPU 資源進入休眠狀態,直到下一個消息到達或者有消息的時候才觸發,經過往 pipe 管道寫端寫入數據來喚醒主線程工做。

這裏採用的 epoll 機制,是一種 IO 多路複用機制,能夠同時監控多個描述符,當某個描述符就緒 (讀或寫就緒) , 則馬上通知相應程序進行讀或者寫操做,本質同步 I/O , 即讀寫是阻塞的。因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量的 CPU 資源。

延時入隊

Handler-c4d53e3afdc11095.jpg

主要指 enqueueMessage() 消息入隊列(Message 單鏈表),上圖代碼對 message 對象池從新排序,遵循規則 ( when 從小到大) 。

此處 for 死循環退出狀況分爲兩種

  1. p == null 表示對象池中已經運行到了最後一個,無須要再循環。
  2. 碰到下一個消息 when 小於前一個,立馬退出循環 (無論對象池中全部 message 是否遍歷完) 進行從新排序。

好了,到了這裏 Handler 源碼分析算是告一段落了,下面咱們來看下面試中容易被問起的問題。

常見問題分析

爲何不能在子線程中更新 UI ,根本緣由是什麼?

checkThread.jpg

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。

爲何主線程用 Looper 死循環不會引起 ANR 異常?

簡單來講就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往 pipe 管道寫入數據來喚醒主線程工做。這裏採用的是 epoll 機制,是一種 IO 多路複用機制。

爲何 Handler 構造方法裏面的 Looper 不是直接 new ?

若是在 Handler 構造方法裏面直接 new Looper(), 多是沒法保證 Looper 惟一,只有用 Looper.prepare() 才能保證惟一性,具體能夠看 prepare 方法。

MessageQueue 爲何要放在 Looper 私有構造方法初始化?

由於一個線程只綁定一個 Looper ,因此在 Looper 構造方法裏面初始化就能夠保證 mQueue 也是惟一的 Thread 對應一個 Looper 對應一個 mQueue。

Handler . post 的邏輯在哪一個線程執行的?是由 Looper 所在線程仍是 Handler 所在線程決定的?

由 Looper 所在線程決定的。邏輯是在 Looper.loop() 方法中,從 MessageQueue 中拿出 message ,而且執行其邏輯,這裏在 Looper 中執行的,所以有 Looper 所在線程決定。

MessageQueue.next() 會由於發現了延遲消息,而進行阻塞。那麼爲何後面加入的非延遲消息沒有被阻塞呢?

能夠參考 消息阻塞和延時 -> 喚醒

Handler 的 dispatchMessage () 分發消息的處理流程?

handlerMessage-.jpg

  1. 屬於 Runnable 接口。

  2. 經過下面代碼形式調用。

    private static Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return true;
            }
        });
    複製代碼
  3. 若是第一步,第二部都不知足直接走下面 handlerMessage 參考下面代碼實現方式

    private static Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
    複製代碼

也能夠經過 debug 方式來具體看 dispatchMessage 執行狀態。

實現本身的 Handler 簡單架構

主要實現測試代碼

Handler-34a4a4e9e149d8c4.jpg

代碼傳送陣

相關文章
相關標籤/搜索