WeTest 導讀java
乾貨!乾貨!或許能夠是一種處理問題的新思路喲!android
前言數據庫
咱們知道android是基於Looper消息循環的系統,咱們經過Handler向Looper包含的MessageQueue投遞Message, 不過咱們常見的用法是這樣吧?安全
通常咱們比較少接觸MessageQueue, 其實它內部的IdleHandler接口有不少有趣的用法,首先看看它的定義:網絡
簡而言之,就是在looper裏面的message暫時處理完了,這個時候會回調這個接口,返回false,那麼就會移除它,返回true就會在下次message處理完了的時候繼續回調,讓咱們看看它有哪些有趣的用法吧~~多線程
1、提供一個android沒有的聲明週期回調時機架構
若是有這種需求,想要在某個activity繪製完成去作一些事情,那這個時機是何時呢?有同窗可能以爲onResume()是一個合適的機會,不是但是這個onResume() 真的是各類繪製都已經完成纔回調的嗎?No, too naive ~~app
你看谷老師說了,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這種回調了。
2、能夠結合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用做了一個消息的觸發器,是否是挺有意思的呢?
3、 結語
若是你沒有用過它,從今天開始試試吧,這篇文章只是我我的的一點小思路,說不定這個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