1.前言
在手機App競爭越來越激烈的今天,Android App的各項性能特別是流暢度不如IOS,安卓基於java虛擬機運行,觸控響應的延遲和卡頓比IOS系統嚴重得多。一些下拉上滑、雙指縮放快速打字等操作,安卓的流暢度都表現比較糟糕,但是,對於App使用過程是否流暢,一直沒有一個可靠的指標將用戶的客觀感受和數據一一對應。雖然之前有FPS(每秒幀數)作為游戲或視頻類App的性能指標,但對於那些界面更新不多的App來說,仍不是一個合適的衡量數據。以下會根據實際app性能測試案例,展開進行app性能評測之流暢度進行原理分析和評測總結。
1.1流暢度相關概念了解
刷新率vs幀率
刷新率:每秒屏幕刷新次數,手機屏幕的刷新率是60HZ
幀率:GPU在一秒內繪制的幀數
撕裂vs掉幀
撕裂
因為屏幕的刷新過程是自上而下、自左向右的,如果幀率>刷新率,當屏幕還沒有刷新n-1幀的數據時,就開始生成第n幀的數據了,從上到下,覆蓋第n-1幀。如果此時刷新屏幕,就會出現圖像的上半部分是第n幀的,下半部分是第n幀的現象。CPU/GPU一直都在渲染。
丟幀
Android系統每隔16ms發出VSYNC信號,觸發GPU對UI進行渲染,如果你的某個操作花費時間是24ms,系統在得到VSYNC信號的時候由於還沒有准備好,就無法進行更新任何內容,那么用戶在32ms內看到的會是同一幀畫面(卡頓現象),即丟幀現象。
單緩存 vs VSYNC vs 雙緩存 vs 三緩存
-
單緩存(沒有引入VSync )
GPU向緩存中寫入數據,屏幕從緩存中讀取數據,刷新后顯示。由於刷新率和幀率並不總是一致的,很可能導致撕裂的現象。為了解決單緩存的畫面撕裂問題,出現了雙緩存和 VSync 。
單緩存
-
VSYNC 和 雙緩存
雙緩存使用了兩個緩存區: Back Buffer 、 Frame Buffer。當寫入下一幀時,GPU會先填充 Back Buffer 中,當刷新屏幕時,屏幕從 Frame Buffer 中讀數據。VSYNC 主要是完成幀的復制,開始下一幀的渲染。
當幀率大於刷新頻率時,通過使幀率被迫跟刷新頻率保持同步,從而避免畫面撕裂的現象(只有當 VSync 信號產生時, CPU/GPU 才會開始繪制)。當VSync 信號產生時,先完成Back Buffer 到 Frame Buffer的復制操作(通過交換內存地址),然后通知 CPU/GPU 繪制下一幀圖像。也只有VSync 信號發生時,才繪制下一幀。

當刷新頻率>幀率時,此時刷新屏幕,發出VSYNC 信號,由於CPU/GPU的渲染操作還沒有完成,就不把Back Buffer的數據復制到 Frame Buffer,此時就從Frame Buffer去取舊數據,這樣在兩個刷新周期里,顯示的是同一幀數據。
-
三重緩存

雙重緩存的缺陷在於:當 CPU/GPU 繪制一幀的時間超過 16 ms 時,會產生 Jank。更要命的是,產生 Jank 的那一幀的顯示期間,GPU/CPU 都是在閑置的。
如下圖,A、B 和 C 都是 Buffer。
如果有第三個 Buffer 能讓 CPU/GPU 在這個時候繼續工作,那就完全可以避免第二個 Jank 的發生了!

1.2 VSync垂直同步
在Android版本更新過程中,發現在Jelly Bean中Google加入了一個Project Butter,用來解決嚴重影響Android口碑的問題之一“UI流暢性差”的問題。
而Project Butter中主要引入了三個核心元素:VSYNC(垂直同步)、Triple Buffer和Choreographer。
VSync是VerticalSynchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術,可以簡單的把它認為是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制。CPU和GPU的處理時間都少於一個VSync的間隔,即16.6ms。如果每個間隔都有繪制的情況下,當前的FPS即為60幀。VSync機制就像是播放動畫片(60幀/s)。每次都會播放畫面,有的時候有人偷懶了,機器壞了,就會出現播放速度降低的狀況。我們把這個播放速度叫做流暢度。



1.3 理解FPS原理
FPS即Frames per second,>>點擊這篇文章,解釋的非常清楚。
用過flash的人應該知道動畫片其實是由一張張畫出來的圖片連貫執行產生的效果,當一張張獨立的圖片切換速度足夠快的時候,會欺騙我們的眼睛,以為這是連續的動作。反之類推,當你的圖片切換不夠快的時候,就會被人眼看穿,反饋給用戶的就是所謂的卡頓現象。
想要讓大腦覺得動作是連續的,至少是每秒10-12幀的速度,而想達到流暢的效果,至少需要每秒24幀。這也是為什么電影片源通常都是24幀的原因,好奇的同學點擊>>知乎高知
看看大神的解答。不過60幀每秒的流暢度是最佳的,我們的目標就是讓程序的流暢度能接近60幀每秒,當然超過60幀速的話大部分人還是會受不了的。
系統獲取FPS的原理是:1s 內 SurfaceFLinger 提交到屏幕的幀數。
計算公式:1000ms / 60 frames ≈ 16.67 ms/frames
原來的測試產品的流暢度,FPS是一個重要的指標,但是用了一段時間后,人們就發現了這樣兩個問題
Question:
1)為什么有時候FPS很低,但是我們卻不覺得App卡頓?
2)App停止操作之后,FPS還是一直在變化,這樣的情況是否會影響FPS的准確度?
后來測試人員分析了系統獲取FPS的原理后,找到了那兩個問題的答案:
Answer:
1)有時候FPS很低,我們卻感覺不到卡頓,因為本來就用不到那么高的FPS,比如畫一個動畫只畫了0.5秒就畫完了,那么FPS最高也只有30幀/秒(標准是60幀/每秒),但這並不代表它是卡頓的,用0.5秒動畫就畫完了,不能為了湊夠60幀/秒,在做個1s的動畫吧。而如果屏幕根本沒有繪制需求,即屏幕顯示的畫面是靜止的,那FPS就為0。
2)App停止操作后FPS還一直變化,是因為屏幕每一幀的合成都是針對手機里的所有進程,那么即使你的App停止了繪制,手機里其他進程可能還在繪制,比如通知欄的各種消息,這會導致FPS繼續變化。
2. 如何計算流暢度
從上一節的原理分析來看,對流暢度的評價沒有一個既定的測量標准。不同的應用有相對適應的計算方式,總結如下:
系統合成幀率(FPS):數據形式最為直觀(FPS 是最早的顯示性能指標,而且在多個平台中都有着類似的定義),且對系統平台的要求最低(API level 1),游戲、視頻等連續繪制的應用可以考慮選用,但不適用於絕大多數非連續繪制的應用;
流暢度(SM):數據形式與 FPS 類似,可以很好的彌補 FPS 無法准確刻畫非連續繪制的應用顯示性能的缺陷;
應用跳幀次數、幅度(Aggregate frame stats):除了對系統平台有較高的要求以外,其采集方式最為簡單(系統自帶功能);
丟幀(Skipped frames):與 Aggregate frame stats 類似, 信息量相對較少,但可適用范圍更廣
2.1 FPS獲取
APP需要盡可能的超過24幀/秒,接近60幀/秒的速度,並且在使用的過程中保持這個速率,因此這意味着我們的程序需要在16.67ms內處理一幅畫面內的所有事,並保持住這個狀態。
計算公式:1000ms / 60 frames ≈ 16.67 ms/frames
2.1.1 GPU呈現模式分析
操作方法:通過 [設置]->[開發者選項]->[GPU呈現模式分析] ->[在屏幕上顯示為條形圖] 進行直觀的取樣,截圖如下:

解讀:屏幕下方的柱形圖會持續刷新,最上方會有一根綠色的線,代表的是16ms的閾值,超過這個界限表示當前幀繪制的時間出現了延遲,及卡頓現象,后面會詳細介紹原因。橫坐標表示時間的持續,每一根柱形圖表示當前幀的繪制時間。因此我們在使用的過程中,下面的柱形圖會一直的刷新,單位是ms。各位看官是否有注意到每一幀的柱形圖顏色不一樣呢(注:不同手機的顏色不一樣,僅限安卓4.0以上版本參考)
優點:可直接在手機上操作,方便直觀
缺點:這個方式獲取到的渲染時間只是UI主線程上的繪制行為。“GPU呈現模式分析”的數據只能說明個現象,比如上面提到的數據,能說明在實際運行中會有短暫的長時間繪制問題。但造成問題的具體原因並沒有說明。
而且“GPU呈現模式分析”顯示的是最后128幀的數據,但丟幀也有可能是兩幀之間存在長時間的操作而造成的。

2.1.2 通過gfxinfo獲取
操作:設備連接usb數據線,使用adb調試工具,隨后對返回的數據進行適當處理便可以得到此時此刻app的fps。adb shell dumpsys gfxinfo yourpackagename
解讀:
Draw:是消耗在構建java顯示列表DisplayList的時間。說白了就是執行每一個View的onDraw方法,創建或者更新每一個View的DisplayList對象的時間。
Process:表示是消耗在Android的2D渲染器執行顯示列表的時間,view越多,要執行的繪圖命令就越多,時間就越長
Execute:消耗在排列每個發送過來的幀的順序的時間.或者說是CPU告訴GPU渲染一幀的時間,這是一個阻塞調用,因為CPU會一直等待GPU發出接到命令的回復。所以這個時間,一般都很短。
Draw + Prepare+Process + Execute = 完整顯示一幀 ,這個時間要小於16ms才能保存每秒60幀。
將數據復制到excel中,然后將數據生成“堆積柱形圖”如下:


缺點: 這種方式是最普遍也是最常用的一種,但在使用上有明顯的痛點,一是設備需要連接usb,二是必須是Android M 版本以上才支持,三是adb命令返回的數據並不是實時fps,需要經過處理才能得到,因此不能在測試app的過程中實時顯示fps
2.1.3 通過系統層級SurfaceFlinger獲取
原理:在 Android 系統中,SurfaceFlinger 扮演了系統中所有 Surface 的管理者的角色,當應用程序所對應的 Surface 更新之后,絕大多數的 Surface 都將在 SurfaceFlinger 之中完成了合並的工作之后,最終才會在 Screen 上顯示出來。
知道android繪制原理的人應該能明白,SurfaceFlinger就是負責繪制Android應用程序UI的服務,所以surfaceFlinger能反應出整體繪制情況,一般正常情況都是連續的,如果出現空檔,一種是沒有操作或者滑動到頭,沒東西需要繪制,這種屬於正常,另一種就是有問題存在,有其他操作時間過長。
操作:設備連接usb數據線,使用adb調試工具,adb shell dumpsys SurfaceFlinger packagename
2.2 Aggregate frame stats指標的計算方法
首先需要說明的是 Aggregate frame stats 不是一個指標,而是一系列指標集合。我們來看一個具體的 Aggregate frame stats 的例子:
Stats since: 752958278148ns
Total frames rendered: 82189
Janky frames: 35335 (42.99%)
90th percentile: 34ms
95th percentile: 42ms
99th percentile: 69ms
Number Missed Vsync: 4706
Number High input latency: 142
Number Slow UI thread: 17270
Number Slow bitmap uploads: 1542
Number Slow draw: 23342
以上統計信息的實現可以詳見源碼:GfxMonitorImpl.java
在 Android M 以上的系統上,上述信息的獲取十分方便(事實上也只有這些系統能夠獲取這些信息)。僅需要執行以下命令即可:
adb shell dumpsys gfxinfo <PACKAGE_NAME>
優點:除了對系統平台有較高的要求以外,其采集方式最為簡單(系統自帶功能);
2.3 丟幀計算
在一次Loop時如果執行時間超過了16.6ms,那么用多於16.6ms的時間除以16.6ms,即是當前App的丟幀(SF: Skipped Frame)
在16.6ms完成工作卻因各種原因沒做完,占了后n個16.6ms的時間,相當於丟了n幀
故:
SF=處理幀數 / (處理幀數 + 額外的垂直同步脈沖) * 60 計算(其中處理幀數常為128)
這個指標的就是指當前應用在丟幀發生時的丟幀幀數。
針對 Logcat 方案, 該數值直接在 Logcat 中輸出,並且帶有時間信息。
04-18 16:31:24.957 I/Choreographer(24164): Skipped 4 frames! The application may be doing too much work on its main thread.
04-18 16:31:25.009 I/Choreographer(24164): Skipped 2 frames! The application may be doing too much work on its main thread.
針對 Choreographer.FrameCallback 方案 以及 代碼注入方案,我們可能很方便的通過計算前后兩幀開始渲染的時間差獲得這一數值,同樣方便。同樣與 Logcat 方案 不同的是,它也是可以設計成實時計算的。
缺點:Android4.2+系統,適用於SW/HW Rendering 及 部分 OpenGL Rendering
2.4 流暢度計算
原理:VSync 機制就像一台轉速固定的發動機(60轉/s),每一轉帶動着做一些 UI 相關的事情。有時候因為各種阻力, 某一圈的工作量比較重, 超過了 16.6ms, 那么這一秒內就不是 60 轉了。
我們通過測量這個轉速,來評判應用的流暢度。
和丟幀相對,在VSync機制中1s內Loop運行的次數。和丟幀相對1s內有60個Loop因為某幾次工作時間超過了16.6ms(丟幀),這樣Loop就無法運行60次(理論最大值)。當流暢度越小的時候說明當前程序越卡頓。
計算方式:SM = 幀率(60) * (單位時長總幀數 - 單位時長丟幀數) / 單位時長總幀數
操作:
VSync機制客戶通過其Loop來了解當前App最高繪制能力,其機制如下:
1)固定每隔16.6ms執行一次;
2)如果沒有繪制事件的時候也會運行這樣一個Loop;
3)Loop在1s之內運行了多少次,即可以表示當前App繪制的最高能力,也就是App卡頓的程度。
if(存在幀的繪制):
Loop = 1 幀繪制完成所占用的Vsync間隔
else: Loop = 1個Vsync間隔
所以SM計算方法為Loop在1s內運行了多少次(Loops per seconds),那么我們可以直接在App代碼中通過Choreographer的回調FrameCallback來計算Loop被運行了幾次,從而知道應用的流暢度。但在實際情況下我們不一定能修改代碼(實際發布的版本不允許加入測試代碼)或者根本拿不到代碼(譬如和競品進行對比)。
所以介紹一種更簡單直觀測量Android應用流暢度的方法,就是通過開源測試工具GT(http://gt.qq.com)。
優點:數據形式與 FPS 類似,可以很好的彌補 FPS 無法准確刻畫非連續繪制的應用顯示性能的缺陷;
缺點:Android4.2+系統,適用於SW/HW Rendering 及 部分 OpenGL Rendering
3.卡頓問題分析工具
分析UI卡頓我們一般都借助工具,通過工具一般都可以直觀的分析出問題原因,從而反推尋求優化方案,具體如下細說各種強大的工具
3.1 Hierarchy Viewer使用
我們可以通過SDK提供的工具HierarchyViewer來進行UI布局復雜程度及冗余等分析
通過命令啟動HierarchyViewer
Hierarchyviewer
接下來Hierarchy window窗口打開:

一個Activity的View樹,通過這個樹可以分析出View嵌套的冗余層級,以及每個View在繪制的使用時長也有表示。
3.2 使用Lint進行資源及冗余UI布局等優化
冗余資源及邏輯等也可能會導致加載和執行緩慢,這可以使用Link工具,發現代碼中的流暢度性能問題;
在AndroidStudio 1.4版本中使用Lint最簡單的辦法:就是將鼠標放在代碼區點擊右鍵->Analyze->Inspect Code–>界面選擇你要檢測的模塊->點擊確認開始檢測,等待一下后會發現如下結果:

如果存在冗余的UI層級嵌套,會進行高亮顯示, 我們根據提示可以點擊跳進去進行優化處理掉的。
3.3 DDMS內存查看器
由於Android系統會依據內存中不同的內存數據類型分別執行不同的GC操作,常見應用開發中導致GC頻繁執行的原因主要可能是因為短時間內有大量頻繁的對象創建與釋放操作,也就是俗稱的內存抖動現象,或者短時間內已經存在大量內存暫用介於閾值邊緣,接着每當有新對象創建時都會導致超越閾值觸發GC操作

根據內存抖動現象,查看log日志進行分析:

如果看到,這種不停的大面積打印GC導致所有線程暫停的操作必定會導致UI視覺的卡頓,所以我們要避免此類問題的出現,具體的常見優化方式如下:
-
檢查代碼,盡量避免有些頻繁觸發的邏輯方法中存在大量對象分配;
-
盡量避免在多次for循環中頻繁分配對象;
-
避免在自定義View的onDraw()方法中執行復雜的操作及創建對象(譬如Paint的實例化操作不要寫在onDraw()方法中等);
-
對於並發下載等類似邏輯的實現盡量避免多次創建線程對象,而是交給線程池處理。
有了上面說明GC導致的性能后我們就該定位分析問題了,我們可以通過運行DDMS->Allocation Tracker標簽打開一個新窗口,然后點擊Start Tracing按鈕,接着運行你想分析的代碼,運行完畢后點擊GetAllocations按鈕就能夠看見一個已分配對象的列表,如下:

3.4 Tracer for OpenGL ES
可以記錄和分析APP每一幀的繪制過程,以及列出所有乃至的OpenGL ES 的繪制函數和耗時;該工具操作后會生成一份記錄App繪制過程和gltrace文件。
3.5 BlockCanary分析android卡頓
在復雜的項目環境中,由於歷史代碼龐大,業務復雜,包含各種第三方庫,所以在出現了卡頓的時候,我們很難定位到底是哪里出現了問題,即便知道是哪一個Activity/Fragment,也仍然需要進去里面一行一行看,動輒數千行的類再加上跳來跳去調來調去的,結果就是不了了之隨它去了,實在不行了再優化吧。於是一拖再拖,最后可能壓根就改不動了,客戶端越來越卡。
Android應用卡頓是非常普遍的現象,偶爾出現ANR。只有當APP出現ANR,我們才能得到當前堆棧信息。當應用只是卡頓或只是不太流暢的時候,我們能不能找出卡頓元凶呢?不依賴Debug和源碼的情況,能不能找出卡頓的堆棧信息呢?我們需要找到一種方法來檢測哪些函數可能會使應用發生ANR,在開發階段就能找出卡頓元凶,提高應用流暢度。
BlockCanary就是來解決這個問題的。告別打點,告別Debug,哪里卡頓,一目了然,讓優化代碼變得有的放矢。
具體使用方法請點擊:
BlockCanary介紹 地址
BlockCanary — 輕松找出Android App界面卡頓元凶
4.XX銀行-性能評測對比掉幀率案例
4.1 總覽
此次質量開放平台-評測中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html)的性能測試的流暢度測試主要是針對場景頁面的掉幀率數據采集進行對比分析, 原理公式為:掉幀率=處理幀數 / (處理幀數 + 額外的垂直同步脈沖) * 60 計算(其中處理幀數常為128)。一般掉幀率超過10%,我們就認為存在卡頓有必要進行分析定位。
4.2 掉幀率對比分析案例
這里選取了同一家銀行的兩個APP與行業競品進行掉幀率對比分析,從掉幀率對比看,行業競品均值為4.1%,90分位約13.1%,75分位約27.5%,中位數約39.6%,25分位約59.9%。

【福建農信】掉幀率為1.783%,表現良好,打敗了行業90%以上的競品
【榕商Bank】掉幀率為6.244%,表現良好,打敗了行業90%以上的競品
整體得分對比分析:
從首頁啟動到加載完成場景分析,【福建農信】實際啟動到首頁場景只有一個簡單的未登錄頁,相比於豐富多樣的【榕商Bank】來說屬於非常簡單的頁面,但是它的掉幀率與豐富資源的【榕商Bank】比較相差不遠。
【福建農信】首頁掉幀率問題分析:
單純從頁面表象觀察,【福建農信】啟動時,未登錄頁是從APP背景頁下方飄進漸漸上升在頁面中間,然后抖動一下再靜止,有一種PPT飛入的動態效果。
通過深入分析得出【福建農信】應用交互中主線程存在卡頓,存在 Activity(LoginActivity)切換過慢的現象:![]()
cn.com.fjnx.mobilebank.per.activity.account.LoginActivity.onCreate(阻塞1639 ms)
com.yitong.fjnx.mbank.android.Splash.onCreate(阻塞1717 ms)
建議【福建農信】優化啟動時未登錄頁進入的方式
【榕商Bank】首頁掉幀率問題分析:
首頁加載掉幀率為8.2%,通過GPU過度繪制調試發現:com.pingan.fstandard.activity.MainActivity存在過度繪制。實際上是因為運營Banner位有輪播動態效果,輪播間隔時間設置的比較長,導致評測時掉幀率偏高,但是這是合理的產品設計,而且也不影響用戶體驗。
綜上對比,【榕商Bank】流暢度表現優於【福建農信】,【福建農信】掉幀率仍然有優化空間。
5.App端卡頓問題排查思路
1)UI線程卡頓
問題:UI線程中有I/O讀寫、數據庫訪問等耗時操作,導致UI線程卡頓;
定位及解決:TraceView 尋找卡住主線程的地方,Systrace 獲取 app 運行是線程的信息以及 API 的執行情況,避免在主線程執行 IO 操作。
2) 復雜、不合理的布局或過度繪制
問題:不合理的布局雖然可以完成功能,但隨着控件數量越多、布局嵌套層次越深,展開布局花費的時間幾乎是線性增長,性能也就越差;
定位及解決: 避免OverDraw導致的性能損耗;可以參考《Android性能優化(二)之布局優化面面觀》
3)同一時間動畫執行的次數過多
問題:同一時間動畫執行的次數過多,導致CPU或GPU負載過重;
4) 內存使用異常導致卡頓
問題:內存抖動、內存泄漏都會導致:GC的次數越多、消耗在GC上的時間越長,CPU花在界面繪制上的時間相應越短;
解決:節省內存的分配空間,盡可能的降低GC的頻率,縮短GC的平均時間;CPU不被占用,卡頓的幾率就會更低; 可以參考《Android性能優化(四)之內存優化實戰》
5) 冗余資源及邏輯等導致加載和執行緩慢;
問題:對線程開啟方式的不同選擇以及不同配置都可能導致卡頓的發生;
解決:任何耗時操作正確的移到異步里,類如I/O讀寫、數據庫訪問等都應該采用異步的方式,不能有“只是一個很小的文件”之類的想法,防微杜漸;
參考:
Android 性能模式 第一季
Android性能優化典范 - 第1季
Android性能優化之渲染篇
Android性能優化系列——Profile GPU Rendering
Profile GPU Rendering Walkthrough
Android 顯示原理簡介
Android 4.4 Graphic系統詳解(2) VSYNC的生成
理解 VSync
了解Android 4.1,之三:黃油項目 —— 運作機理及新鮮玩意
Hierarchy Viewer使用詳解
作者:蕭竹
鏈接:https://www.jianshu.com/p/642f47989c7c
來源:簡書
插播:金融壹賬通質量開放平台現提供測試一站式解決方案,包括UI自動化、測試過程管理、app評測、接口自動化、接口壓測、輿情監控等測試服務,歡迎訪問:http://fit-stg1.jryzt.com/Hyperion-server/html/index.html。