Android性能專項FPS測試實踐

前言

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

測試需求

通過技術(shù)手段量化程序卡頓程度,過程數(shù)據(jù)可視化
多平臺機型適配,方案不能依賴root
不能有Android的API版本限制(因為需要兼容多個系統(tǒng)版本)
監(jiān)控流程可自動化執(zhí)行
過程需要連續(xù)可靠
測試準備

理解FPS的概念

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

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

獲取FPS的可行性方案

在查閱了很多的資料,也學(xué)習(xí)了同行前輩們的各種記錄后,我根據(jù)自己需要將可行性方案的范圍縮小到三種。

方案一
通過 [設(shè)置]->[開發(fā)者選項]->[GPU呈現(xiàn)模式分析] ->[在屏幕上顯示為條形圖] 進行直觀的取樣,截圖如下:

設(shè)置完成后的效果是這樣的:

屏幕下方的柱形圖會持續(xù)刷新,最上方會有一根綠色的線,代表的是16ms的閾值,超過這個界限表示當(dāng)前幀繪制的時間出現(xiàn)了延遲,及卡頓現(xiàn)象,后面會詳細介紹原因。橫坐標表示時間的持續(xù),每一根柱形圖表示當(dāng)前幀的繪制時間。因此我們在使用的過程中,下面的柱形圖會一直的刷新,單位是ms。各位看官是否有注意到每一幀的柱形圖顏色不一樣呢(注:不同手機的顏色不一樣,僅限安卓4.0以上版本參考)?下圖是官網(wǎng)提供的比較典型的GPU渲染卡頓的例子:

繪制過程中的不同顏色具有不同的含義,詳細解釋請移步>> 官網(wǎng) 查看更多。

那么是不是說我只需要打開界面去數(shù)一下超過綠色閾值的柱狀圖有多少就可以觀察我們應(yīng)用的流暢度了?然而并沒有,因為這個方式獲取到的渲染時間只是UI主線程上的繪制行為,目前我所接手的項目,采用的方式是捕捉相機的數(shù)據(jù)然后放到GPU中去進行繪制,有單獨的繪制線程,單獨的視圖,所以這個方案并不適合我手上的項目。

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

** 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 版本以上才支持,而且需要拖動屏幕產(chǎn)生的數(shù)據(jù)才比較準確,對于我這種需要適配多種機型和版本的情況就不太符合了,關(guān)于這個命令的詳細解釋,各位可以看一下谷歌官方的>>Document。

方案三
adb shell dumpsys SurfaceFlinger --latency com.qtest.demo/com.qtest.DemoActivity
1
執(zhí)行這條命令以后,你會得到這樣的信息:

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

第一行是設(shè)備的刷新周期refresh-period,單位是納秒,換算成毫秒就是16666667\1000\1000≈16.67ms;這條命令的含義是獲取當(dāng)前l(fā)ayer(窗口、圖層)的最近128幀的信息(僅保存128幀),所以上面我省略的部分實際上總共有128行(刨去第一行刷新率,需要按127幀來換算),這三列數(shù)據(jù)的解釋是(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,屏幕從圖形芯片獲取每幀的數(shù)據(jù),然后逐行進行繪制。理想狀況下,你期望顯示屏在繪制完一幀之后,圖形芯片整好能提供新幀的數(shù)據(jù)。圖像撕裂的狀況就發(fā)生在圖形芯片在圖像繪制到一半的時候,就載入了新一幀的數(shù)據(jù),以致你最終得到的數(shù)據(jù)幀是半個幀的新數(shù)據(jù)和半個幀的老數(shù)據(jù)。而垂直同步,顧名思義就是用來同步的。它告知GPU在載入新幀之前,要等待屏幕繪制完成前一幀。
沒有垂直同步的示意圖

有垂直同步的示意圖

【掉幀jank】
統(tǒng)計硬件掉幀數(shù)的計算方式是:在一個刷新周期內(nèi)(16.67ms)會記錄127行繪制時間,其中包括起始繪制(上面第一列的時間數(shù)據(jù))和完成繪制兩個納秒時間(上面第三列的時間數(shù)據(jù)),每兩行的繪制時間間隔應(yīng)該相等,否則就發(fā)生了幀延遲,即掉幀(jank),每個周期內(nèi)發(fā)生掉幀就計數(shù)1次,在整個繪制周期內(nèi)的掉幀數(shù)即是渲染掉幀的數(shù)量。
測試指標&范圍

搞清楚一些基礎(chǔ)概念后,我們需要確定接下來測試需要獲取的數(shù)據(jù),本次測試除了常規(guī)的數(shù)據(jù)以外,業(yè)務(wù)方也通過程序埋點輸出了一些關(guān)注的指標信息,概況起來包括但不限于以下幾點:

  • 組件初始化時間(業(yè)務(wù)方埋點)
  • APP啟動時間(冷啟動、熱啟動)
  • CPU占用(活動、靜默狀態(tài))
  • PSS內(nèi)存占用(活動、靜默狀態(tài))//因為不能root手機,所以沒有取USS
  • 電池溫度變化(活動、靜默狀態(tài))
  • FPS
  • 硬件渲染掉幀數(shù)
  • 單幀渲染平均時間
  • 單幀檢測處理時間(業(yè)務(wù)方埋點)

OS版本:4.4、5.0、5.1、4.2、4.3、6.0、4.0
品牌覆蓋:華為、小米、Nexus、VIVO、奇酷、酷派、三星、錘子
分辨率覆蓋:1920X1080、2560X1440、1280X720、854X480
業(yè)務(wù)場景:針對業(yè)務(wù)特點進行設(shè)置的測試場景,在此不表。

采樣策略

在調(diào)研了網(wǎng)上各種方案后,個人覺得 @sandman 的方案是最符合我的預(yù)期,因此直接拿來使用。方案如下:

  1. 通過命令:dumpsys SurfaceFlinger | grep "|....|"獲取當(dāng)前置頂窗口名稱
  2. 歷史記錄127行數(shù)據(jù),按60幀算可記錄2.12S數(shù)據(jù),從而不用頻繁獲取。(最終考慮設(shè)定1.6S間隔刷新數(shù)據(jù)。)
  3. 定期清零重新記錄,避免如何分清哪些數(shù)據(jù)是上次的。命令:dumpsys SurfaceFlinger --latency-clear
  4. 有刷新則計算幀率,無刷新則不輸出數(shù)據(jù),有時候取到的fps為1,就是這個原因
  5. 每次采樣數(shù)據(jù)大于等于1幀則計算FPS,丟幀率,最大幀間隔
  6. 針對業(yè)務(wù)需求,增加了單幀平均渲染時間的統(tǒng)計

具體實現(xiàn)

網(wǎng)上的實現(xiàn)方式大多數(shù)都是取自于這里:
https://github.com/ChromiumWebApps/chromium/blob/master/build/android/pylib/perf/surface_stats_collector.py
掌握python開發(fā)的同學(xué)可以閱讀和學(xué)習(xí)一下作者的采集思路。針對源代碼,有篇博客做了詳細的解讀,感興趣的同學(xué)可以看這里:
http://blog.csdn.net/itfootball/article/details/43084527
我采取的實現(xiàn)方式是@sandman的辦法,利用shell文件在手機內(nèi)部獲取數(shù)據(jù)后生成csv文件,再通過adb命令pull到本地進行統(tǒng)計,針對我自己的需求,對shell代碼略微做了調(diào)整,增加了單幀平均渲染時間的統(tǒng)計,運行效果像這樣:

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容