㈠ android面試必問handler機制淺析
Handler是Android中的非同步消息處理機制。當發送一個消息之後,這個消息是進入一個消息隊列(MessageQueue),在消息隊列中通過Looper去循環的獲取隊列中的消息,然後將消息分派給對應的處理者進行處理。
Message:存儲需要處理操作的信息
MessageQueue:先進先出,存儲handler發送過來的消息
Looper:循環器,它是消息隊列和handler的通信媒介,1:循環的取出消息隊列中的消息;2:將取出的消息發送給對應的處理者
Handler:主線程和子線程的通信媒介,1:添加消息到消息隊列; 2:處理循環器分派過來的消息
在handler機制中,Looper.loop方法會不斷循環獲取Message, 其中的消息的獲取是通過調用MessageQueue的next()方法獲取的,而該方法會調用nativePollOnce()方法 ,這是一個native方法。底層的實現涉及到Linux pipe/epoll機制,nativePollOnce()被阻塞時,主線程會釋放CPU資源,進入休眠狀態. 直到下個消息到達或者有事務發生,會通過pipe管道寫端寫入數據來喚醒looper工作。
Android6.0及以前的版本使用管道與epoll來完成Looper的休眠與喚醒的
Android6.0及以後的版本使用eventfd與epoll來完成Looper的休眠與喚醒的
如果不處理的話,會阻塞線程,處理方案是調用Looper的quit()(清空所有的延遲和非延遲的消息)和quitSafely()(只清空延遲消息)衡沖; 這個方法會調用MessageQueue的quit()方法,清空所有的Message,並調用nativeWake()方法喚醒之前被阻塞的nativePollOnce(),使得方法next()方法中的for循環繼續執行,接下來發現Message為null後就會結束循環,Looper結束。如此便可以釋放內存和線程
同進程線程間內存共享,通過咐和殲handler通信,消息的內容是不需要從一個線程拷貝到另一個線程,因為兩個線程間可使用的內存是同一個區域。(注意:線程私有區域ThreadLocal)
管道的作用就是當一個線程准備好Message,並放入消息池,這時需要通知了一個線程B去處理這個消息。線程A向管道的寫端寫入數據,管道有數據便會喚醒線程B去處理消息。管道的作用是用於通知另一個線程的,這便是最核心的作用。
從內存角度,通信過程中binder涉及到一次內存拷貝,handler機制中的Message根本不需要拷貝,本身就是在同一片內存。
從CPU角度,為了Binder通信底層驅動還需要創建一個binder線程池,每次通信涉及binder線程的創建和內存的分配等比較浪費CPU資源
原因:handler發送的消息在當前handler的消息隊列中,如果此時activity被finish掉了,那麼消息隊列的消息依舊由handler進行處理,若此時handler申明為內存類(非靜態內部類),內部類持有外部類的實例引用,這樣在GC垃圾回收時發現Activity還有其他引用存在,因而就不會去回首這個Activity,進而棚物導致Activity泄漏。
方法:使用靜態內部類,並且使用WeakReference包裹外部類的對象。首先靜態內部類不持有外部類的引用,使用靜態的handler不會導致activity的泄漏,handler定義static的同時,還要用WeakReference包裹外部類的對象。
㈡ [Android]View的Handler機制
有一定開發經驗,大家應該對Handler的機制有非常深刻的了解,Handler在Android開發中被廣泛用於線程間通信。
近來遇到了一個問題,在view init的情況下使用view自身的handler會崩潰,但是直接使用postDelay卻指嫌能正常運行。
這里需要思考幾個問題
1.view的handler是從哪裡來的呢?
2.view的handler為何會崩潰?
3.View的post的運行機制是?
讀了這篇文章,你就能理解到這些問題
可以看到是handler是從mAttachInfo過來的.
在dispatchAttachedToWindow的時候來傳遞過來,而viewgroup中觸發這個傳遞操作。
這里最終會在ViewRootImpl中創建出來AttachInfo對象。
而handler是來自於自定義的ViewRootHandler,那就可以知道,整個Activity的View系統共用一個Handler是由ViewRootImpl創建,用於處理頁面視圖事件等處理。
dispatchAttachedToWindow是在View調用init初始化之後的,handler還沒有被初始化,所以就導致handler返回null對唯舉手象出外,答脊如果不判空就會崩潰了。
可以看到當mAttachInfo未初始化的時候,會先將runnable塞到一個隊列當中。
當view被綁定到窗口的時候,會將隊列和handler綁定,然後啟動隊列任務
㈢ Android的handler機制的原理
Android的handler機制的原理分為非同步通信准備,消息發送,消息循環,消息處理。
1、非同步通信准備
在主線程中創建處理器對象(Looper)、消息隊列對象(Message Queue)和Handler對象。
2、消息入隊
工作線程通過Handler發送消息(Message) 到消息隊列(Message Queue)中。
3、消息循環
消息出隊: Looper循環取出消息隊列(Message Queue) 中的的消息(Message)。
消息分發: Looper將取出的消息 (Message) 發送給創建該消息的處理者(Handler)。
4、消息處理
處理者(Handler) 接收處理器(Looper) 發送過來的消息(Message),根據消息(Message) 進行U操作。
handler的作用
handler是android線程之間的消息機制,主要的作用是將一個任務切換到指定的線程中去執行,(准確的說是切換到構成handler的looper所在的線程中去出處理)android系統中的一個例子就是主線程中的所有操作都是通過主線程中的handler去處理的。
Handler的運行需要底層的 messagequeue和 looper做支撐。
㈣ 消息機制
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源碼分析] - 非同步通信Handler機制
一、問題:在Android啟動後會在新進程里創建一個主線程,也叫UI線程( 非線程安全 )這個線程主要負責監聽屏幕點擊事件與界面繪制。當Application需要進行耗時操作如網路請求等,如直接在主線程進行容易發生ANR錯誤。所以會創建子線程來執行耗時任務,當子線程執行完畢需要通知UI線程並修改界面時,不可以直接在子線程修改UI,怎麼辦?
解決方法:Message Queue機制可以實現子線程與UI線程的通信。
該機制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable對象 發給Looper,由它把消息放入所屬線程的消息隊列中,然後Looper又會自動把消息隊列里的消息/Runnable對象 廣播 到所屬線程里的Handler,由Handler處理接收到的消息或Runnable對象。
1、Handler
每次創建Handler對象時,它會自動綁定到創建它的線程上。如果是主線程則默認包含一個Message Queue,否則需要自己創建一個消息隊列來存儲。
Handler是多個線程通信的信使。比如在線程A中創建AHandler,給它綁定一個ALooper,同時創建屬於A的消息隊列AMessageQueue。然後在線程B中使用AHandler發送消息給ALooper,ALooper會把消息存入到AMessageQueue,然後再把AMessageQueue廣播給A線程里的AHandler,它接收到消息會進行處理。從而實現通信。
2、Message Queue
在主線程里默認包含了一個消息隊列不需要手動創建。在子線程里,使用Looper.prepare()方法後,會先檢查子線程是否已有一個looper對象,如果有則無法創建,因為每個線程只能擁有一個消息隊列。沒有的話就為子線程創建一個消息隊列。
Handler類包含Looper指針和MessageQueue指針,而Looper里包含實際MessageQueue與當前線程指針。
下面分別就UI線程和worker線程講解handler創建過程:
首先,創建handler時,會自動檢查當前線程是否包含looper對象,如果包含,則將handler內的消息隊列指向looper內部的消息隊列,否則,拋出異常請求執行looper.prepare()方法。
- 在 UI線程 中,系統自動創建了Looper 對象,所以,直接new一個handler即可使用該機制;
- 在 worker線程 中,如果直接創建handler會拋出運行時異常-即通過查『線程-value』映射表發現當前線程無looper對象。所以需要先調用Looper.prepare()方法。在prepare方法里,利用ThreadLocal<Looper>對象為當前線程創建一個Looper(利用了一個Values類,即一個Map映射表,專為thread存儲value,此處為當前thread存儲一個looper對象)。然後繼續創建handler, 讓handler內部的消息隊列指向該looper的消息隊列(這個很重要,讓handler指向looper里的消息隊列,即二者共享同一個消息隊列,然後handler向這個消息隊列發送消息,looper從這個消息隊列獲取消息) 。然後looper循環消息隊列即可。當獲取到message消息,會找出message對象里的target,即原始發送handler,從而回調handler的handleMessage() 方法進行處理。
- handler與looper共享消息隊列 ,所以handler發送消息只要入列,looper直接取消息即可。
- 線程與looper映射表 :一個線程最多可以映射一個looper對象。通過查表可知當前線程是否包含looper,如果已經包含則不再創建新looper。
5、基於這樣的機制是怎樣實現線程隔離的,即在線程中通信呢。
核心在於 每一個線程擁有自己的handler、message queue、looper體系 。而 每個線程的Handler是公開 的。B線程可以調用A線程的handler發送消息到A的共享消息隊列去,然後A的looper會自動從共享消息隊列取出消息進行處理。反之一樣。
二、上面是基於子線程中利用主線程提供的Handler發送消息出去,然後主線程的Looper從消息隊列中獲取並處理。那麼還有另外兩種情況:
1、主線程發送消息到子線程中;
採用的方法和前面類似。要在子線程中實例化AHandler並設定處理消息的方法,同時由於子線程沒有消息隊列和Looper的輪詢,所以要加上Looper.prepare(),Looper.loop()分別創建消息隊列和開啟輪詢。然後在主線程中使用該AHandler去發送消息即可。
2、子線程A與子線程B之間的通信。
1、 Handler為什麼能夠實現不同線程的通信?核心點在哪?
不同線程之間,每個線程擁有自己的Handler、消息隊列和Looper。Handler是公共的,線程可以通過使用目標線程的Handler對象來發送消息,這個消息會自動發送到所屬線程的消息隊列中去,線程自帶的Looper對象會不斷循環從裡面取出消息並把消息發送給Handler,回調自身Handler的handlerMessage方法,從而實現了消息的線程間傳遞。
2、 Handler的核心是一種事件激活式(類似傳遞一個中斷)的還是主要是用於傳遞大量數據的?重點在Message的內容,偏向於數據傳輸還是事件傳輸。
目前的理解,它所依賴的是消息隊列,發送的自然是消息,即類似事件中斷。
0、 Android消息處理機制(Handler、Looper、MessageQueue與Message)
1、 Handler、Looper源碼閱讀
2、 Android非同步消息處理機制完全解析,帶你從源碼的角度徹底理解
謝謝!
wingjay
![](https://avatars0.githubusercontent.com/u/9619875?v=3&s=460)
㈥ Android多線程的四種方式:Handler、AsyncTask、ThreadPoolExector、IntentService
非同步通信機制,將工作線程中需更新UI的操作信息 傳遞到 UI主線程,從而實現 工作線程對UI的更新處理,最終實現非同步消息的處理。Handler不僅僅能將子線程的數據傳遞給主線程,它能實現任意兩個線程的數據傳遞。
(1)Message
Message 可以在線程之間傳遞消息。可以在它的內部攜帶少量數據,用於在不同線程之間進行數據交換。除了 what 欄位,還可以使用 arg1 和 arg2 來攜帶整型數據,使用 obj 來攜帶 Object 數據。
(2) Handler
Handler 作為處理中心,用於發送(sendMessage 系列方法)與處理消息(handleMessage 方法)。
(3) MessageQueue
MessageQueue 用於存放所有通過 Handler 發送的消息。這部分消息會一直存放在消息隊列中,直到被處理。每個線程中只會有一個 MessageQueue 對象
(4) Looper
Looper 用於管理 MessageQueue 隊列,Looper對象通過loop()方法開啟了一個死循環——for (;;){},不斷地從looper內的MessageQueue中取出Message,並傳遞到 Handler 的 handleMessage() 方法中。每個線程中只會有一個 Looper 對象。
AsyncTask 是一種輕量級的任務非同步類,可以在後檯子線程執行任務,且將執行進度及執行結果傳遞給 UI 線程。
(1)onPreExecute()
在 UI 線程上工作,在任務執行 doInBackground() 之前調用。此步驟通常用於設置任務,例如在用戶界面中顯示進度條。
(2)doInBackground(Params... params)
在子線程中工作,在 onPreExecute() 方法結束後執行,這一步被用於在後台執行長時間的任務,Params 參數通過 execute(Params) 方法被傳遞到此方法中。任務執行結束後,將結果傳遞給 onPostExecute(Result) 方法,同時我們可以通過 publishProgress(Progress) 方法,將執行進度發送給 onProgressUpdate(Progress) 方法。
(3)onProgressUpdate(Progress... values)
在 UI 線程上工作,會在 doInBackground() 中調用 publishProgress(Progress) 方法後執行,此方法用於在後台計算仍在執行時(也就是 doInBackgound() 還在執行時)將計算執行進度通過 UI 顯示出來。例如,可以通過動畫進度條或顯示文本欄位中的日誌,從而方便用戶知道後台任務執行的進度。
(4)onPostExecute(Result result)
在 UI 線程上工作,在任務執行完畢(即 doInBackground(Result) 執行完畢)並將執行結果傳過來的時候工作。
使用規則:
(1)AsyncTask 是個抽象類,所以要創建它的子類實現抽象方法
(1)AsyncTask 類必須是在 UI 線程中被載入,但在Android 4.1(API 16)開始,就能被自動載入完成。
(2)AsyncTask 類的實例對象必須在 UI 線程中被創建。
(3)execute() 方法必須是在 UI 線程中被調用。
(4)不要手動調用方法 onPreExecute()、onPostExecute()、doInBackground()、onProgressUpdate()
(5)任務只能執行一次(如果嘗試第二次執行,將拋出異常)。即一個AsyncTask對象只能調用一次execute()方法。
原理:
其源碼中原理還是 Thread 與 Handler 的實現,其包含 兩個線程池,一個 Handler,如下所示:
名稱類型作用
SERIAL_EXECUTOR線程池分發任務,串列分發,一次只分發一個任務
THREAD_POOL_EXECUTOR線程池執行任務,並行執行,執行的任務由 SERIAL_EXECUTOR 分發
InternalHandlerHandler負責子線程與主線程的溝通,通知主線程做 UI 工作
一方面減少了每個並行任務獨自建立線程的開銷,另一方面可以管理多個並發線程的公共資源,從而提高了多線程的效率。所以ThreadPoolExecutor比較適合一組任務的執行。Executors利用工廠模式對ThreadPoolExecutor進行了封裝。
Executors提供了四種創建ExecutorService的方法,他們的使用場景如下:
1. Executors.newFixedThreadPool()
創建一個定長的線程池,每提交一個任務就創建一個線程,直到達到池的最大長度,這時線程池會保持長度不再變化。
當線程處於空閑狀態時,它們並不會被回收,除非線程池被關閉。當所有的線程都處於活動狀態時,新任務都會處於等待狀態,直到有線程空閑出來。
只有核心線程並且不會被回收,能夠更加快速的響應外界的請求。
2. Executors.newCachedThreadPool()
創建一個可緩存的線程池,如果當前線程池的長度超過了處理的需要時,它可以靈活的回收空閑的線程,當需要增加時,它可以靈活的添加新的線程,而不會對池的長度作任何限制
線程數量不定的線程池,只有非核心線程,最大線程數為 Integer.MAX_VALUE。當線程池中的線程都處於活動狀態時,線程池會創建新的線程來處理新任務,否則利用空閑的線程來處理新任務。線程池中的空閑線程具有超時機制,為 60s。
任務隊列相當於一個空集合,導致任何任務都會立即被執行,適合執行大量耗時較少的任務。當整個線程池都處於限制狀態時,線程池中的線程都會超時而被停止。
3. Executors.newScheledThreadPool()
創建一個定長的線程池,而且支持定時的以及周期性的任務執行,類似於Timer。
非核心線程數沒有限制,並且非核心線程閑置的時候立即回收,主要用於執行定時任務和具有固定周期的重復任務。
4. Executors.newSingleThreadExecutor()
創建一個單線程化的executor,它只創建唯一的worker線程來執行任務
只有一個核心線程,保證所有的任務都在一個線程中順序執行,意義在於不需要處理線程同步的問題。
一般用於執行後台耗時任務,當任務執行完成會自動停止;同時由於它是一個服務,優先順序要遠遠高於線程,更不容易被系統殺死,因此比較適合執行一些高優先順序的後台任務。
使用步驟:創建IntentService的子類,重寫onHandleIntent方法,在onHandleIntent中執行耗時任務
原理:在源碼實現上,IntentService封裝了HandlerThread和Handler。onHandleIntent方法結束後會調用IntentService的stopSelf(int startId)方法嘗試停止服務。
IntentService的內部是通過消息的方式請求HandlerThread執行任務,HandlerThread內部又是一種使用Handler的Thread,這就意味著IntentService和Looper一樣是順序執行後台任務的
(HandlerThread:封裝了Handler + ThreadHandlerThread適合在有需要一個工作線程(非UI線程)+任務的等待隊列的形式,優點是不會有堵塞,減少了對性能的消耗,缺點是不能同時進行多個任務的處理,需要等待進行處理。處理效率低,可以當成一個輕量級的線程池來用)
㈦ Handler源碼分析
Handler對於Android開發者再熟悉不過了,也是面試題的常客了,所以了解Handler機制的源碼就很有必要了,雖然Handler分析的文章已經有很多,但是自己總結一遍,印象才更深刻。
Handler機制,是Android中的一種消息傳遞機制,在開發中十分常用。由於Android從3.0開始不允許耗時操作在主線程中執行,必須在子線程中執行完後,將結果發送到主線程中更新UI。所以簡單來講Handler就是子線程和主線程通信的一種技術。
先是常規使用,Handler在主線程中創建,開啟子線程處理耗時操作,再通過Handler發送消息到主線程,Handler的handleMessage()方法就會被回調,再更新UI。
以及也很常用的,post()和postDelayed()。
還有一種場景,就是子線程中創建Handler,讓子線程成為輪訓的線程,接收其他線程的消息,開發中並不多,但是特定場景會很有用,例如有一個一直執行的子線程,一直定時掃描著當前位置信息,到了指定范圍,發送一個播放語音的消息的消息到主線程。
接下來就是Handler源碼分析了:
一般我們獲取Message會調用Handler的obtainMessage()方法,這個方法是獲取一個復用的Message對象,內部採用享元模式復用Message對象,在Android中,View繪制,Activity生命周期,都是使用Handler發送Message實現,如果每次都new一個消息對象,肯定是十分消耗內存的,也容易產生GC垃圾回收導致卡頓。
我們平常在主線程使用Handler時,並沒有調用過Looper.prepare()和Looper.loop()這2個方法,為什麼創建Handler時不會拋出異常呢?
原因就是創建Handler時,調用Looper.myLooper()獲取主線程綁定的Looper不為空,所以沒有拋出異常。經過Looper類中查找發現,除了Looper.prepare()之外,還有一個prepareMainLooper()的方法。
prepareMainLooper()方法的注釋,意思大概就是,創建主線程的Looper對象,該方法由Android框架在主線程自動調用,我們不應該主動調用該方法。
那麼什麼時候會調用prepareMainLooper()方法呢,AndroidStudio點擊方法查找調用鏈,我們發現在ActivityThread中有調用。ActivityThread是Android程序的主線程,main方法則是啟動的方法,我們看到先是調用了Looper.prepareMainLooper(),初始化主線程的Looper。再調用了Looper.loop()開啟主線程輪訓。
㈧ 關於 Handler 的這 20 個問題,你都清楚嗎
Android 11 開始,AsyncTask 正式謝幕,變成了不推薦使用的 API。官方建議採用 Kotlin 協程替代,或者自行實現。
事實上,無論是 AsyncTask 還是協程,背後都有 Handler 的功勞。無論從普及原理的角度、還是從自行實現的角度,我們都需要吃透這個 Android 系統所特有的線程間通信方式Handler 機制!
初嘗 Handler 機制的時候,原以為 Handler 類發揮了很大的作用。當你深入了解它的原理之後,會發現 Handler 只是該機制的 調用入口和回調 而已,最重要的東西是 Looper 和 MessagQueue,以及不斷流轉的 Message。
本次針對 Handler 機制常被提及和容易困擾的 20 個問題進行整理和回答,供大家解惑和回顧~
簡述下 Handler 機制的總體原理?
Looper 存在哪?如何可以保證線程獨有?
如何理解 ThreadLocal 的作用?
主線程 Main Looper 和一般 Looper 的異同?
Handler 或者說 Looper 如何切換線程?
Looper 的 loop() 死循環為什麼不卡死?
Looper 的等待是如何能夠准確喚醒的?
Message 如何獲取?為什麼這么設計?
MessageQueue 如何管理 Message?
理解 Message 和 MessageQueue 的異同?
Message 的執行時刻如何管理?
Handler、Mesage 和 Runnable 的關系如何理解?
IdleHandler 空閑 Message 了解過嗎?有什麼用?
非同步 Message 或同步屏障了解過嗎?怎麼用?什麼原理?
Looper 和 MessageQueue、Message 及 Handler 的關系?
Native 側的 NativeMessageQueue 和 Looper 的作用是?
Native 側如何使用 Looper?
Handler 為什麼可能導致內存泄露?如何避免?
Handler 在系統當中的應用
Android 為什麼不允許並發訪問 UI?
1. 簡述下 Handler 機制的總體原理?
Looper 准備和開啟輪循:
尚無 Message 的話,調用 Native 側的 pollOnce() 進入 無限等待
存在 Message,但執行時間 when 尚未滿足的話,調用 pollOnce() 時傳入剩餘時長參數進入 有限等待
Looper#prepare() 初始化線程獨有的 Looper 以及 MessageQueue
Looper#loop() 開啟 死循環 讀取 MessageQueue 中下一個滿足執行時間的 Message
Message 發送、入隊和出隊:
Native 側如果處於無限等待的話:任意線程向 Handler 發送 Message 或 Runnable 後,Message 將按照 when 條件的先後,被插入 Handler 持有的 Looper 實例所對應的 MessageQueue 中 適當的位置 。MessageQueue 發現有合適的 Message 插入後將調用 Native 側的 wake() 喚醒無限等待的線程。這將促使 MessageQueue 的讀取繼續 進入下一次循環 ,此刻 Queue 中已有滿足條件的 Message 則出隊返回給 Looper
Native 側如果處於有限等待的話:在等待指定時長後 epoll_wait 將返回。線程繼續讀取 MessageQueue,此刻因為時長條件將滿足將其出隊
Looper 處理 Message 的實現:
Looper 得到 Message 後回調 Message 的 callback 屬性即 Runnable,或依據 target 屬性即 Handler,去執行 Handler 的回調。
存在 mCallback 屬性的話回調 Handler$Callback
反之,回調 handleMessage()
2. Looper 存在哪?如何可以保證線程獨有?
Looper 實例被管理在靜態屬性 sThreadLocal 中
ThreadLocal 內部通過 ThreadLocalMap 持有 Looper,key 為 ThreadLocal 實例本身,value 即為 Looper 實例
每個 Thread 都有一個自己的 ThreadLocalMap,這樣可以保證每個線程對應一個獨立的 Looper 實例,進而保證 myLooper() 可以獲得線程獨有的 Looper
彩蛋:一個 App 擁有幾個 Looper 實例?幾個 ThreadLocal 實例?幾個 MessageQueue 實例?幾個 Message 實例?幾個 Handler 實例
一個線程只有一個 Looper 實例
一個 Looper 實例只對應著一個 MessageQueue 實例
一個 MessageQueue 實例可對應多個 Message 實例,其從 Message 靜態池裡獲取,存在 50 的上限
一個線程可以擁有多個 Handler 實例,其Handler 只是發送和執行任務邏輯的入口和出口
ThreadLocal 實例是靜態的,整個進程 共用一個實例 。每個 Looper 存放的 ThreadLocalMap 均弱引用它作為 key
3. 如何理解 ThreadLocal 的作用?
首先要明確並非不是用來切換線程的, 只是為了讓每個線程方便程獲取自己的 Looper 實例 ,見 Looper#myLooper()
後續可供 Handler 初始化時 指定其所屬的 Looper 線程
也可用來線程判斷自己是否 是主線程
4. 主線程 Main Looper 和一般 Looper 的異同?
區別:
Main Looper 不可 quit
主線程需要不斷讀取系統消息和用書輸入,是進程的入口,只可被系統直接終止。進而其 Looper 在創建的時候設置了 不可quit的標志 ,而 其他線程的 Looper 則可以也必須手動 quit
Main Looper 實例還被 靜態緩存
為了便於每個線程獲得主線程 Looper 實例,見 Looper#getMainLooper(),Main Looper 實例還作為 sMainLooper 屬性緩存到了 Looper 類中。
相同點:
都是通過 Looper#prepare() 間接調用 Looper 構造函數創建的實例
都被靜態實例 ThreadLocal 管理,方便每個線程獲取自己的 Looper 實例
彩蛋:主線程為什麼不用初始化 Looper?
App 的入口並非 MainActivity,也不是 Application,而是 ActivityThread。
其為了 Application、ContentProvider、Activity 等組件的運行,必須事先啟動不停接受輸入的 Looper 機制,所以在 main() 執行的最後將調用 prepareMainLooper() 創建 Looper 並調用 loop() 輪循。
不需要我們調用,也不可能有我們調用。
可以說如果主線程沒有創建 Looper 的話,我們的組件也不可能運行得到!
5. Handler 或者說 Looper 如何切換線程?
Handler 創建的時候指定了其所屬線程的 Looper,進而持有了 Looper 獨有的 MessageQueue
Looper#loop() 會持續讀取 MessageQueue 中合適的 Message,沒有 Message 的時候進入等待
當向 Handler 發送 Message 或 Runnable 後,會向持有的 MessageQueue 中插入 Message
Message 抵達並滿足條件後會喚醒 MessageQueue 所屬的線程,並將 Message 返回給 Looper
Looper 接著回調 Message 所指向的 Handler Callback 或 Runnable,達到線程切換的目的
簡言之,向 Handler 發送 Message 其實是向 Handler 所屬線程的獨有 MessageQueue 插入 Message。而線程獨有的 Looper 又會持續讀取該 MessageQueue。所以向其他線程的 Handler 發送完 Message,該線程的 Looper 將自動響應。
6. Looper 的 loop() 死循環為什麼不卡死?
為了讓主線程持續處理用戶的輸入,loop() 是 死循環 ,持續調用 MessageQueue#next() 讀取合適的 Message。
但當沒有 Message 的時候,會調用 pollOnce() 並通過 Linux 的 epoll 機制進入等待並釋放資源。同時 eventFd 會監聽 Message 抵達的寫入事件並進行喚醒。
這樣可以 空閑時釋放資源、不卡死線程,同時能持續接收輸入的目的 。
彩蛋1:loop() 後的處理為什麼不可執行
因為 loop() 是死循環,直到 quit 前後面的處理無法得到執行,所以避免將處理放在 loop() 的後面。
彩蛋2:Looper 等待的時候線程到底是什麼狀態?
調用 Linux 的 epoll 機制進入 等待 ,事實上 java 側列印該線程的狀態,你會發現線程處於 Runnable 狀態,只不過 CPU 資源被暫時釋放。
7. Looper 的等待是如何能夠准確喚醒的?
讀取合適 Message 的 MessageQueue#next() 會因為 Message 尚無或執行條件尚未滿足進行兩種等的等待:
無限等待
尚無 Message(隊列中沒有 Message 或建立了同步屏障但尚無非同步 Message)的時候,調用 Natvie 側的 pollOnce() 會傳入參數 -1 。
Linux 執行 epoll_wait() 將進入無限等待,其等待合適的 Message 插入後調用 Native 側的 wake() 向喚醒 fd 寫入事件觸發喚醒 MessageQueue 讀取的下一次循環
有限等待
有限等待的場合將下一個 Message 剩餘時長作為參數 交給 epoll_wait(),epoll 將等待一段時間之後 自動返回 ,接著回到 MessageQueue 讀取的下一次循環
8. Message 如何獲取?為什麼這么設計?
享元設計模式:通過 Message 的靜態方法 obatin() 獲取,因為該方法不是無腦地 new,而是 從單鏈表池子里獲取實例 ,並在 recycle() 後將其放回池子
好處在於復用 Message 實例,滿足頻繁使用 Message 的場景,更加高效
當然,緩存池存在上限 50 ,因為沒必要無限制地緩存,這本身也是一種浪費
需要留意緩存池是靜態的,也就是整個進程共用一個緩存池
9. MessageQueue 如何管理 Message?
MessageQueue 通過單鏈表管理 Message,不同於進程共用的 Message Pool,其是線程獨有的
通過 Message 的執行時刻 when 對 Message 進行排隊和出隊
MessageQueue 除了管理 Message,還要管理空閑 Handler 和 同步屏障
10. 理解 Message 和 MessageQueue 的異同?
相同點:都是通過 單鏈表來管理 Message 實例;
Message 通過 obtain() 和 recycle() 向單鏈表獲取插入節點
MessageQueue 通過 enqueueMessage() 和 next() 向單鏈表獲取和插入節點
區別:
Message 單鏈表是 靜態的,供進程使用的緩存池
MessageQueue 單鏈表 非靜態,只供 Looper 線程使用
11. Message 的執行時刻如何管理?
發送的 Message 都是按照執行時刻 when 屬性的先後管理在 MessageQueue 里
延時 Message 的 when 等於調用的當前時刻和 delay 之和
非延時 Message 的 when 等於當前時刻(delay 為 0)
插隊 Message 的 when 固定為 0,便於插入隊列的 head
之後 MessageQueue 會根據 讀取的時刻和 when 進行比較
將 when 已抵達的出隊,
尚未抵達的計算出 當前時刻和目標 when 的插值 ,交由 Native 等待對應的時長,時間到了自動喚醒繼續進行 Message 的讀取
事實上,無論上述哪種 Message 都不能保證在其對應的 when 時刻執行,往往都會延遲一些!因為必須等當前執行的 Message 處理完了才有機會讀取隊列的下一個 Message。
比如發送了非延時 Message,when 即為發送的時刻,可它們不會立即執行。都要等主線程現有的任務(Message)走完才能有機會出隊,而當這些任務執行完 when 的時刻已經過了。假使隊列的前面還有其他 Message 的話,延遲會更加明顯!
彩蛋:. onCreate() 里向 Handler 發送大量 Message 會導致主線程卡頓嗎?
不會,發送的大量 Message 並非立即執行,只是先放到隊列當中而已。
onCreate() 以及之後同步調用的 onStart() 和 onResume() 處理,本質上也是 Message。等這個 Message 執行完之後,才會進行讀取 Message 的下一次循環,這時候才能回調 onCreate 里發送的 Message。
需要說明的是,如果發送的是 FrontOfQueue 將 Message 插入隊首也不會立即先執行,因為 onStart 和 onResume 是 onCreate 之後同步調用的,本質上是同一個 Message 的作業周期
12. Handler、Mesage 和 Runnable 的關系如何理解?
作為使用 Handler 機制的入口, Handler 是發送 Message 或 Runnable 的起點
發送的 Runnable 本質上也是 Message ,只不過作為 callback 屬性被持有
Handler 作為 target 屬性被持有在 Mesage 中 ,在 Message 執行條件滿足的時候供 Looper 回調
事實上,Handler 只是供 App 使用 Handler 機制的 API,實質來說,Message 是更為重要的載體。
13. IdleHandler 空閑 Message 了解過嗎?有什麼用?
適用於期望 空閑時候執行,但不影響主線程操作 的任務
系統應用:
Activity destroy 回調就放在了 IdleHandler 中
ActivityThread 中 GCHandler 使用了 IdleHandler,在空閑的時候執行 GC 操作
App 應用:
發送一個返回 true 的 IdleHandler,在裡面讓某個 View 不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個 View
將某部分初始化放在 IdleHandler 里不影響 Activity 的啟動
14. 非同步 Message 或同步屏障了解過嗎?怎麼用?什麼原理?
非同步 Message:設置了 isAsync 屬性的 Message 實例
可以用非同步 Handler 發送
也可以調用 Message#setAsynchronous() 直接設置為非同步 Message
同步屏障:在 MessageQueue 的 某個位置放一個 target 屬性為 null 的 Message ,確保此後的非非同步 Message 無法執行,只能執行非同步 Message
原理:當 MessageQueue 輪循 Message 時候 發現建立了同步屏障的時候,會去跳過其他 Message,讀取下個 async 的 Message 並執行,屏障移除之前同步 Message 都會被阻塞
應用:比如 屏幕刷新 Choreographer 就使用到了同步屏障 ,確保屏幕刷新事件不會因為隊列負荷影響屏幕及時刷新。
注意: 同步屏障的添加或移除 API 並未對外公開,App 需要使用的話需要依賴反射機制
15. Looper 和 MessageQueue、Message 及 Handler 的關系?
Message 是承載任務的載體,在 Handler 機制中貫穿始終
Handler 則是對外公開的 API,負責發送 Message 和處理任務的回調,是 Message 的生產者
MessagQueue 負責管理待處理 Message 的入隊和出隊,是 Message 的容器
Looper 負責輪循 MessageQueue,保持線程持續運行任務,是 Message 的消費者
彩蛋:如何保證 MessageQueue 並發訪問安全?
任何線程都可以通過 Handler 生產 Message 並放入 MessageQueue 中,可 Queue 所屬的 Looper 在持續地讀取並嘗試消費 Message。如何保證兩者不產生死鎖?
Looper 在消費 Message 之前要先拿到 MessageQueue 的鎖, 只不過沒有 Message 或 Message 尚未滿足條件的進行等待前會事先釋放鎖 ,具體在於 nativePollOnce() 的調用在 synchronized 方法塊的外側。
Message 入隊前也需先拿到 MessageQueue 的鎖,而這時 Looper 線程正在等待且不持有鎖,可以確保 Message 的成功入隊。入隊後執行喚醒後釋放鎖,Native 收到 event 寫入後恢復 MessagQueue 的讀取並可以拿到鎖,成功出隊。
這樣一種在沒有 Message 可以消費時執行等待同時不佔著鎖的機制,避免了生產和消費的死鎖。
16. Native 側的 NativeMessageQueue 和 Looper 的作用是?
NativeMessageQueue 負責連接 Java 側的 MessageQueue,進行後續的 wait 和 wake,後續將創建 wake 的FD,並通過 epoll 機制等待或喚醒。 但並不參與管理 Java 的 Message
Native 側也需要 Looper 機制,等待和喚醒的需求是同樣的,所以將這部分實現都封裝到了 JNI 的NativeMessageQueue 和 Native 的 Looper 中, 供 Java 和 Native 一起使用
17. Native 側如何使用 Looper?
Looper Native 部分承擔了 Java 側 Looper 的等待和喚醒,除此之外其還提供了 Message、MessageHandler 或 WeakMessageHandler、LooperCallback 或 SimpleLooperCallback 等 API
這些部分可供 Looper 被 Native 側直接調用,比如 InputFlinger 廣泛使用了 Looper
主要方法是調用 Looper 構造函數或 prepare 創建 Looper,然後通過 poll 開始輪詢,接著 sendMessage 或 addEventFd,等待 Looper 的喚醒。 使用過程和 Java 的調用思路類似
18. Handler 為什麼可能導致內存泄露?如何避免?
持有 Activity 實例的內名內部類或內部類的 生命周期 應當和 Activity 保持一致,否則產生內存泄露的風險。
如果 Handler 使用不當,將造成不一致,表現為:匿名內部類或內部類寫法的 Handler、Handler$Callback、Runnable,或者Activity 結束時仍有活躍的 Thread 線程或 Looper 子線程
具體在於:非同步任務仍然活躍或通過發送的 Message 尚未處理完畢,將使得內部類實例的 生命周期被錯誤地延長 。造成本該回收的 Activity 實例 被別的 Thread 或 Main Looper 占據而無法及時回收 (活躍的 Thread 或 靜態屬性 sMainLooper 是 GC Root 對象)
建議的做法:
無論是 Handler、Handler$Callback 還是 Runnable,盡量採用 靜態內部類 + 弱引用 的寫法,確保盡管發生不當引用的時候也可以因為弱引用能清楚持有關系
另外在 Activity 銷毀的時候及時地 終止 Thread、停止子線程的 Looper 或清空 Message ,確保徹底切斷 Activity 經由 Message 抵達 GC Root 的引用源頭(Message 清空後會其與 Handler 的引用關系,Thread 的終止將結束其 GC Root 的源頭)
注意:靜態的 sThreadLocal 實例不持有存放 Looper 實例的 ThreadLocalMap,而是由 Thread 持有。從這個角度上來講,Looper 會被活躍的 GC Root Thread 持有,進而也可能導致內存泄露。
彩蛋:網傳的 Handler$Callback 方案能否解決內存泄露?
不能。
Callback 採用內部類或匿名內部類寫法的話,默認持有 Activity 的引用,而 Callback 被 Handler 持有。這最終將導致 Message -> Handler -> Callback -> Activity 的鏈條仍然存在。
19. Handler 在系統當中的應用
特別廣泛,比如:
Activity 生命周期的管理
屏幕刷新
HandlerThread、IntentService
AsyncTask 等。
主要利用 Handler 的切換線程、主線程非同步 Message 的重要特性。注意:Binder 線程非主線程,但很多操作比如生命周期的管理都要回到主線程,所以很多 Binder 調用過來後都要通過 Handler 切換回主線程執行後續任務,比如 ActviityThread$H 就是 extends Handler。
20. Android 為什麼不允許並發訪問 UI?
Android 中 UI 非線程安全,並發訪問的話會造成數據和顯示錯亂。
但此限制的檢查始於ViewRootImpl#checkThread(),其會在刷新等多個訪問 UI 的時機被調用,去檢查當前線程,非主線程的話拋出異常。
而 ViewRootImpl 的創建在 onResume() 之後,也就是說如果在 onResume() 執行前啟動線程訪問 UI 的話是不會報錯的,這點需要留意!
彩蛋:onCreate() 里子線程更新 UI 有問題嗎?為什麼?
不會。
因為異常的檢測處理在 ViewRootImpl 中,該實例的創建和檢測在 onResume() 之後進行。
㈨ Android Handler那些事兒,消息屏障IdelHandlerANR
Handler 是Android SDK中用來處理非同步消息的核心類,子線程可以通過handler來通知主線程進行ui更新。
備註:本文源碼截圖 基於Android sdk 28
Handler機制 消息發送主要流程如圖
應用程序啟動後,zygote fork一個應用進程後,和普通java程序一樣,程序會首先執行ActivityThread中的main函數。在main函數中,程序首先會創建Looper對象並綁定到主線程中,然後開啟loop循環。(ps:主線程loop循環不能退出)
在prepareMainLooper方法中,最終會創建Looper,MessageQueue對象 以及創建native層MessageQueue對象。
使用Handler.sendMessageXXX或這 postDedayXXX發送消息後,最終會調用到SendMessageAtTime方法中。
然後調用MessageQueue.enqueueMessage將消息存到消息隊列中。
存入消息後,然後通過調用native方法 喚醒主線程進行消息處理。
當應用程序啟動,做完一些必要工作之後,便會開啟Loop循環,除非系統異常,否則該循環不會停止。loop循環中,主要做兩件事,第一,從消息隊列中取消息。第二,進行消息分發處理。
MessageQueue.next() 方法 通過調用 native方法 nativePollOnce(ptr, nextPollTimeoutMillis)實現無消息處理時,進入阻塞的功能。
當nextPollTimeoutMillis 值為0時,該方法會立刻返回;
當nextPollTimeoutMillis 值為-1時,該方法會無限阻塞,直到被喚醒;
當nextPollTimeoutMillis 值大於0時,該方法會將該值設置為超時時間,阻塞到達一定時間後,返回;
在loop循環中 ,通過調用 msg.target.dispatchMessage(msg) 進行消息的分發處理
使用當前線程的MessageQueue.addIdleHandler方法可以在消息隊列中添加一個IdelHandler。
當MessageQueue 阻塞時,即當前線程空閑時,會回調IdleHandler中的方法;
當IdelHandler介面返回false時,表示該IdelHandler只執行一次,
a,延遲執行
例如,當啟動Activity時,需要延時執行一些操作,以免啟動過慢,我們常常使用以下方式延遲執行任務,但是在延遲時間上卻不好控制。
其實,這時候使用IdelHandler 會更優雅
b,批量任務,任務密集,且只關注最終結果
例如,在開發一個IM類型的界面時,通常情況下,每次收到一個IM消息時,都會刷新一次界面,但是當短時間內, 收到多條消息時,就會刷新多次界面,容易造成卡頓,影響性能,此時就可以使用一個工作線程監聽IM消息,在通過添加IdelHandler的方式通知界面刷新,避免短時間內多次刷新界面情況的發生。
在Android的消息機制中,其實有三種消息: 普通消息、非同步消息及消息屏障。
消息屏障 也是一種消息,但是它的target為 null。可以通過MessageQueue中的postSyncBarrier方法發送一個消息屏障(該方法為私有,需要反射調用)。
在消息循環中,如果第一條消息就是屏障消息,就往後遍歷,看看有沒有非同步消息:
如果沒有,則無限休眠,等待被喚醒
如果有,就看離這個消息被觸發時間還有多久,設置一個超時時間,繼續休眠
非同步消息 和普通消息一樣,只不過它被設置setAsynchronous 為true。有了這個標志位,消息機制會對它有些特別的處理,我們稍後說。
所以 消息屏障和非同步消息的作用 很明顯,在設置消息屏障後,非同步消息具有優先處理的權利。
這時候我們回顧將消息添加到消息隊列中時,可以發現,其實並不是每一次添加消息時,都會喚醒線程。
當該消息插入到隊列頭時,會喚醒該線程;
當該消息沒有插入到隊列頭,但隊列頭是屏障,且該消息是隊列中 靠前的一個非同步消息,則會喚醒線程,執行該消息;
調用MessageQueue.removeSyncBarrier 方法可以移除指定的消息屏障
ANR 即 Application Not Response, 是系統進程對應用行為的一種監控,如果應用程序沒有在規定時間內完成任務的話,就會引起ANR。
ANR類型
Service Timeout : 前台服務20s, 後台服務200s
BroadcastQueue Timeout : 前台廣播 10s,後台廣播60s
ContentPrivider Timeout : 10s
InputDispatching Timeout : 5s
比如,在啟動一個服務時, AMS端通過應用進程的Binder對象創建Service, 在scheleCreateService()方法中 會調用到當前service的onCreate()生命周期函數;
bumpServiceExecutingLocked()方法內部實際上會調用到scheleServiceTimeoutLocked()方法,發送一個ActivityManagerService.SERVICE_TIMEOUT_MSG類型消息到AMS工作線程中。
消息的延時時間,如果是前台服務,延時20s, 如果是後台服務,延時200s;
如果Service的創建 工作在 上訴消息的延時時間內完成,則會移除該消息,
否則,在Handler正常收到這個消息後,就會進行服務超時處理,即彈出ANR對話框。
復雜情況下,可能會頻繁調用sendMessage 往消息隊列中,添加消息,導致消息積壓,造成卡頓,
1,重復消息過濾
頻繁發送同類型消息時,有可能隊列中之前的消息還沒有處理,又發了一條相同類型的消息,更新之前的數據,這時候,可以採用移除前一個消息的方法,優化消息隊列。
2,互斥消息取消
在發送消息時,優先將消息隊列中還未處理的信息已經過時的消息 移除,優化隊列
3,隊列優化-復用消息
創建消息時,優先採用之前回收的消息,避免重復創建對象,引起GC
完~
(如果錯誤或不足,望指出, 大家共同進步)
㈩ Handler 中的 epoll
在 Linux 中,epoll 機制是一個重要的機制。在 Android 中的 Handler,簡單的利用了 epoll 機制,做到了消息隊列的阻塞和喚醒。
epoll 機制相關的函數有
因為對於Handler 對爛帶於 epoll 沒有過於深入的使用,只是利用了 epoll 進行了阻塞和喚醒,還是比較好理解的。
於是,便利用 epoll 機制在mEpollFd上添加(EPOLL_CTL_ADD)了監聽的 fd(mWakeEventFd);
在 java 層,next( )@Message 會阻塞到nativePollOnce(long ptr, int timeoutMillis),特別的是,當沒有消息時,timeoutMillis = -1表示一直阻塞。如果有 delay 的消息,則 timeoutMillis 表示 delay的時間。
此時利用epoll 機制在 epoll_wait()上設置超時時間。當 timeoutMillis = -1時會一直等待知道有新消息來。否則當超時時間到達時信歷賀,會返回到 next()@Message就可以處理那條 delay 的消息了。
當有新消息來臨時並且是立刻執行的,enqueueMessage()@Message 會調用nativeWake(),否則會根據新來的消息的 delay 時滑派間和隊列中的 delay 時間進行對比,消息隊列是按照msg 的到達時間和 delay 時間進行排序,如果新來的消息在前並且需要 delay 也會進行 wake()
當往 mWakeEventFd 寫入一個 1,便會從 epoll_wait() 馬上返回。進行新一輪的消息處理。
另外,native 層的 Looper 的 epoll 機制沒有這么簡單,只是在 Handler 中只是簡單地使用了。
Linux中的epoll