導航:首頁 > 操作系統 > android的message機制

android的message機制

發布時間:2023-09-25 22:47:58

『壹』 深入分析android-Handler消息機制

Handler是Android消息機制的上層介面。通過它可以輕松地將一個任務切換到Handler所在的線程中去執行。通常情況下,Handler的使用場景就是 更新UI 。

在子線程中,進行耗時操作,執行完操作後,發送消息,通知主線程更新UI。

Handler消息機制主要包括: MessageQueue 、 Handler 、 Looper 這三大部分,以及 Message 。

從上面的類圖可以看出:

MessageQueue、Handler和Looper三者之間的關系: 每個線程中只能存在一個Looper,Looper是保存在ThreadLocal中的。 主線程(UI線程)已經創建了一個Looper,所以在主線程中不需要再創建Looper,但是在其他線程中需要創建Looper。 每個線程中可以有多個Handler,即一個Looper可以處理來自多個Handler的消息。 Looper中維護一個MessageQueue,來維護消息隊列,消息隊列中的Message可以來自不同的Handler。

在子線程執行完耗時操作,當Handler發送消息時,將會調用 MessageQueue.enqueueMessage ,向消息隊列中添加消息。 當通過 Looper.loop 開啟循環後,會不斷地從消息池中讀取消息,即調用 MessageQueue.next , 然後調用目標Handler(即發送該消息的Handler)的 dispatchMessage 方法傳遞消息, 然後返回到Handler所在線程,目標Handler收到消息,調用 handleMessage 方法,接收消息,處理消息。

從上面可以看出,在子線程中創建Handler之前,要調用 Looper.prepare() 方法,Handler創建後,還要調用 Looper.loop() 方法。而前面我們在主線程創建Handler卻不要這兩個步驟,因為系統幫我們做了。

初始化Looper

從上可以看出,不能重復創建Looper,每個線程只能創建一個。創建Looper,並保存在 ThreadLocal 。其中ThreadLocal是線程本地存儲區(Thread Local Storage,簡稱TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。

開啟Looper

創建Handler

發送消息

post方法:

send方法:

『貳』 Android消息隊列淺析

 當面試官問到你消息對列的時候,恭喜你,已經跨過初級,在試探你的中級水平了。

Android的消息循環是參考Windows的消息循環機制來實現的。

消息隊列4件套   Message、MessageQueue、Looper、Handler

1、Message 是消息對列的消息實體類,因為消息隊列中會存放最多10個Message對象。常用屬性 what,是消息體的Tag,用來區分是那個一消息體。

2、 MessageQueue  先進先出」的原則存放消息,將Message對象以鏈表的方式串聯起來。

3、Looper 是MessageQueue的管理者,主線程中是一對一的關系。子線程需要用到消息對列的話就需要經典二人組 。先調用 Looper.prepare()方法,然後再調用Looper.loop();

4、Handler 是封裝和處理Message對象的。

通過源碼可知消息走向如下

handler.sendMessage()-->handler.sendMessageDelayed()-->handler.sendMessageAtTime()-->msg.target = this;queue.enqueueMessage==>把msg添加到消息隊列中

『叄』 詳解Android消息機制之Message

在分析Message這個類之前,有必要先看看它的類注釋其中有這么一段話:

從這段話得知,盡管Message本身的構造方式是公共的,但實現Message對象的最好方法確實是通過Message.obtain()函數返回,或者通過Handler.obtainMessage()方法,查看其最終還是調用了obtain函數。

如果使用new來實現我們初步的推測,應該是會構建大量的Message對象,對內存有一定的影響。
在這還是先看一下谷歌給這個函數的注釋:

從obtain函數的注釋中也能看出其作用就是用避免大量的構建Message對象,但它是究竟是如何處理的呢?帶著疑問查看obtain函數:

實現很簡單:

但看到這里還是很模糊,雖然sPool看上去像一個消息池,但再仔細看居然是一個Message對象,這樣真的就能避免多次構建Message對象嗎?繼續看會發現一個next欄位,再看它的注釋 sometimes we store linked lists of these things ,Message的消息池原來是一個鏈表,如下圖所示 。

每一個Message 對象通過next指向下一個Message(最後一個Message的next為null)形成一個鏈表,Message對象就成了一個可用的Message池。

到這終於知道Message對象原來是從鏈表中獲取的,但還有一個疑問:Message對象是什麼時候放入鏈表中的呢?從obtain函數並沒有看見存儲Message的操作。這時候又要回到文章開頭的那段類注釋的最後一句話: which will pull them from a pool of recycled objects。
消息池是一些回收的對象,也就是說Message對象是在回收的時候將其添加到鏈表中的。通過查看在Message中有個recycle方法:

在recycleUnchecked函數中會先清空該消息的各個欄位,並且把flags設置為FLGA_IN_USE,表明該消息已經被使用了。然後判斷是否要將消息回收到消息池中,如果池的大小小於MAX_POOL_SIZE,就將自身添加到鏈表的表頭,sPoolSize++。
例如最開始的開始的時候鏈表中沒有任何消息,將第一個Message對象添加到表中,此時的sPool為空,因此next也為空,sPool又指向this,這時sPool就指向當前這個被回收的Message對象,sPoolSize加1。我們把這個Message命名為m1,這時的鏈表應該如下:

如果再次插入一個名為m2的Message,那麼m2將被插入表頭,sPool指向m2,這時sPool的鏈表中結構如下:

對象池默認的大小為50,如果池的大小小於50,被回收的消息將會被插入到鏈表頭部。

如果池中有元素,這時候再調用obtain函數時,實際上是就獲取鏈表中表頭的元素,也就是sPool。再把sPool指針往後移動一個。在obtain漢中,首先會聲明一個Message對象m,並且讓m指向sPool.sPool實際上指向了m2,因此m實際上指向的也是m2,這里相當於保持了m2這個元素。下一步是sPool指向m2的下一個元素,也就是m1。sPool也完成後移之後此時把m.next置空,也就相當於m2.next變成了null。最後就是m指向了m2元素,m2的next為空,sPool從原來的表頭m2指向了下一個元素m1,最後將對象的元素減1,這樣m2就順利的脫離了消息池隊伍,就返回給了調用obtain函數的。

『肆』 Android 系統運行機制 【Looper】【Choreographer】篇

目錄:
1 MessageQueue next()
2 Vsync
3 Choreographer doFrame
4 input

系統是一個無限循環的模型, Android也不例外,進程被創建後就陷入了無限循環的狀態

系統運行最重要的兩個概念:輸入,輸出。

Android 中輸入 輸出 的往復循環都是在 looper 中消息機制驅動下完成的

looper 的循環中, messageQueue next 取消息進行處理, 處理輸入事件, 進行輸出, 完成和用戶交互

應用生命周期內會不斷 產生 message 到 messageQueue 中, 有: java層 也有 native層

其中最核心的方法就是 messageQueue 的 next 方法, 其中會先處理 java 層消息, 當 java 層沒有消息時候, 會執行 nativePollOnce 來處理 native 的消息 以及監聽 fd 各種事件

從硬體來看, 屏幕不會一直刷新, 屏幕的刷新只需要符合人眼的視覺停留機制

24Hz , 連續刷新每一幀, 人眼就會認為畫面是流暢的

所以我們只需要配合上這個頻率, 在需要更新 UI 的時候執行繪制操作

如何以這個頻率進行繪制每一幀: Android 的方案是 Vsync 信號驅動。

Vsync 信號的頻率就是 24Hz , 也就是每隔 16.6667 ms 發送一次 Vsync 信號提示系統合成一幀。

監聽屏幕刷新來發送 Vsync 信號的能力,應用層 是做不到的, 系統是通過 jni 回調到 Choreographer 中的 Vsync 監聽, 將這個重要信號從 native 傳遞到 java 層。

總體來說 輸入事件獲取 Vsync信號獲取 都是先由 native 捕獲事件 然後 jni 到 java 層實現業務邏輯

執行的是 messageQueue 中的關鍵方法: next

next 主要的邏輯分為: java 部分 和 native 部分

java 上主要是取java層的 messageQueue msg 執行, 無 msg 就 idleHandler

java層 無 msg 會執行 native 的 pollOnce@Looper

native looper 中 fd 監聽封裝為 requestQueue, epoll_wait 將 fd 中的事件和對應 request 封裝為 response 處理, 處理的時候會調用 fd 對應的 callback 的 handleEvent

native 層 pollOnce 主要做的事情是:

vsync 信號,輸入事件, 都是通過這樣的機制完成的。

epoll_wait 機制 拿到的 event , 都在 response pollOnce pollInner 處理了

這里的 dispatchVsync 從 native 回到 java 層

native:

java:

收到 Vsync 信號後, Choreographer 執行 doFrame

應用層重要的工作幾乎都在 doFrame 中

首先看下 doFrame 執行了什麼:

UI 線程的核心工作就在這幾個方法中:

上述執行 callback 的過程就對應了圖片中 依次處理 input animation traversal 這幾個關鍵過程

執行的周期是 16.6ms, 實際可能因為一些 delay 造成一些延遲、丟幀

input 事件的整體邏輯和 vsync 類似

native handleEvent ,在 NativeInputEventReceiver 中處理事件, 區分不同事件會通過 JNI

走到 java 層,WindowInputEventReceiver 然後進行分發消費

native :

java:

input事件的處理流程:

輸入event deliverInputEvent

deliver的 input 事件會來到 InputStage

InputStage 是一個責任鏈, 會分發消費這些 InputEvent

下面以滑動一下 recyclerView 為例子, 整體邏輯如下:

vsync 信號到來, 執行 doFrame,執行到 input 階段

touchEvent 消費, recyclerView layout 一些 ViewHolder

scroll 中 fill 結束,會執行 一個 recyclerView viewProperty 變化, 觸發了invalidate

invalidate 會走硬體加速, 一直到達 ViewRootImpl , 從而將 Traversal 的 callback post choreographer執行到 traversal 階段就會執行

ViewRootImpl 執行 performTraversal , 會根據目前是否需要重新layout , 然後執行layout, draw 等流程

整個 input 到 traversal 結束,硬體繪制後, sync 任務到 GPU , 然後合成一幀。

交給 SurfaceFlinger 來顯示。

SurfaceFlinger 是系統進程, 每一個應用進程是一個 client 端, 通過 IPC 機制,client 將圖像顯示工作交給 SurfaceFlinger

launch 一個 app:

『伍』 消息機制

Android的消息機制是指Handler的運行機制以及Handler所附帶的MessageQueue和Looper的工作過程。Handler的主要作用是將一個任務切換到某個指定的線程中去執行。
Android規定訪問UI只能在主線程中進行,如果在子線程中訪問UI,那麼程序就會拋出異常。

主線程即UI線程,它就是ActivityThread,ActivityThread被創建時就會初始化Looper,這也是主線程中默認可以使用Handler的原因。

1.Handler創建時會採用當前線程的Looper來構建內部的消息循環系統,Handler通過ThreadLocal來獲取當前線程的Looper,ThreadLocal作用是可以在每個線程中存儲數據;
2.Handler創建完畢後,這個時候其內部的Looper以及MessageQueue就可以和Handler一起協同工作了,通過Handler的post方法將一個Runnable投遞到Handler內部的Looper中去處理,也可以通過Handler的send方法發送一個消息,這個消息同樣會在Looper中處理;
3.當Handler的send方法被調用時,它會調用MessageQueue的enqueueMessage方法將這個消息放入消息隊列,Looper發現有新消息到來時,就會處理這個消息,最終消息中的Runnable或者Handler的handleMessage方法就會被調用。
4.Looper是運行在創建Handler所在的線程中,這樣Handler中的業務邏輯會被切換到創建Handler所在的線程中去執行了。

ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲之後,只有在指定線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據。
使用場景:android源碼中會使用如Looper,ActivityThread,AMS中都手寬消用到了ThreadLocal還有就是復雜邏輯下的對象傳遞,比畢知如監聽器的傳遞。

MessageQueue主要包含兩個操作,插入和讀取分別對應enqueueMessage
和next,enqueueMessage的作用是往消息隊列中插入一條消息,而next的作用是從消息隊列中取出一條消息並將其從消息隊列中移除。它是通過一個單鏈表的數據結構來維護消息列表。

消息循環,它會不停地從MessageQueue中查看是否有新消息,如果有新消息就會立刻處理,否則就一直阻塞在那裡。
通過Looper.prepare()即可以為當前線程創建一個Looper,接著通過Looper.loop()來開啟消息循環。

Looper除了prepare方法外,還提供了prepareMainLooper方法,這個方法主要是給主線程也就是ActivityThread創建Looper使用的,其本質也是通過prepare方法來實現的。Looper提供了getMainLooper方法,通過它可以在任何地方獲取到主線程的Looper,Looper也可以退出,提供了quit和quitSafely方法。
Looper的loop方法是一個死循環,唯一跳出循環的方式是MessageQueue的next方法返回了null;當Looper的quit方法被調用時,Looper就會調用MessageQueue的quit或者quitSafely方法來通知消息隊列退出,當消息隊列被標記為退出狀態時,它的next方法就會返回null。
loop方法會調用MessageQueue的next方法來獲取新消息,而next是一個阻塞操作,當沒有消息時,next方法會一直阻塞在那,這也導致了loop方法一直阻塞在那。

Android的主線程就是ActivityThread,主線程的入口方法為main,在main方法中系統會通過Looper.prepareMainLooper()來創建主線程的Looper以及MessageQueue,並通過Looper.loop()來開啟主線程的消息循環。
主線程的消巧埋息循環開始了以後,ActivityThread還需要一個Handler來和消息隊列進行交互,這個Handler就是ActivityThread.H,它內部定義了一組消息類型,主要包括了四大組件的啟動和停止過程。
ActivityThread通過ApplicationThread和AMS進行進程間通信,AMS以進程間通信的方式完成ActivityThread的請求後會回調ApplicationThread中的Binder方法,然後ApplicationThread會向H發送
消息,H接收消息後會將ApplicationThread中的邏輯切換到ActivityThread中去執行,即切換到主線程中去執行,這個過程就是主線程的消息循環模型。

在子線程執行完耗時操作,Handler通過sendMessage發送消息後,會調用MessageQueue.enqueueMessage方法向消息隊列中添加消息,然後Looper調用loop()方法開啟循環後會不斷地從消息隊列中讀取消息,然後調用目標Handler的dispatchMessage方法傳遞消息,然後回到Handler所在線程,目標Handler收到消息,調用handleMessage方法,接收消息,處理消息。

每個線程中只能存在一個Looper,Looper是保存在ThreadLocal中的。主線程(UI線程)已經創建了一個Looper,所以在主線程中不需要再創建Looper,但是在其他線程中需要創建Looper。每個線程中可以有多個Handler,即一個Looper可以處理來自多個Handler的消息。 Looper中維護一個MessageQueue,來維護消息隊列,消息隊列中的Message可以來自不同的Handler。

閱讀全文

與android的message機制相關的資料

熱點內容
抖音小店app如何聯系客服 瀏覽:973
linux打開多個終端 瀏覽:500
linux中的命令 瀏覽:573
優化小米6相機演算法 瀏覽:256
ak47pdf 瀏覽:141
人與文化pdf 瀏覽:640
原子幣app在哪裡注冊的 瀏覽:529
php數組保存文件 瀏覽:226
無理的命令 瀏覽:510
問道手游解壓失敗是什麼原因 瀏覽:776
mysql命令提示 瀏覽:373
apachephp中文亂碼 瀏覽:342
pythonimportpylab 瀏覽:238
阿里雲app伺服器價格表 瀏覽:981
appstore怎麼搶手機 瀏覽:845
列印伺服器是什麼列印隊列 瀏覽:359
網上怎麼用app辦理營業執照 瀏覽:861
sql如何查看伺服器地址 瀏覽:780
編譯速度和系統有關嗎 瀏覽:60
復盛製冷壓縮機 瀏覽:983