1. 程式人生 > >你知道android的MessageQueue.IdleHandler嗎?

你知道android的MessageQueue.IdleHandler嗎?

源碼 .com 有一個 開始 關系 研發流程 nis 想要 tin

WeTest 導讀

幹貨!幹貨!或許可以是一種處理問題的新思路喲!


前言

我們知道android是基於Looper消息循環的系統,我們通過Handler向Looper包含的MessageQueue投遞Message, 不過我們常見的用法是這樣吧?

技術分享圖片

一般我們比較少接觸MessageQueue, 其實它內部的IdleHandler接口有很多有趣的用法,首先看看它的定義:

技術分享圖片

簡而言之,就是在looper裏面的message暫時處理完了,這個時候會回調這個接口,返回false,那麽就會移除它,返回true就會在下次message處理完了的時候繼續回調,讓我們看看它有哪些有趣的用法吧~~

一、提供一個android沒有的聲明周期回調時機

如果有這種需求,想要在某個activity繪制完成去做一些事情,那這個時機是什麽時候呢?有同學可能覺得onResume()是一個合適的機會,不是可是這個onResume() 真的是各種繪制都已經完成才回調的嗎?No, too naive ~~

技術分享圖片

你看谷老師說了,onStart是用戶可見,onResume是用戶可交互,谷老師可沒說onResume是繪制完成吧~那麽android那些耗時的measure, layout, draw是在什麽時候執行的呢?它們跟onResume()又有何關系呢?讓我們先來看看源碼吧~

1. ActivityThread.java

我們知道app的進程其實是ActivityThread, 那麽activity的生命周期自然是它來執行了,

技術分享圖片

performResumeActivity就是回調onResume了, 我們繼續看wm.addView方法, 這個ViewManager是一個接口,其實現者是WindowManagerImpl

2.WindowManagerImpl.java

技術分享圖片

這個mGlobal是WindowManagerGlobal對象,我們繼續

3.WindowManagerGlobal.java

技術分享圖片

這裏我們new 出了ViewRootImpl對象, 我們知道這個對象就是android view的根對象了,負責view繪制的measure, layout, draw的巨長的方法 performTraversals就是這個類的,我們繼續看setView方法

4.ViewRootImpl.java

技術分享圖片

這個函數調用了關鍵方法requestLayout(), 我們繼續跟蹤,順便說下,後面一連串的BadTokenException就是我們常常遇到的dialog相關拋出的,也有些特殊場景也會出這個異常,可以到這裏查看線索。

技術分享圖片

調用了scheduleTraversals, 從名字就能看出來了吧:

它往Choreographer裏面post了一個runnable, 這個Choreographer是android負責幀率刷新相關的東西,我們暫時可以不關註它,可以理解為往主線程post一個消息是一樣的,順便說下這個Choreographer可以做幀率檢測相關的東西,,可以用於卡頓檢測什麽的···

技術分享圖片

技術分享圖片

我們看這個runnable果然是去執行了那個巨長無比的函數performTraversals函數, 現在我們可以總結下流程了:

技術分享圖片

結論:所以如果我們想在界面繪制出來後做點什麽,那麽在onResume裏面顯然是不合適的,它先於measure等流程了 有人可能會說在onResume裏面post一個runnable可以嗎?還是不行,因為那樣就會變成這個樣子

技術分享圖片

所以你的行為一樣會在繪制之前執行,這個時候我們的主角IdleHandler就發揮作用了,我們前面說了,它是在looper裏面message暫時執行完畢了就會回調,顧名思義嘛,Idle就是隊列為空的意思,那麽我們的onResume和measure, layout, draw都是一個個message的話,這個IdleHandler就提供了一個它們都執行完畢的回調了,大概就是這樣

技術分享圖片

說了這麽多,那麽現在獲取到這個時機有什麽用呢? look!!

技術分享圖片

這個是我們地圖的公交詳情頁面, 進入之後產品要求左邊的頁卡需要展示,可以看到左邊的頁卡是一個非常復雜的布局,那麽進入之後的效果可以明顯看到頭部的展示信息是先顯示空白再100毫秒左右之後才展示出來的,原因就是這個頁卡的內容比較復雜,用數據向它填充的時候花了較長時間,代碼如下:

技術分享圖片

技術分享圖片

可以看到這個detailView就是這個側滑的頁卡了,填充裏面的數據花了90ms,如果這個時間是用在了界面view繪制之前的話,就會出現以上的效果了,view先是白的,再出現,這樣就體驗不好了,如果我們把它放到IdleHandler裏面呢?代碼如下:

技術分享圖片

效果是這樣的:

技術分享圖片

看出不同了嗎?頂部的頁卡先展示出來了,這樣體驗是不是會更好一些呢。雖然只有短短90ms,不過我們做app也應該關註這種細節優化的,是吧~ 這個做法也提供了一種思路,android本身提供的activity框架和fragment框架並沒有提供繪制完成的回調,如果我們自己實現一個框架,就可以使用這個IdleHandler來實現一個onRenderFinished這種回調了。

二、可以結合HandlerThread, 用於單線程消息通知器

我們先思考一個問題,如果有一個model數據管理模塊,怎麽設計?比如地圖的收藏模塊的model部分。就是下面這個圖的小星星:

技術分享圖片

它原來的model設計大概是這個樣子的:

技術分享圖片

由於這個model是單例的,而且是多線程可以訪問的,所以它的增刪改查都加上了鎖,而且由於外部訪問需要遍歷有哪些收藏點,所以外部遍歷列表也需要加鎖,大概是這樣的:

技術分享圖片

因為是多線程可訪問的,如果遍歷不加鎖的話,其他線程刪除了一個收藏,就會crash的,原來的這樣設計有幾個不好的地方:

1. 外部使用者需要關系鎖的使用,增加了負擔,不用還不安全

2. 如果在主線程加鎖的話,可能另一個線程執行操作會阻塞主線程造成anr

總之,多線程代碼就是容易出錯,而且真的出錯的時候查起來太費勁了,目前收藏夾模塊就有N多bug,所以我想用單線程來解決這個問題,由於model層的訪問需要數據庫和網絡等,所以需要異步線程,那麽單線程隊列+異步線程,首先想到的就是HandlerThread, 大概架構如下:

技術分享圖片

現在,我們把原來多線程的邏輯改到了單線程裏面,各種收藏的model共用一個HandlerThread,這樣我們增刪改查都不用加鎖了,出錯幾率大大減小,而且這種model的設計有點類似插件的意思,可以很方便的增加其他收藏。

Ok, 那麽跟我們的主題IdleHandler有什麽關系呢?思考這樣一個問題,地圖上的小星星需要實時更新,也就是model的任何變化都需要顯示到地圖上,那麽收藏的小星星就應該作為model的觀察者,以前的做法是向收藏model註冊監聽,在每一個增刪改查操作後都對觀察者回調,大概是這樣:

技術分享圖片

這樣有一個小小的問題,就是如果有一個操作生成10個快速連續的增刪改查操作,那麽我們的UI就會收到10次回調,而這種場景下我們其實只需要最後一次回調就夠了,中間操作其實不用刷新UI的。

那麽現在改成單線程模型,我們又該如何處理這個問題呢?當然我們也能在每個post到異步線程的runnable裏面去回調觀察者,但這樣未免不夠優雅,所以這個時候IdleHandler不就又可以發揮作用了嗎?它是在消息暫時處理完的時候回調的呀,不是很符合我們的時機麽,對吧?

技術分享圖片

就是這個樣子了,這裏為什麽不用第一個場景下的Looper.myQueue().addIdleHandler()呢?註意這個地方Looper.myQueue()如果在主線程調用就會使用主線程looper了,所以我選擇反射這個HandlerThread的looper來設置它,這個IdleHandler我們返回了true, 表示我們要長期監聽消息隊列,因為返回false,下次就沒有回調了哦。

好了,結論是這個地方IdleHandler用作了一個消息的觸發器,是不是挺有意思的呢?

三、 結語

如果你沒有用過它,從今天開始試試吧,這篇文章只是我個人的一點小思路,說不定這個IdleHandler有很多其他的用法呢~~

騰訊WeTest提供上千臺真實手機,隨時隨地進行測試,保障應用/手遊品質。節省百萬硬件費用,加速敏捷研發流程。

同時騰訊WeTest兼容性測試團隊積累了10年的手遊測試經驗,旨在通過制定針對性的測試方案,精準選取目標機型,執行專業、完整的測試用例,來提前發現遊戲版本的兼容性問題,針對性地做出修正和優化,來保障手遊產品的質量。目前該團隊已經支持所有騰訊在研和運營的手遊項目。

歡迎進入:http://wetest.qq.com/product/cloudphone 體驗安卓真機

歡迎進入:http://wetest.qq.com/product/expert-compatibility-testing 使用專家兼容測試服務。WeTest兼容性測試團隊期待與您交流!You Create,We Test!

如果對使用當中有任何疑問,歡迎聯系騰訊WeTest企業QQ:800024531

你知道android的MessageQueue.IdleHandler嗎?