1. 程式人生 > >Android效能專項FPS測試實踐

Android效能專項FPS測試實踐

前言

最近手上有個專案,需要進行流暢度的專項測試,目前已經進行了一段時間,因此想總結一些經驗和教訓跟大家分享。

測試需求

通過技術手段量化程式卡頓程度,過程資料視覺化
多平臺機型適配,方案不能依賴root
不能有Android的API版本限制(因為需要相容多個系統版本)
監控流程可自動化執行
過程需要連續可靠
測試準備

理解FPS的概念

FPS即Frames per second,>>點選這篇文章 解釋的非常清楚。當我們準備測試流暢度的時候,必須先理解兩個關鍵指標60幀每秒以及16.67毫秒,這兩個值代表什麼意思?怎麼得來的? 
用過flash的人應該知道動畫片其實是由一張張畫出來的圖片連貫執行產生的效果,當一張張獨立的圖片切換速度足夠快的時候,會欺騙我們的眼睛,以為這是連續的動作。反之類推,當你的圖片切換不夠快的時候,就會被人眼看穿,反饋給使用者的就是所謂的卡頓現象。 
想要讓大腦覺得動作是連續的,至少是每秒10-12幀的速度,而想達到流暢的效果,至少需要每秒24幀。這也是為什麼電影片源通常都是24幀的原因,好奇的同學點選>>知乎高知看看大神的解答。不過60幀每秒的流暢度是最佳的,我們的目標就是讓程式的流暢度能接近60幀每秒,當然超過60幀速的話大部分人還是會受不了的。

綜上所述,APP需要儘可能的超過24幀/秒,接近60幀/秒的速度,並且在使用的過程中保持這個速率,試想一下你吃著火鍋看著電影,突然影象發生了跳躍或者畫面撕裂,那種感覺就像米飯裡吃到了沙子一樣極度不爽,因此這意味著我們的程式需要在16.67ms內處理一幅畫面內的所有事,並保持住這個狀態。
計算公式:1000ms / 60 frames ≈ 16.67 ms/frames

獲取FPS的可行性方案

在查閱了很多的資料,也學習了同行前輩們的各種記錄後,我根據自己需要將可行性方案的範圍縮小到三種。

方案一 
通過 [設定]->[開發者選項]->[GPU呈現模式分析] ->[在螢幕上顯示為條形圖] 進行直觀的取樣,截圖如下: 

設定完成後的效果是這樣的: 
 
螢幕下方的柱形圖會持續重新整理,最上方會有一根綠色的線,代表的是16ms的閾值,超過這個界限表示當前幀繪製的時間出現了延遲,及卡頓現象,後面會詳細介紹原因。橫座標表示時間的持續,每一根柱形圖表示當前幀的繪製時間。因此我們在使用的過程中,下面的柱形圖會一直的重新整理,單位是ms。各位看官是否有注意到每一幀的柱形圖顏色不一樣呢(注:不同手機的顏色不一樣,僅限安卓4.0以上版本參考)?下圖是官網提供的比較典型的GPU渲染卡頓的例子: 


繪製過程中的不同顏色具有不同的含義,詳細解釋請移步>> 官網 檢視更多。

那麼是不是說我只需要開啟介面去數一下超過綠色閾值的柱狀圖有多少就可以觀察我們應用的流暢度了?然而並沒有,因為這個方式獲取到的渲染時間只是UI主執行緒上的繪製行為,目前我所接手的專案,採用的方式是捕捉相機的資料然後放到GPU中去進行繪製,有單獨的繪製執行緒,單獨的檢視,所以這個方案並不適合我手上的專案。

方案二
adb shell dumpsys gfxinfo yourpackagename
1
使用這個命令可以得到如下的返回值,節選關鍵部分:

** Graphics info for pid 3125 [com.qtest.demo] **

Stats since: 37470720668ns
Total frames rendered: 95
Janky frames: 0 (0.00%)
90th percentile: 7ms
95th percentile: 7ms
99th percentile: 11ms
Number Missed Vsync: 0
Number High input latency: 0
Number Slow UI thread: 0
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 0

大家可以看到,使用起來非常方便,但是遺憾的是必須是Android M 版本以上才支援,而且需要拖動螢幕產生的資料才比較準確,對於我這種需要適配多種機型和版本的情況就不太符合了,關於這個命令的詳細解釋,各位可以看一下谷歌官方的>>Document。

方案三
adb shell dumpsys SurfaceFlinger --latency com.qtest.demo/com.qtest.DemoActivity
1
執行這條命令以後,你會得到這樣的資訊:

16666667
575271438588    575276081296    575275172129
575305169681    575309795514    575309142441
。。。此處省略很多行。。。
580245208898    580250445565    580249372231
580279290043    580284176346    580284812908
580330468482    580334851815    580333739054

第一行是裝置的重新整理週期refresh-period,單位是納秒,換算成毫秒就是16666667\1000\1000≈16.67ms;這條命令的含義是獲取當前layer(視窗、圖層)的最近128幀的資訊(僅儲存128幀),所以上面我省略的部分實際上總共有128行(刨去第一行重新整理率,需要按127幀來換算),這三列資料的解釋是(ps:翻譯不正確的請指正):

第一列: when the app started to draw (開始繪製圖像的瞬時時間)
第二列: the vsync immediately preceding SF submitting the frame to the h/w (VSYNC信令將軟體SF幀傳遞給硬體HW之前的垂直同步時間)
第三列: timestamp immediately after SF submitted that frame to the h/w (SF將幀傳遞給HW的瞬時時間,及完成繪製的瞬時時間)
【垂直同步】 
Vertical Synchronization,螢幕從圖形晶片獲取每幀的資料,然後逐行進行繪製。理想狀況下,你期望顯示屏在繪製完一幀之後,圖形晶片整好能提供新幀的資料。影象撕裂的狀況就發生在圖形晶片在影象繪製到一半的時候,就載入了新一幀的資料,以致你最終得到的資料幀是半個幀的新資料和半個幀的老資料。而垂直同步,顧名思義就是用來同步的。它告知GPU在載入新幀之前,要等待螢幕繪製完成前一幀。 
沒有垂直同步的示意圖 
 
有垂直同步的示意圖 

【掉幀jank】 
統計硬體掉幀數的計算方式是:在一個重新整理週期內(16.67ms)會記錄127行繪製時間,其中包括起始繪製(上面第一列的時間資料)和完成繪製兩個納秒時間(上面第三列的時間資料),每兩行的繪製時間間隔應該相等,否則就發生了幀延遲,即掉幀(jank),每個週期內發生掉幀就計數1次,在整個繪製週期內的掉幀數即是渲染掉幀的數量。
測試指標&範圍

搞清楚一些基礎概念後,我們需要確定接下來測試需要獲取的資料,本次測試除了常規的資料以外,業務方也通過程式埋點輸出了一些關注的指標資訊,概況起來包括但不限於以下幾點: 
- 元件初始化時間(業務方埋點) 
- APP啟動時間(冷啟動、熱啟動) 
- CPU佔用(活動、靜默狀態) 
- PSS記憶體佔用(活動、靜默狀態)//因為不能root手機,所以沒有取USS 
- 電池溫度變化(活動、靜默狀態) 
- FPS 
- 硬體渲染掉幀數 
- 單幀渲染平均時間 
- 單幀檢測處理時間(業務方埋點)

OS版本:4.4、5.0、5.1、4.2、4.3、6.0、4.0 
品牌覆蓋:華為、小米、Nexus、VIVO、奇酷、酷派、三星、錘子 
解析度覆蓋:1920X1080、2560X1440、1280X720、854X480 
業務場景:針對業務特點進行設定的測試場景,在此不表。

取樣策略

在調研了網上各種方案後,個人覺得 @sandman 的方案是最符合我的預期,因此直接拿來使用。方案如下: 
1. 通過命令:dumpsys SurfaceFlinger | grep "|....|"獲取當前置頂視窗名稱 
2. 歷史記錄127行資料,按60幀算可記錄2.12S資料,從而不用頻繁獲取。(最終考慮設定1.6S間隔重新整理資料。) 
3. 定期清零重新記錄,避免如何分清哪些資料是上次的。命令:dumpsys SurfaceFlinger --latency-clear 
4. 有重新整理則計算幀率,無重新整理則不輸出資料,有時候取到的fps為1,就是這個原因 
5. 每次取樣資料大於等於1幀則計算FPS,丟幀率,最大幀間隔 
6. 針對業務需求,增加了單幀平均渲染時間的統計

具體實現

網上的實現方式大多數都是取自於這裡: 
https://github.com/ChromiumWebApps/chromium/blob/master/build/android/pylib/perf/surface_stats_collector.py 
掌握python開發的同學可以閱讀和學習一下作者的採集思路。針對原始碼,有篇部落格做了詳細的解讀,感興趣的同學可以看這裡: 
http://blog.csdn.net/itfootball/article/details/43084527 
我採取的實現方式是@sandman的辦法,利用shell檔案在手機內部獲取資料後生成csv檔案,再通過adb命令pull到本地進行統計,針對我自己的需求,對shell程式碼略微做了調整,增加了單幀平均渲染時間的統計,執行效果像這樣: 

這種取樣的方式最大的好處就是不用root手機,針對shell程式碼的解釋,由於篇幅太長我就不在這裡貼了,大家可以移步這裡去看詳細的解釋: 
https://testerhome.com/topics/4775 
各位可以在這裡下載完整版工具,工具包中包含了利用python生成HTML報表的程式碼: 
https://yunpan.cn/ckcypyNLDAet2 (提取碼:005f)

那麼測試出來的報告大概可以是這樣的: