Android消息機制

從本篇文章開始將開始詳細的來分析一下Android中常用的handler機制:基礎篇可以看看這篇文章:https://www.jianshu.com/p/b4d745c7ff7a?tdsourcetag=s_pcqq_aiomsg

深入篇可以看看這篇文章:https://blog.csdn.net/qian520ao/article/details/78262289#1-looper-%E6%AD%BB%E5%BE%AA%E7%8E%AF%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%BA%94%E7%94%A8%E5%8D%A1%E6%AD%BB?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沒有消息時,便阻塞在loopqueue.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()都是發送消息,加入消息隊列得方式也是一樣,區別在於處理消息得方式。通過跟蹤源碼,容易區分。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章