從本篇文章開始將開始詳細的來分析一下Android中常用的handler機制:基礎篇可以看看這篇文章:https://www.jianshu.com/p/b4d745c7ff7a?tdsourcetag=s_pcqq_aiomsg
Android的消息機制主要是指handler的運行機制,
以上模型的解釋:
1、以handler的sendMessage方法爲例,當發送一個消息後,會將此消息加入消息隊列MessageQueue中。
2、Looper負責去遍歷消息隊列並且將隊列中的消息分發給對應的handler進行處理
3、在Handler的handMessage方法中處理該消息,這就完成了一個消息的發送和處理過程
這裏從圖中可以看到參與消息處理有四個對象,它們分別是 Handler, Message, MessageQueue,Looper。
ThreadLocal 的工作原理
ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有再指定線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據。
Android消息機制源碼分析
創建全局唯一Looper對象和全局唯一MessageQueue消息對象
Activity中創建Handler
消息發送
消息處理
消息阻塞和延時
Looper 的阻塞主要是靠 MessageQueue 來實現的,在next()@MessageQuese 進行阻塞,在 enqueueMessage()@MessageQueue 進行喚醒。主要依賴 native 層的 Looper 依靠 epoll 機制進行的。
阻塞和延時,主要是next()中nativePollOnce(ptr, nextPollTimeoutMillis)調用naive方法操作管道,由nextPollTimeoutMillis決定是否需要阻塞nextPollTimeoutMillis爲0的時候表示不阻塞,爲-1的時候表示一直阻塞直到被喚醒,其他時間表示延時。
喚醒
主要是指enqueueMessage()@MessageQueue 進行喚醒。
簡單理解阻塞和喚醒
就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裏,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。
這裏採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。
從阻塞到喚醒,消息切換
延時入隊
主要指enqueueMessage()消息入列是,上圖代碼對message對象池得重新排序,遵循規則(when從小到大)。
此處for死循環推出情況分兩種
第一種:p==null表示對象池中已經運行到了最後一個,無需再循環。
第二種:碰到下一個消息when小於前一個,立馬推出循環(不管對象池中所有message是否遍歷完),進行從新排序。
Native消息機制
參考文檔:
https://blog.csdn.net/chewbee/article/details/78108201#nativewake%E6%96%B9%E6%B3%95
常見問題分析
爲什麼不能在子線程中更新UI,根本原因是什麼?
mThread是UI線程,這裏會檢查當前線程是不是UI線程。那麼爲什麼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()方法中,從MsgQueue中拿出msg,並且執行其邏輯,這是在Looper中執行的,因此有Looper所在線程決定。
MessageQueue.next()會因爲發現了延遲消息,而進行阻塞。那麼爲什麼後面加入的非延遲消息沒有被阻塞呢?
見:喚醒
Handler的dispatchMessage()分發消息的處理流程?
Msg.callback 在mHandler1.post()中使用
mCallback在new Handler是通過接口回調
Post()和sendMessage()都是發送消息,加入消息隊列得方式也是一樣,區別在於處理消息得方式。通過跟蹤源碼,容易區分。