面試官帶你學Android——面試中Handler 這些必備知識點你都知道嗎?
在Android面試中,關於 Handler 的問題是必備的,但是這些關於 Handler 的知識點你都知道嗎?
一、題目層次
- Handler 的基本原理
- 子執行緒中怎麼使用 Handler
- MessageQueue 獲取訊息是怎麼等待
- 為什麼不用 wait 而用 epoll 呢?
- 執行緒和 Handler Looper MessageQueue 的關係
- 多個執行緒給 MessageQueue 發訊息,如何保證執行緒安全
- Handler 訊息延遲是怎麼處理的
- View.post 和 Handler.post 的區別
- Handler 導致的記憶體洩漏
- 非 UI 執行緒真的不能操作 View 嗎
二、題目詳解
程式碼分析基於 Android SDK 28
大家可以先看上面的問題思考一下,如果都清楚的話,下面的文章也沒必要看了~
1. Handler 的基本原理
關於 Handler 的原理,相比不用多說了,大家都應該知道,一張圖就可以說明(圖片來自網路)。
2. 子執行緒中怎麼使用 Handler
除了上面 Handler 的基本原理,子執行緒中如何使用 Handler 也是一個常見的問題。
子執行緒中使用 Handler 需要先執行兩個操作:Looper.prepare 和 Looper.loop。
為什麼需要這樣做呢?Looper.prepare 和 Looper.loop 都做了什麼事情呢?
我們知道如果在子執行緒中直接建立一個 Handler 的話,會報如下的錯誤:
我們可以看一下 Handler 的建構函式,裡面會對 Looper 進行判斷,如果透過 ThreadLocal 獲取的 Looper 為空,則報上面的錯誤。
那麼 Looper.prepare 裡做了什麼事情呢?
可以看到,Looper.prepare 就是建立了 Looper 並設定給 ThreadLocal,這裡的一個細節是每個 Thread 只能有一個 Looper,否則也會丟擲異常。
而 Looper.loop 就是開始讀取 MessageQueue 中的訊息,進行執行了。
這裡一般會引申一個問題,就是主執行緒中為什麼不用手動呼叫這兩個方法呢?相信大家也都明白,就是 ActivityThread.main 中已經進行了呼叫。
透過這個問題,又可以引申到 ActivityThread 相關的知識,這裡就不細說了。
3. MessageQueue 如何等待訊息
上面說到 Looper.loop 其實就是開始讀取 MessageQueue 中的訊息了,那 MessageQueue 中沒有訊息的時候,Looper 在做什麼呢?我們知道是在等待訊息,那是怎麼等待的呢?
透過 Looper.loop 方法,我們知道是 MessageQueue.next() 來獲取訊息的,如果沒有訊息,那就會阻塞在這裡,MessageQueue.next 是怎麼等待的呢?
在 MessageQueue.next 裡呼叫了 native 方法 nativePollOnce。
從上面程式碼中我們可以看到,在 native 側,最終是使用了 epoll_wait 來進行等待的。
這裡的 epoll_wait 是 Linux 中 epoll 機制中的一環,關於 epoll 機制這裡就不進行過多介紹了,大家有興趣可以參考
segmentfault.com/a/119000000…
那其實說到這裡,又有一個問題,為什麼不用 java 中的 wait / notify 而是要用 native 的 epoll 機制呢?
4. 為什麼不用 wait 而用 epoll 呢?
說起來 java 中的 wait / notify 也能實現阻塞等待訊息的功能,在 Android 2.2 及以前,也確實是這樣做的。
可以參考這個 commit
/android/2.1…
那為什麼後面要改成使用 epoll 呢?透過看 commit 記錄,是需要處理 native 側的事件,所以只使用 java 的 wait / notify 就不夠用了。
具體的改動就是這個 commit
android.googlesource.com/platform/fr…
不過這裡最開始使用的還是 select,後面才改成 epoll。
具體可見這個 commit
android.googlesource.com/platform/fr…
至於 select 和 epoll 的區別,這裡也不細說了,大家可以在上面的參考文章中一起看看。
5. 執行緒和 Handler Looper MessageQueue 的關係
這裡的關係是一個執行緒對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。
6. 多個執行緒給 MessageQueue 發訊息,如何保證執行緒安全
既然一個執行緒對應一個 MessageQueue,那多個執行緒給 MessageQueue 發訊息時是如何保證執行緒安全的呢?
說來簡單,就是加了個鎖而已。
7. Handler 訊息延遲是怎麼處理的
Handler 引申的另一個問題就是延遲訊息在 Handler 中是怎麼處理的?定時器還是其他方法?
這裡我們先從事件發起開始看起:
從上面的程式碼邏輯來看,Handler post 訊息以後,一直呼叫到 MessageQueue.enqueueMessage 裡,其中最重要的一步操作就是傳入的時間是 uptimeMillis + delayMillis。
透過上面程式碼我們看到,post 一個延遲訊息時,在 MessageQueue 中會根據 when 的時長進行一個順序排序。
接著我們再看看怎麼使用 when 的。
透過上面的程式碼分析,我們知道了執行 Handler.postDelayd 時候,會執行下面幾個步驟:
- 將我們傳入的延遲時間轉化成距離開機時間的毫秒數
- MessageQueue 中根據上一步轉化的時間進行順序排序
- 在 MessageQueue.next 獲取訊息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則透過 epoll_wait 的 timeout 進行等待
- 如果該訊息需要等待,會進行 idel handlers 的執行,執行完以後會再去檢查此訊息是否可以執行
8. View.post 和 Handler.post 的區別
我們最常用的 Handler 功能就是 Handler.post,除此之外,還有 View.post 也經常會用到,那麼這兩個有什麼區別呢?
我們先看下 View.post 的程式碼。
透過程式碼來看,如果 AttachInfo 不為空,則透過 handler 去執行,如果 handler 為空,則透過 RunQueue 去執行。
那我們先看看這裡的 AttachInfo 是什麼。
這個就需要追溯到 ViewRootImpl 的流程裡了,我們先看下面這段程式碼。
程式碼寫了一些關鍵部分,在 ViewRootImpl 建構函式里,建立了 mAttachInfo,然後在 performTraversals 裡,如果 mFirst 為 true,則呼叫 host.dispatchAttachedToWindow,這裡的 host 就是 DecorView。
這裡還有一個知識點就是 mAttachInfo 中的 mHandler 其實是 ViewRootImpl 內部的 ViewRootHandler。
然後就呼叫到了 DecorView.dispatchAttachedToWindow,其實就是 ViewGroup 的 dispatchAttachedToWindow,一般 ViewGroup 中相關的方法,都是去依次呼叫 child 的對應方法,這個也不例外,依次呼叫子 View 的 dispatchAttachedToWindow,把 AttachInfo 傳進去,在 子 View 中給 mAttachInfo 賦值。
看到這裡,大家可能忘記我們開始剛剛要做什麼了。
我們是在看 View.post 的流程,再回顧一下 View.post 的程式碼:
現在我們知道 attachInfo 是什麼了,是 ViewRootImpl 首次觸發 performTraversals 傳進來的,也就是觸發 performTraversals 之後,View.post 都是透過 ViewRootImpl 內部的 Handler 進行處理的。
如果在 performTraversals 之前或者 mAttachInfo 置為空以後進行執行,則透過 RunQueue 進行處理。
那我們再看看 getRunQueue().post(action); 做了些什麼事情。
這裡的 RunQueue 其實是 HandlerActionQueue。
HandlerActionQueue 的程式碼看一下。
透過上面的程式碼我們可以看到,執行 getRunQueue().post(action); 其實是將程式碼新增到 mActions 進行儲存,然後在 executeActions 的時候進行執行。
executeActions 執行的時機只有一個,就是在 dispatchAttachedToWindow(AttachInfo info, int visibility) 裡面呼叫的。
看到這裡我們就知道了,View.post 和 Handler.post 的區別就是:
- 如果在 performTraversals 前呼叫 View.post,則會將訊息進行儲存,之後在 dispatchAttachedToWindow 的時候透過 ViewRootImpl 中的 Handler 進行呼叫。
- 如果在 performTraversals 以後呼叫 View.post,則直接透過 ViewRootImpl 中的 Handler 進行呼叫。
這裡我們又可以回答一個問題了,就是為什麼 View.post 裡可以拿到 View 的寬高資訊呢?
因為 View.post 的 Runnable 執行的時候,已經執行過 performTraversals 了,也就是 View 的 measure layout draw 方法都執行過了,自然可以獲取到 View 的寬高資訊了。
9. Handler 導致的記憶體洩漏
這個問題就是老生常談了,可以由此再引申出記憶體洩漏的知識點,比如:如何排查記憶體洩漏,如何避免記憶體洩漏等等。
10. 非 UI 執行緒真的不能操作 View 嗎
我們使用 Handler 最多的一個場景就是在非主執行緒透過 Handler 去操作 主執行緒的 View。
那麼非 UI 執行緒真的不能操作 View 嗎?
我們在執行 UI 操作的時候,都會呼叫到 ViewRootImpl 裡,以 requestLayout 為例,在 requestLayout 裡會透過 checkThread 進行執行緒的檢查。
我們看這裡的檢查,其實並不是檢查主執行緒,是檢查 mThread != Thread.currentThread,而 mThread 指的是 ViewRootImpl 建立的執行緒。
所以非 UI 執行緒確實不能操作 View,但是檢查的是建立的執行緒是否是當前執行緒,因為 ViewRootImpl 建立是在主執行緒建立的,所以在非主執行緒操作 UI 過不了這裡的檢查。
三、總結
一個小小的 Handler,其實可以引申出很多問題,這裡這是列舉了一些大家可能忽略的問題,更多的問題就等待大家去探索了~
這裡來總結一下:
1. Handler 的基本原理
一張圖解釋(圖片來自網路)
2. 子執行緒中怎麼使用 Handler
- Looper.prepare 建立 Looper 並新增到 ThreadLocal 中
- Looper.loop 啟動 Looper 的迴圈
3. MessageQueue 獲取訊息是怎麼等待
透過 epoll 機制進行等待和喚醒。
4. 為什麼不用 wait 而用 epoll 呢?
在 Android 2.2 及之前,使用 Java wait / notify 進行等待,在 2.3 以後,使用 epoll 機制,為了可以同時處理 native 側的訊息。
5. 執行緒和 Handler Looper MessageQueue 的關係
一個執行緒對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。
6. 多個執行緒給 MessageQueue 發訊息,如何保證執行緒安全
透過對 MessageQueue 加鎖來保證執行緒安全。
7. Handler 訊息延遲是怎麼處理的
- 將傳入的延遲時間轉化成距離開機時間的毫秒數
- MessageQueue 中根據上一步轉化的時間進行順序排序
- 在 MessageQueue.next 獲取訊息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則透過 epoll_wait 的 timeout 進行等待
- 如果該訊息需要等待,會進行 idel handlers 的執行,執行完以後會再去檢查此訊息是否可以執行
8. View.post 和 Handler.post 的區別
View.post 最終也是透過 Handler.post 來執行訊息的,執行過程如下:
- 如果在 performTraversals 前呼叫 View.post,則會將訊息進行儲存,之後在 dispatchAttachedToWindow 的時候透過 ViewRootImpl 中的 Handler 進行呼叫。
- 如果在 performTraversals 以後呼叫 View.post,則直接透過 ViewRootImpl 中的 Handler 進行呼叫。
9. Handler 導致的記憶體洩漏
略過不講~
10. 非 UI 執行緒真的不能操作 View 嗎
不能操作,原因是 ViewRootImpl 會檢查建立 ViewRootImpl 的執行緒和當前操作的執行緒是否一致。而 ViewRootImpl 是在主執行緒建立的,所以非主執行緒不能操作 View。
今天的文章就結束了,希望大家能學到一些不一樣的知識~
面試前該如何複習?
其實客戶端開發的知識點就那麼多,面試問來問去還是那麼點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。
這裡再分享一下我面試期間的複習路線:(以下體系的複習資料是我從各路大佬收集整理好的)
《Android開發七大模組核心知識筆記》
《960全網最全Android開發筆記》
《379頁Android開發面試寶典》
歷時半年,我們整理了這份市面上最全面的安卓面試題解析大全
包含了騰訊、百度、小米、阿里、樂視、美團、58、360、新浪、搜狐等一線網際網路公司面試被問到的題目。熟悉本文中列出的知識點會大大增加透過前兩輪技術面試的機率。
如何使用它?
1.可以透過目錄索引直接翻看需要的知識點,查漏補缺。
2.五角星數表示面試問到的頻率,代表重要推薦指數
《507頁Android開發相關原始碼解析》
只要是程式設計師,不管是Java還是Android,如果不去閱讀原始碼,只看API文件,那就只是停留於皮毛,這對我們知識體系的建立和完備以及實戰技術的提升都是不利的。
真正最能鍛鍊能力的便是直接去閱讀原始碼,不僅限於閱讀各大系統原始碼,還包括各種優秀的開源庫。
資料太多,全部展示會影響篇幅,暫時就先列舉這些部分截圖,以上資源均免費分享,以上內容均放在了開源專案: 【 github 】 中已收錄,大家可以自行獲取。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2726413/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 學習Python這些面試題你都知道嗎?Python面試題
- 年終小盤點:這十八個Android開發必備知識點,你都知道嗎?Android
- 這些喜聞樂見的Java面試知識點,你都掌握了嗎?Java面試
- 不想被面試官虐?Android知識彙總,你必須知道的Handler八大問題!面試Android
- 關於Java面試,你應該準備這些知識點Java面試
- 邦芒面試:想面試成功,這些事你必須知道面試
- 關於JVM,你必須知道的這些知識點JVM
- 這些必備Java技能,你都會了嗎Java
- 面試官:Java中物件都存放在堆中嗎?你知道逃逸分析?面試Java物件
- 這些必會的計算機網路知識點你都掌握了嗎計算機網路
- 吃透了這些 Redis 知識點,面試官一定覺得你很 NBRedis面試
- Redis 面試必備知識點Redis面試
- 面試現場:這些常問的面試題你都會了嗎面試題
- Netty中的這些知識點,你需要知道!Netty
- 面試官:你瞭解es6的知識嗎?面試
- 一些Java中不為人知的特殊方法,學完後面試官可能都沒你知道的多!Java面試
- 【面試篇】金九銀十面試季,這些面試題你都會了嗎?面試題
- 這 20 多個高併發程式設計必備的知識點,你都會嗎?程式設計
- 面試官:你知道怎麼求素數嗎?面試
- 身為初學Java的你,這些IDE的優缺點你都知道嗎?JavaIDE
- Nacos必知必會:這些知識點你一定要掌握!
- 面試官: 我必問的容器知識點!面試
- 你去面試,需要準備什麼知識點?面試
- Chrome DevTools中的這些騷操作,你都知道嗎?Chromedev
- 面試官:你知道Callback Hell(回撥地獄)嗎?面試
- 你知道PHP中Exception, Error Handler的這些細PHPExceptionError
- 收藏!這些 IDE 使用技巧,你都知道嗎IDE
- 面試軟體工程師,這些準備工作你做了嗎?面試軟體工程工程師
- 最新阿里Java面試題,這些面試題你會嗎?阿里Java面試題
- 這些鮮為人知的前端冷知識,你都GET了嗎?前端
- 關於Java面試,你應該準備這些知識-第二篇Java面試
- 你說你懂計算機網路,那這些你都知道嗎計算機網路
- 你真的理解反向傳播嗎?面試必備反向傳播面試
- 金三銀四跳槽季,這些面試題你都會了嗎?面試題
- 介面中這些細節你知道嗎?
- 深入理解MySQL---資料庫知識最全整理,這些你都知道了嗎?MySql資料庫
- 騰訊這套SpringMvc面試題你懂多少(面試必備)SpringMVC面試題
- 關於Linux系統,這些你都知道嗎?Linux