Android 消息循環(Handler/Looper/MessageQueue)原理總結

注:如下內容基於Android API Version 27(Android 8.1)Linux Kernel 3.18.0linux

概述

HandlerLooperMessageQueue組成了Android的消息循環系統。消息循環系統是Android App的神經中樞,不管是與AMS/WMS打交道,仍是UI繪製,亦或是手機輸入事件的派發都依賴於消息循環系統。android

Android的消息循環運行於底層,咱們在上層開發遇到的一系列的組件生命週期回調,好比ActivityonCreateViewonTouchEvent,都是從主線程的消息循環(Looper.loop通過層層調用過來的。咱們在這些生命週期裏填寫代碼最終造成了可使用的App,而這一切都是被底層的消息循環驅動着,所以咱們能夠說Android App是一種基於消息驅動模型的應用程序。git

MessageQueue

消息隊列,用於存儲和獲取消息,包括Java層消息、Native層消息和各類文件描述符(FD)就緒消息。github

Looper

消息循環,內部包含一個MessageQueue對象,經過一個無限循環的loop方法,持續從MessageQueue獲取並處理消息。socket

Handler

Java層幫助類,內部包含一個Looper對象,Handler經過Looper中的MessageQueue往Java層的消息隊列發送消息。Handler同時提供了消息處理的回調函數,Handler收到消息回調後再決定將消息派發到哪裏去。函數

消息的發送與處理

消息循環起始於Looperloop方法調用,此方法內部是一個無限的for循環,線程一旦進入loop就再也出不去了,除非主動退出。之後線程所作的事就三件:取消息、執行消息處理函數和等待,而線程的全部有意義的工做都發生在消息處理函數中。oop

Android的消息循環同時兼顧了Java層和Native層,Jave層和Native各自維護者本身的消息隊列,Native層同時還監控着一批FDFD就緒也是做爲一種消息進行處理的。消息循環首先嚐試消費Native層的消息(包括普通消息和FD消息),消費完後再消費Java層的消息,若是Java層沒有可供消費的消息了,線程就會在下一次消費Native時層阻塞在Native的epoll_wait調用上,epoll_wait用於等待其所監控的FD就緒,若是Java層發送了新消息或者Native層發送了新消息,再或者有FD就緒了,線程就會被喚醒,從而消息循環就會繼續處理消息,直到Java、FD和Native都沒有消息了線程就又再次陷入阻塞。 Java層的消息在Java層處理,Native層的消息在Native層處理,Java層的消息和Native層的消息的惟一聯繫是他們在同一個循環線程中處理,線程阻塞是發生在Native層,線程喚起是發生在Java層和Native層,線程阻塞的時長和當前是否有消息或者下一個消息的處理時間有關。佈局

Java層消息派發流程

Java應用程序經過Handler將消息發送給MessageQueue,消息按照時間從先到後進行排序放入鏈表中,若是消息要當即被處理,也就是說沒有延遲的消息,此時若是線程處於阻塞狀態,Java層經過jni調用Native層的接口喚醒線程,線程喚醒後會從Nativie的Looer.pollOnce返回到Java層的nativePollOnce,而後繼續處理Java層的消息,消息處理完後根據是否還有消息或者下一個消息等待處理的延遲時間來決定Native層下次是一直阻塞仍是使用一個超時時間進行阻塞。大體流程就是:阻塞-發消息-喚醒線程-處理消息-阻塞。post

Native層消息派發流程

Java層的MessageQueue對象持有一個Native層的MessageQueue對象,Native層的MessageQueue持有一個Native層的Looper對象,Java層的MessageQueue調用Native的nativePollOnce最終調到Native的Lopper.pollOnce。Native的Lopper建立時會將本身加入到Native的線程本地存儲中去(TLS),以便之後在線程執行的任意地方拿到Looper對象向Native消息隊列發送消息。.net

Native層的Looper使用eventfd進行線程的喚醒,eventfd是linux系統調用,經過eventfd建立一個FD,而後將這個FD和其餘真正要監控的FD加入到同一個epoll監控列表中,當須要喚起線程時向這個FD寫入一個字符,這樣epoll檢測到有FD就緒就會從阻塞中喚醒。當Java層須要喚醒Native層的epoll阻塞時只須要調用Native層的方法向event FD寫入一個字符,這樣epoll_wait就返回了。

Native層的Lopper.pollOnceepoll_wait返回後首先處理Native消息隊列中的消息,Native消息隊列的消息是經過Native Lopper.sendMessage函數添加的,每個消息都綁定了一個MessageHandler對象,處理消息就是調用MessageHandlerhandleMessage函數將消息對象自己傳給發送消息者。處理完普通消息後,Native Looper接着處理FD就緒的消息,每個FD消息在添加的時候(Looper.addFD)會關聯一個LooperCallback對象,Native Looper就是經過回調FD所綁定的LopperCallback來由使用方本身處理FD就緒的消息的。 處理完全部的這兩種消息後Native Looper就返回了,接着線程就走到了Java層的消息處理邏輯中去了。

線程局部存儲

咱們之因此在建立了Looper的線程中的任意位置能夠訪問這個Looper緣由是Looper對象是藉助ThreadLocal存儲的。ThreadLocal即線程局部存儲。Looper內部聲明瞭一個靜態的ThreadLocal對象,TheadLocal內部是以線程對象爲key,線程局部數據爲value存儲在一個類map的結構中的。只要線程調用Looper.prepare將當前建立的Looper對象存在ThreadLocal中,之後在線程執行的任意位置均可以調用Looper.myLooper將存儲的Looper對象拿出來,而且每一個線程對應各自的Looper對象。

關於線程局部存儲能夠參閱:Android 線程局部存儲ThreadLocal原理總結

Android消息循環在如下重要場景中都有應用

  • 子線程作完任務後經過消息循環轉到主線程去刷新UI。
  • AMSWMS對App進程的回調從binder線程轉到App主線程。
  • vsync信號的處理。ViewRootImpl刷新佈局基於vsync信號,而vsync信號是經過讀取socketpairFD得到的,將socketpairFD加入到Native的消息輪詢中能夠直接使vsync的回調發生在主線程,從而避免了一次線程切換。
  • 設備輸入事件(Input)一樣是經過將socketpairFD加入到Native的消息輪詢中從而實現了消息直接於主線程處理。

參考
blog.csdn.net/luoshengyan…
androidxref.com/8.1.0_r33/x…
androidxref.com/8.1.0_r33/x…
linux.die.net/man/2/event…
linux.die.net/man/2/socke…
chao-tic.github.io/blog/2018/1…

相關文章
相關標籤/搜索