歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~git
本文由 likunhuang發表於 雲+社區專欄
實現背景shell
應用的使用流暢度,是衡量用戶體驗的重要標準之一。Android 因爲機型配置和系統的不一樣,項目複雜App場景豐富,代碼多人蔘與迭代歷史較久,代碼可能會存在不少UI線程耗時的操做,實際測試時候也會偶爾發現某些業務場景發生卡頓的現象,用戶也常常反饋和投訴App使用遇到卡頓。所以,咱們愈來愈關注和提高用戶體驗的流暢度問題。小程序
已有方案服務器
在這以前,咱們將反饋的常見卡頓場景,或測試過程當中常見的測試場景使用UI自動化來重複操做,用adb系統工具觀察App的卡頓數據狀況,試圖重現場景來定位問題。微信
經常使用的方式是使用adb SurfaceFlinger服務和adb gfxinfo功能,在自動化操做app的過程當中,使用adb獲取數據來監控app的流暢狀況,發現出現出現卡頓的時間段,尋找出現卡頓的場景和操做。app
方式1:adb shell dumpsys SurfaceFlinger框架
使用‘adb shell dumpsys SurfaceFlinger’命令便可獲取最近127幀的數據,經過按期執行adb命令,獲取幀數來計算出幀率FPS。機器學習
優勢:命令簡單,獲取方便,動態頁面下數據直觀顯示頁面的流暢度;函數
缺點:對於靜態頁面,沒法感知它的卡頓狀況。使用FPS在靜態頁面狀況下,因爲獲取數據不變,計算結果爲0,沒法有效地衡量靜態頁面卡頓程度;工具
經過外部adb命令取得的數據信息衡量app頁面卡頓狀況的同時,app層面沒法在運行時判斷是否卡頓,也就沒法記錄下當時運行狀態和現場信息。
方式2:adb shell dumpsys gfxinfo
使用‘adb shell dumpsys gfxinfo’命令便可獲取最新128幀的繪製信息,詳細包括每一幀繪製的Draw,Process,Execute三個過程的耗時,若是這三個時間總和超過16.6ms即認爲是發生了卡頓。
優勢:命令簡單,獲取方便,不只能夠計算幀率,還能夠觀察卡頓時每一幀的瓶頸處於哪一個維度(onDraw,onProcess,onExecute);
缺點:同方式1擁有同樣的缺點,沒法衡量靜態頁面下的卡頓程度;app層面依然沒法在發生卡頓時獲取運行狀態和信息,致使跟進和重現困難。
已有的兩種方案比較適合衡量回歸卡頓問題的修復效果和判斷某些特定場景下是否有卡頓狀況,然而,這樣的方式有幾個明顯的不足:
一、通常很難構造實際用戶卡頓的環境來重現;
二、這種方式操做起來比較麻煩,需編寫自動化用例,沒法覆蓋大量的可疑場景,測試重現耗時耗人力;
三、沒法衡量靜態頁面的卡頓狀況;
四、出現卡頓的時候app沒法及時獲取運行狀態和信息,開發定位困難。
全新方案
基於這樣的痛點,咱們但願能使用一套有效的檢測機制,可以覆蓋各類可能出現的卡頓場景,一旦發生卡頓,能幫助咱們更方便地定位耗時卡頓發生的地方,記錄下具體的信息和堆棧,直接從代碼程度給到開發定位卡頓問題。咱們設想的Android卡頓監控系統須要達到幾項基本功能:
一、如何有效地監控到App發生卡頓,同時在發生卡頓時正確記錄app的狀態,如堆棧信息,CPU佔用,內存佔用,IO使用狀況等等;
二、統計到的卡頓信息上報到監控平臺,須要處理分析分類上報內容,並經過平臺Web直觀簡便地展現,供開發跟進處理。
如何從App層面監控卡頓?
咱們的思路是,通常主線程過多的UI繪製、大量的IO操做或是大量的計算操做佔用CPU,致使App界面卡頓。只要咱們能在發生卡頓的時候,捕捉到主線程的堆棧信息和系統的資源使用信息,便可準確分析卡頓發生在什麼函數,資源佔用狀況如何。那麼問題就是如何有效檢測Android主線程的卡頓發生,目前業界兩種主流有效的app監控方式以下,在《Android卡頓監控方式實現》這篇文章中我將分別詳細闡述這二者的特色和實現。
一、利用UI線程的Looper打印的日誌匹配;
二、使用Choreographer.FrameCallback
方式3: 利用UI線程的Looper打印的日誌匹配判斷是否卡頓
Android主線程更新UI。若是界面1秒鐘刷新少於60次,即FPS小於60,用戶就會產生卡頓感受。簡單來講,Android使用消息機制進行UI更新,UI線程有個Looper,在其loop方法中會不斷取出message,調用其綁定的Handler在UI線程執行。若是在handler的dispatchMesaage方法裏有耗時操做,就會發生卡頓。
只要檢測msg.target.dispatchMessage(msg) 的執行時間,就能檢測到部分UI線程是否有耗時的操做,從而判斷是否發生了卡頓,並打印UI線程的堆棧信息。
優勢:用戶使用app或者測試過程當中都能從app層面來監控卡頓狀況,一旦出現卡頓能記錄app狀態和信息, 只要dispatchMesaage執行耗時過大都會記錄下來,再也不有前面兩種adb方式面臨的問題與不足。
缺點:需另開子線程獲取堆棧信息,會消耗少許系統資源。
方式4: 利用Choreographer.FrameCallback監控卡頓
咱們知道, Android系統每隔16ms發出VSYNC信號,來通知界面進行重繪、渲染,每一次同步的週期爲16.6ms,表明一幀的刷新頻率。SDK中包含了一個相關類,以及相關回調。理論上來講兩次回調的時間週期應該在16ms,若是超過了16ms咱們則認爲發生了卡頓,利用兩次回調間的時間週期來判斷是否發生卡頓(這個方案是Android 4.1 API 16以上才支持)。
這個方案的原理主要是經過Choreographer類設置它的FrameCallback函數,當每一幀被渲染時會觸發回調FrameCallback, FrameCallback回調void doFrame (long frameTimeNanos)函數。一次界面渲染會回調doFrame方法,若是兩次doFrame之間的間隔大於16.6ms說明發生了卡頓。
優勢:不只可用來從app層面來監控卡頓,同時能夠實時計算幀率和掉幀數,實時監測App頁面的幀率數據,一旦發現幀率太低,可自動保存現場堆棧信息。
缺點:需另開子線程獲取堆棧信息,會消耗少許系統資源。
總結下上述四種方案的對比狀況:
SurfaceFlinger | gfxinfo | Looper.loop | Choreographer.FrameCallback | |
---|---|---|---|---|
監控是否卡頓 | √ | √ | √ | √ |
支持靜態頁面卡頓檢測 | × | × | √ | √ |
支持計算幀率 | √ | √ | × | √ |
支持獲取App運行信息 | × | × | √ | √ |
實際項目使用中,咱們一開始兩種監控方式都用上,上報的兩種方式收集到的卡頓信息咱們分開處理,發現卡頓的監控效果基本至關。同一個卡頓發生時,兩種監控方式都能記錄下來。 因爲Choreographer.FrameCallback的監控方式不只用來監控卡頓,也方便用來計算實時幀率,所以咱們如今只使用Choreographer.FrameCallback來監控app卡頓狀況。
痛點1:如何保證捕獲卡頓堆棧的準確性?
細心的同窗能夠發現,咱們經過上述兩種方案(Looper.loop和Choreographer.FrameCallback)能夠判斷是當前主線程是否發生了卡頓,進而在計算髮現卡頓後的時刻dump下了主線程的堆棧信息。實際上,經過一個子線程,監控主線程的活動狀況,計算髮現超過閾值後dump下主線程的堆棧,那麼生成的堆棧文件只是捕捉了一個時刻的現場快照。打個不太恰當的比方,至關於閉路電視監控只拍下了兇案發生後的慘狀,而並無錄下這個案件發生的過程,那麼做爲警察的你只看到告終局,依然很難判斷案情和兇手。在實際的運用中,咱們也發現這種方式下獲取到的堆棧狀況,查看相關的代碼和函數,常常已經不是發生卡頓的代碼了。
如圖所示,主線程在T1~T2時間段內發生卡頓,上述方案中獲取卡頓堆棧的時機已是T2時刻。實際卡頓多是這段時間內某個函數的耗時過大致使卡頓,而不必定是T2時刻的問題,如此捕獲的卡頓信息就沒法如實反應卡頓的現場。
咱們看看在這以前微信iOS主線程卡頓監控系統是如何實現的捕獲堆棧。微信iOS的方案是起檢測線程每1秒檢查一次,若是檢測到主線程卡頓,就將全部線程的函數調用堆棧dump到內存中。本質上,微信iOS方案的計時起點是固定的,檢查次數也是固定的。若是任務1執行花費了較長的時間致使卡頓,但因爲監控線程是隔1秒掃一次的,可能到了任務N才發現並dump下來堆棧,並不能抓到關鍵任務1的堆棧。這樣的狀況的確是存在的,只不過現上監控量大走人海戰術,經過幾率分佈抓到卡頓點,但依然不是最佳的捕獲方案。
所以,擺在咱們面前的是如何更加精準地獲取卡頓堆棧。爲了卡頓堆棧的準確度,咱們想要能獲取一段時間內的堆棧,而不是一個點的堆棧,以下圖:
咱們採用高頻採集的方案來獲取一段卡頓時間內的多個堆棧,而再也不是隻有一個點的堆棧。這樣的方案的優勢是保證了監控的完備性,整個卡頓過程的堆棧都得以採樣、收集和落地。
具體作法是在子線程監控的過程當中,每一輪log輸出或是每一幀開始啓動monitor時,咱們便已經開啓了高頻採樣收集主線程堆棧的工做了。當下一輪log或者下一幀結束monitor時,咱們判斷是否發生卡頓(計算耗時是否超過閾值),來決定是否將內存中的這段堆棧集合落地到文件存儲。也就是說,每一次卡頓的發生,咱們記錄了整個卡頓過程的多個高頻採樣堆棧。由此精確地記錄下整個兇案發生的詳細過程,供上報後分析處理(後文會闡述如何從一次卡頓中多個堆棧信息中提取出關鍵堆棧)。
採樣頻率與性能消耗
目前咱們的策略是判斷一個卡頓是否發生的耗時閾值是80ms(5*16.6ms),當一個卡頓達80ms的耗時,採集1~2個堆棧基本能夠定位到耗時的堆棧。所以採樣堆棧的頻率咱們設爲52ms(經驗值)。
固然,高頻採集堆棧的方案,必然會致使app性能上帶來的影響。爲此,爲了評估對App的性能影響,在上述默認設置的狀況下,咱們作一個簡單的測試實驗觀察。實驗方法:ViVoX9 上運行微信讀書App,使用卡頓監控與高頻採樣,和不使用卡頓監控的狀況下,保持兩次的操做動做相同,分析性能差別,數據以下:
關閉監控 | 打開監控 | 對比狀況(上漲) | ||
---|---|---|---|---|
CPU | 1.07% | 1.15% | 0.08% | |
Memory | Native Heap | 38794 | 38894 | 100 kB |
Dalvik Heap | 25889 | 26984 | 1095 kB | |
Dalvik Other | 2983 | 3099 | 116 kB | |
.so mmap | 38644 | 38744 | 100 kB |
沒有線程快照 | 加上線程快照 | |||
---|---|---|---|---|
性能指標 | 2.4.5.368.91225 | 2.4.8.376.91678 | 上漲 | |
CPU | CPU | 63 | 64 | 0.97% |
流量KB | Flow | 28624 | 28516 | |
內存KB | NativeHeap | 59438 | 60183 | 1.25% |
DalvikHeap | 7066 | 7109 | 0.61% | |
DalvikOther | 6965 | 6992 | 0.40% | |
Sommap | 22206 | 22164 | ||
日誌大小KB | file size | 294893 | 1561891 | 430% |
壓縮包大小KB | zip size | 15 | 46 | 206% |
從實驗結果可知,高頻採樣對性能消耗很小,能夠不影響用戶體驗。
監控使用Choreographer.FrameCallback, 採樣頻率設52ms),最終結果是性能消耗帶來的影響很小,可忽略:
1)監控代碼自己對主線程有必定的耗時,但影響很小,約0.1ms/S;
2)卡頓監控開啓後,增長0.1%的CPU使用;
3)卡頓監控開啓後,增長Davilk Heap內存約1MB;
4)對於流量,文件可按天寫入,壓縮文件最大約100KB,一天上傳一次
痛點2:海量卡頓堆棧後該如何處理?
卡頓堆棧上報到平臺後,須要對上報的文件進行分析,提取和聚類過程,最終展現到卡頓平臺。前面咱們提到,每一次卡頓發生時,會高頻採樣到多個堆棧信息描述着這一個卡頓。作個最小的估算,天天上報收集2000個用戶卡頓文件,每一個卡頓文件dump下了用戶遇到的10個卡頓,每一個卡頓高頻收集到30個堆棧,這就已經產生20001030=60W個堆棧。按照這個量級發展,一個月可產生上千萬的堆棧信息,每一個堆棧仍是幾十行的函數調用關係。這麼大量的信息對存儲,分析,頁面展現等均帶來至關大的壓力。很快就能撐爆存儲層,平臺沒法展現這麼大量的數據,開發更是沒辦法處理這些多的堆棧問題。於是,海量卡頓堆棧成爲咱們另一個面對的難題。
在一個卡頓過程當中,通常卡頓發生在某個函數的調用上,在這多個堆棧列表中,咱們把每一個堆棧都作一次hash處理後進行排重分析,有很大的概率會是dump到同一個堆棧hash,以下圖:
咱們對一個卡頓中多個堆棧進行統計,去重後找出最高重複次數的堆棧,發現堆棧C出現了3次,此次卡頓頗有可能就是卡在堆棧3反映的函數調用上。因爲採樣頻率不低,所以出現卡頓後通常都有很多的卡頓,如此可找出重複次數最高的堆棧,做爲重點分析卡頓問題,從而進行修復。
舉個實際上報數據例子,能夠由下圖看到,一個卡頓如序號3,在T1~T2時間段共收集到62個堆棧,咱們發現大部分堆棧都是同樣的,因而咱們把堆棧hash後嘗試去重,發現排重後只有2個堆棧,而其中某個堆棧重複了59次,咱們能夠重點關注和處理這個堆棧反映出的卡頓問題。
把一個卡頓抽離成一個關鍵的堆棧的思路,能夠大大下降了數據量, 前面說起60W個堆棧就能夠縮減爲2W個堆棧(2000101=2W)。
按照這個方法,處理後的每一個卡頓只剩下一個堆棧,進而每一個卡頓都有惟一的標識(hash)。到此,咱們還能夠對卡頓進行聚類操做,進一步排重和縮小數據量。分類前對每一個堆棧,根據業務的不一樣設置好過濾關鍵字,提取出感興趣的代碼行,去除其餘冗餘的系統函數後進行歸類。目前主要有兩種方式的分類:
一、按堆棧最外層分類,這種分類方法把一樣入口的函數致使的卡頓收攏到一塊兒,開發修復對應入口的函數來解決卡頓,然而這種方式有必定的風險,可能一樣入口但最終調用不一樣的函數致使的卡頓則會被忽略;
二、按堆棧最內層分類,這種分類方法能收攏一樣根源問題的卡頓,缺點就是可能忽略調用方可能有多個業務入口,會形成fix不全面。
固然,這兩種方式的聚類,從必定程度上分類大量的卡頓,但不太好控制的是,究竟要取堆棧的多少層做爲識別分類。層數越多,則聚類結果變多,分類更細,問題零碎;層數越少,則聚類結果變少,達不到分類的效果。這是一個權衡的過程,實際則按照必定的嘗試效果後去劃分層數,如微信iOS卡頓監控採用的策略是一級分類按最內層倒數2層分類,二級分類按最內層倒數4層。
對於咱們產品,目前咱們沒有按層數最內或最外來劃分,直接過濾出感興趣的關鍵字的代碼後直接分類。這樣的分類效果下來數據量級在承受範圍內,如以前的2W堆棧可聚類剩下大約2000個(視具體聚類結果)。同時,天天新上報的堆棧都跟歷史數據對比聚合,只過濾出未重複的堆棧,更進一步地縮減上報堆棧的真正存儲量。
卡頓監控系統的處理流程
用戶上報
目前咱們的策略是:
一、經過後臺配置下發,灰度0.2%的用戶量進行卡頓監控和上報;
二、若是用戶反饋有卡頓問題,也可實時撈取卡頓日誌來分析;
三、天天灰度的用戶一個機器上報一次,上報後刪除文件不影響存儲空間。
後臺解析
一、主要負責處理上報的卡頓文件,過濾、去重、分類、反解堆棧、入庫等流程;
二、自動迴歸修復好的卡頓問題,讀取tapd 卡頓bug單的修復結果,更新平臺展現,計算修復好的卡頓問題,後續版本是否從新出現(修復不完全)
平臺展現
上報處理後的卡頓展現平臺
http://test.itil.rdgz.org/wel...
主要展現卡頓處理後的數據:
一、以版本爲維度展現卡頓問題列表,按照卡頓上報重複的次數降序列出;
二、歸類後展現每一個卡頓的關鍵耗時代碼,也可查看所有堆棧內容;
三、支持操做卡頓記錄,如搜索卡頓,提tapd單,標註已解決等;
四、展現每一個版本的卡頓問題修復數據狀況,版本分佈,監控修復後是否重現等。
自動提單
實際使用中,爲了加強跟進效果,咱們設立一些規則,好比卡頓重複上報超過100次,卡頓耗時達到1000ms等,自動提tapd bug單給開發處理,系統也會自動更新卡頓問題的修復狀況和數據,開發只需按期review tapd bug單處理修復卡頓問題便可,整個卡頓系統從監控,上報,分析,聚類,展現,提單到迴歸,整個流程自動化實現,再也不須要人工介入。
實際應用效果
一、接入產品:微信讀書,企業微信,QQ郵箱
二、應用場景:現網用戶的監控,發佈前測試的監控,天天自動化運行的監控
三、發現問題:三個多月時間,歸類後的卡頓過萬,提bug單約500,開發已解決超過200個卡頓問題
卡頓監控的組件化
考慮到Android卡頓監控的通用性,除了應用於Android WeRead中,咱們也推廣到廣研的其餘產品中,如企業微信,QQ郵箱。所以,在開發GG的努力下,推出了卡頓監控庫http://git.code.oa.com/moai/m... ,其餘Android產品可快速接入卡頓監控的SDK來監控app卡頓狀況。
目前monitor卡頓監控庫主要有監控主線程卡頓狀況,獲取平均幀率使用狀況,高頻採樣和獲取卡頓信息等基本功能。這裏要注意幾點:
一、採樣堆棧信息的頻率和卡頓耗時的閾值都可在SDK中設置;
二、SDK默認判斷一個卡頓是否發生的耗時閾值是80ms(5*16.6ms)
三、採樣堆棧的頻率是52ms(約3幀+,儘可能錯開系統幀率的節奏,堆棧可儘可能落到繪製幀過程當中)
四、啓動監控後,卡頓日誌就會不斷經過內部的writer輸出,實現MonitorLogWriter.setDelegate才能獲取這些日誌,具體的日誌落地和上報策略由於各個App不一樣因此沒有集成到SDK中
五、monitor start後一直監控主線程, 包括切換到後臺時也會,直到主動stop或者app被kill。因此在切後臺時要主動stop monitor,切前臺時要從新start
1.組件引入方式
2.主線程卡頓監控的使用方式
1)啓動監控
2)中止監控
3)獲取卡頓信息
app中加入監控卡頓SDK後,會實時輸出卡頓的時間點和堆棧信息,咱們將這些信息寫入日誌文件落地,同時天天固定場景上報到服務器,如天天上報一次,用戶打開app後進行上報等策略。收集不一樣用戶不一樣手機不一樣場景下的全部卡頓堆棧信息,可供分析,定位和優化問題。
特別緻謝
此文最後特別感謝陽經理(ayangxu)、豪哥(veruszhong)、cginechen對Android卡頓監控組件化的鼎力支持,感謝姑姑(janetjiang)悉心指導與提議!但願卡頓監控系統能愈來愈多地暴露卡頓問題,在你們的共同努力下不斷提高App的流暢體驗!
相關閱讀
Javascript框架設計思路圖
小程序優化36計
【每日課程推薦】機器學習實戰!快速入門在線廣告業務及CTR相應知識
此文已由做者受權騰訊雲+社區發佈,更多原文請點擊
搜索關注公衆號「雲加社區」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社區!