Android App性能評(píng)測(cè)分析-流暢度篇

1.前言

在手機(jī)App競(jìng)爭(zhēng)越來越激烈的今天,Android App的各項(xiàng)性能特別是流暢度不如IOS,安卓基于java虛擬機(jī)運(yùn)行,觸控響應(yīng)的延遲和卡頓比IOS系統(tǒng)嚴(yán)重得多。一些下拉上滑、雙指縮放快速打字等操作,安卓的流暢度都表現(xiàn)比較糟糕,但是,對(duì)于App使用過程是否流暢,一直沒有一個(gè)可靠的指標(biāo)將用戶的客觀感受和數(shù)據(jù)一一對(duì)應(yīng)。雖然之前有FPS(每秒幀數(shù))作為游戲或視頻類App的性能指標(biāo),但對(duì)于那些界面更新不多的App來說,仍不是一個(gè)合適的衡量數(shù)據(jù)。以下會(huì)根據(jù)實(shí)際app性能測(cè)試案例,展開進(jìn)行app性能評(píng)測(cè)之流暢度進(jìn)行原理分析和評(píng)測(cè)總結(jié)。

1.1流暢度相關(guān)概念了解

刷新率vs幀率

刷新率:每秒屏幕刷新次數(shù),手機(jī)屏幕的刷新率是60HZ
幀率:GPU在一秒內(nèi)繪制的幀數(shù)

撕裂vs掉幀

撕裂

因?yàn)槠聊坏乃⑿逻^程是自上而下、自左向右的,如果幀率>刷新率,當(dāng)屏幕還沒有刷新n-1幀的數(shù)據(jù)時(shí),就開始生成第n幀的數(shù)據(jù)了,從上到下,覆蓋第n-1幀。如果此時(shí)刷新屏幕,就會(huì)出現(xiàn)圖像的上半部分是第n幀的,下半部分是第n幀的現(xiàn)象。CPU/GPU一直都在渲染。

丟幀

Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)GPU對(duì)UI進(jìn)行渲染,如果你的某個(gè)操作花費(fèi)時(shí)間是24ms,系統(tǒng)在得到VSYNC信號(hào)的時(shí)候由于還沒有準(zhǔn)備好,就無法進(jìn)行更新任何內(nèi)容,那么用戶在32ms內(nèi)看到的會(huì)是同一幀畫面(卡頓現(xiàn)象),即丟幀現(xiàn)象。

單緩存 vs VSYNC vs 雙緩存 vs 三緩存

  • 單緩存(沒有引入VSync )

    GPU向緩存中寫入數(shù)據(jù),屏幕從緩存中讀取數(shù)據(jù),刷新后顯示。由于刷新率和幀率并不總是一致的,很可能導(dǎo)致撕裂的現(xiàn)象。為了解決單緩存的畫面撕裂問題,出現(xiàn)了雙緩存和 VSync 。

    單緩存.png
  • VSYNC 和 雙緩存

    雙緩存使用了兩個(gè)緩存區(qū): Back Buffer 、 Frame Buffer。當(dāng)寫入下一幀時(shí),GPU會(huì)先填充 Back Buffer 中,當(dāng)刷新屏幕時(shí),屏幕從 Frame Buffer 中讀數(shù)據(jù)。VSYNC 主要是完成幀的復(fù)制,開始下一幀的渲染。

image.png

當(dāng)幀率大于刷新頻率時(shí),通過使幀率被迫跟刷新頻率保持同步,從而避免畫面撕裂的現(xiàn)象(只有當(dāng) VSync 信號(hào)產(chǎn)生時(shí), CPU/GPU 才會(huì)開始繪制)。當(dāng)VSync 信號(hào)產(chǎn)生時(shí),先完成Back Buffer 到 Frame Buffer的復(fù)制操作(通過交換內(nèi)存地址),然后通知 CPU/GPU 繪制下一幀圖像。也只有VSync 信號(hào)發(fā)生時(shí),才繪制下一幀。

image.png

當(dāng)刷新頻率>幀率時(shí),此時(shí)刷新屏幕,發(fā)出VSYNC 信號(hào),由于CPU/GPU的渲染操作還沒有完成,就不把Back Buffer的數(shù)據(jù)復(fù)制到 Frame Buffer,此時(shí)就從Frame Buffer去取舊數(shù)據(jù),這樣在兩個(gè)刷新周期里,顯示的是同一幀數(shù)據(jù)。

  • 三重緩存
image.png

雙重緩存的缺陷在于:當(dāng) CPU/GPU 繪制一幀的時(shí)間超過 16 ms 時(shí),會(huì)產(chǎn)生 Jank。更要命的是,產(chǎn)生 Jank 的那一幀的顯示期間,GPU/CPU 都是在閑置的。
如下圖,A、B 和 C 都是 Buffer。
如果有第三個(gè) Buffer 能讓 CPU/GPU 在這個(gè)時(shí)候繼續(xù)工作,那就完全可以避免第二個(gè) Jank 的發(fā)生了!


image.png

1.2 VSync垂直同步

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

Drawing without VSync.png

Drawing with VSync.png

VSync機(jī)制.png

1.3 理解FPS原理

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

系統(tǒng)獲取FPS的原理是:1s 內(nèi) SurfaceFLinger 提交到屏幕的幀數(shù)。
計(jì)算公式:1000ms / 60 frames ≈ 16.67 ms/frames

原來的測(cè)試產(chǎn)品的流暢度,F(xiàn)PS是一個(gè)重要的指標(biāo),但是用了一段時(shí)間后,人們就發(fā)現(xiàn)了這樣兩個(gè)問題

Question:
1)為什么有時(shí)候FPS很低,但是我們卻不覺得App卡頓?
2)App停止操作之后,F(xiàn)PS還是一直在變化,這樣的情況是否會(huì)影響FPS的準(zhǔn)確度?

后來測(cè)試人員分析了系統(tǒng)獲取FPS的原理后,找到了那兩個(gè)問題的答案:

Answer:
1)有時(shí)候FPS很低,我們卻感覺不到卡頓,因?yàn)楸緛砭陀貌坏侥敲锤叩腇PS,比如畫一個(gè)動(dòng)畫只畫了0.5秒就畫完了,那么FPS最高也只有30幀/秒(標(biāo)準(zhǔn)是60幀/每秒),但這并不代表它是卡頓的,用0.5秒動(dòng)畫就畫完了,不能為了湊夠60幀/秒,在做個(gè)1s的動(dòng)畫吧。而如果屏幕根本沒有繪制需求,即屏幕顯示的畫面是靜止的,那FPS就為0。
2)App停止操作后FPS還一直變化,是因?yàn)槠聊幻恳粠暮铣啥际轻槍?duì)手機(jī)里的所有進(jìn)程,那么即使你的App停止了繪制,手機(jī)里其他進(jìn)程可能還在繪制,比如通知欄的各種消息,這會(huì)導(dǎo)致FPS繼續(xù)變化。

2. 如何計(jì)算流暢度

從上一節(jié)的原理分析來看,對(duì)流暢度的評(píng)價(jià)沒有一個(gè)既定的測(cè)量標(biāo)準(zhǔn)。不同的應(yīng)用有相對(duì)適應(yīng)的計(jì)算方式,總結(jié)如下:

系統(tǒng)合成幀率(FPS):數(shù)據(jù)形式最為直觀(FPS 是最早的顯示性能指標(biāo),而且在多個(gè)平臺(tái)中都有著類似的定義),且對(duì)系統(tǒng)平臺(tái)的要求最低(API level 1),游戲、視頻等連續(xù)繪制的應(yīng)用可以考慮選用,但不適用于絕大多數(shù)非連續(xù)繪制的應(yīng)用;
流暢度(SM):數(shù)據(jù)形式與 FPS 類似,可以很好的彌補(bǔ) FPS 無法準(zhǔn)確刻畫非連續(xù)繪制的應(yīng)用顯示性能的缺陷;
應(yīng)用跳幀次數(shù)、幅度(Aggregate frame stats):除了對(duì)系統(tǒng)平臺(tái)有較高的要求以外,其采集方式最為簡(jiǎn)單(系統(tǒng)自帶功能);
丟幀(Skipped frames):與 Aggregate frame stats 類似, 信息量相對(duì)較少,但可適用范圍更廣

2.1 FPS獲取

APP需要盡可能的超過24幀/秒,接近60幀/秒的速度,并且在使用的過程中保持這個(gè)速率,因此這意味著我們的程序需要在16.67ms內(nèi)處理一幅畫面內(nèi)的所有事,并保持住這個(gè)狀態(tài)。
計(jì)算公式:1000ms / 60 frames ≈ 16.67 ms/frames

2.1.1 GPU呈現(xiàn)模式分析

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

GPU條形圖.png

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

2.1.2 通過gfxinfo獲取

操作:設(shè)備連接usb數(shù)據(jù)線,使用adb調(diào)試工具,隨后對(duì)返回的數(shù)據(jù)進(jìn)行適當(dāng)處理便可以得到此時(shí)此刻app的fps。
adb shell dumpsys gfxinfo yourpackagename
解讀
Draw:是消耗在構(gòu)建java顯示列表DisplayList的時(shí)間。說白了就是執(zhí)行每一個(gè)View的onDraw方法,創(chuàng)建或者更新每一個(gè)View的DisplayList對(duì)象的時(shí)間。
Process:表示是消耗在Android的2D渲染器執(zhí)行顯示列表的時(shí)間,view越多,要執(zhí)行的繪圖命令就越多,時(shí)間就越長(zhǎng)
Execute:消耗在排列每個(gè)發(fā)送過來的幀的順序的時(shí)間.或者說是CPU告訴GPU渲染一幀的時(shí)間,這是一個(gè)阻塞調(diào)用,因?yàn)镃PU會(huì)一直等待GPU發(fā)出接到命令的回復(fù)。所以這個(gè)時(shí)間,一般都很短。

Draw + Prepare+Process + Execute = 完整顯示一幀 ,這個(gè)時(shí)間要小于16ms才能保存每秒60幀。

將數(shù)據(jù)復(fù)制到excel中,然后將數(shù)據(jù)生成“堆積柱形圖”如下:

image.png

image.png

缺點(diǎn): 這種方式是最普遍也是最常用的一種,但在使用上有明顯的痛點(diǎn),一是設(shè)備需要連接usb,二是必須是Android M 版本以上才支持,三是adb命令返回的數(shù)據(jù)并不是實(shí)時(shí)fps,需要經(jīng)過處理才能得到,因此不能在測(cè)試app的過程中實(shí)時(shí)顯示fps

2.1.3 通過系統(tǒng)層級(jí)SurfaceFlinger獲取

原理:在 Android 系統(tǒng)中,SurfaceFlinger 扮演了系統(tǒng)中所有 Surface 的管理者的角色,當(dāng)應(yīng)用程序所對(duì)應(yīng)的 Surface 更新之后,絕大多數(shù)的 Surface 都將在 SurfaceFlinger 之中完成了合并的工作之后,最終才會(huì)在 Screen 上顯示出來。
知道android繪制原理的人應(yīng)該能明白,SurfaceFlinger就是負(fù)責(zé)繪制Android應(yīng)用程序UI的服務(wù),所以surfaceFlinger能反應(yīng)出整體繪制情況,一般正常情況都是連續(xù)的,如果出現(xiàn)空檔,一種是沒有操作或者滑動(dòng)到頭,沒東西需要繪制,這種屬于正常,另一種就是有問題存在,有其他操作時(shí)間過長(zhǎng)。

操作:設(shè)備連接usb數(shù)據(jù)線,使用adb調(diào)試工具,
adb shell dumpsys SurfaceFlinger packagename

2.2 Aggregate frame stats指標(biāo)的計(jì)算方法

首先需要說明的是 Aggregate frame stats 不是一個(gè)指標(biāo),而是一系列指標(biāo)集合。我們來看一個(gè)具體的 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

以上統(tǒng)計(jì)信息的實(shí)現(xiàn)可以詳見源碼:GfxMonitorImpl.java

在 Android M 以上的系統(tǒng)上,上述信息的獲取十分方便(事實(shí)上也只有這些系統(tǒng)能夠獲取這些信息)。僅需要執(zhí)行以下命令即可:

adb shell dumpsys gfxinfo <PACKAGE_NAME>

優(yōu)點(diǎn):除了對(duì)系統(tǒng)平臺(tái)有較高的要求以外,其采集方式最為簡(jiǎn)單(系統(tǒng)自帶功能);

2.3 丟幀計(jì)算

在一次Loop時(shí)如果執(zhí)行時(shí)間超過了16.6ms,那么用多于16.6ms的時(shí)間除以16.6ms,即是當(dāng)前App的丟幀(SF: Skipped Frame)
在16.6ms完成工作卻因各種原因沒做完,占了后n個(gè)16.6ms的時(shí)間,相當(dāng)于丟了n幀
故:

SF=處理幀數(shù) / (處理幀數(shù) + 額外的垂直同步脈沖) * 60 計(jì)算(其中處理幀數(shù)常為128)
這個(gè)指標(biāo)的就是指當(dāng)前應(yīng)用在丟幀發(fā)生時(shí)的丟幀幀數(shù)。

針對(duì) Logcat 方案, 該數(shù)值直接在 Logcat 中輸出,并且?guī)в袝r(shí)間信息。

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.

針對(duì) Choreographer.FrameCallback 方案 以及 代碼注入方案,我們可能很方便的通過計(jì)算前后兩幀開始渲染的時(shí)間差獲得這一數(shù)值,同樣方便。同樣與 Logcat 方案 不同的是,它也是可以設(shè)計(jì)成實(shí)時(shí)計(jì)算的。
缺點(diǎn):Android4.2+系統(tǒng),適用于SW/HW Rendering 及 部分 OpenGL Rendering

2.4 流暢度計(jì)算

原理:VSync 機(jī)制就像一臺(tái)轉(zhuǎn)速固定的發(fā)動(dòng)機(jī)(60轉(zhuǎn)/s),每一轉(zhuǎn)帶動(dòng)著做一些 UI 相關(guān)的事情。有時(shí)候因?yàn)楦鞣N阻力, 某一圈的工作量比較重, 超過了 16.6ms, 那么這一秒內(nèi)就不是 60 轉(zhuǎn)了。
我們通過測(cè)量這個(gè)轉(zhuǎn)速,來評(píng)判應(yīng)用的流暢度。
和丟幀相對(duì),在VSync機(jī)制中1s內(nèi)Loop運(yùn)行的次數(shù)。和丟幀相對(duì)1s內(nèi)有60個(gè)Loop因?yàn)槟硯状喂ぷ鲿r(shí)間超過了16.6ms(丟幀),這樣Loop就無法運(yùn)行60次(理論最大值)。當(dāng)流暢度越小的時(shí)候說明當(dāng)前程序越卡頓。

計(jì)算方式:SM = 幀率(60) * (單位時(shí)長(zhǎng)總幀數(shù) - 單位時(shí)長(zhǎng)丟幀數(shù)) / 單位時(shí)長(zhǎng)總幀數(shù)

操作:
VSync機(jī)制客戶通過其Loop來了解當(dāng)前App最高繪制能力,其機(jī)制如下:

1)固定每隔16.6ms執(zhí)行一次;

2)如果沒有繪制事件的時(shí)候也會(huì)運(yùn)行這樣一個(gè)Loop;

3)Loop在1s之內(nèi)運(yùn)行了多少次,即可以表示當(dāng)前App繪制的最高能力,也就是App卡頓的程度。

if(存在幀的繪制):

      Loop = 1 幀繪制完成所占用的Vsync間隔

else:

      Loop = 1個(gè)Vsync間隔

所以SM計(jì)算方法為L(zhǎng)oop在1s內(nèi)運(yùn)行了多少次(Loops per seconds),那么我們可以直接在App代碼中通過Choreographer的回調(diào)FrameCallback來計(jì)算Loop被運(yùn)行了幾次,從而知道應(yīng)用的流暢度。但在實(shí)際情況下我們不一定能修改代碼(實(shí)際發(fā)布的版本不允許加入測(cè)試代碼)或者根本拿不到代碼(譬如和競(jìng)品進(jìn)行對(duì)比)。

所以介紹一種更簡(jiǎn)單直觀測(cè)量Android應(yīng)用流暢度的方法,就是通過開源測(cè)試工具GT(http://gt.qq.com)。

優(yōu)點(diǎn):數(shù)據(jù)形式與 FPS 類似,可以很好的彌補(bǔ) FPS 無法準(zhǔn)確刻畫非連續(xù)繪制的應(yīng)用顯示性能的缺陷;
缺點(diǎn):Android4.2+系統(tǒng),適用于SW/HW Rendering 及 部分 OpenGL Rendering

3.卡頓問題分析工具

分析UI卡頓我們一般都借助工具,通過工具一般都可以直觀的分析出問題原因,從而反推尋求優(yōu)化方案,具體如下細(xì)說各種強(qiáng)大的工具

3.1 Hierarchy Viewer使用

我們可以通過SDK提供的工具HierarchyViewer來進(jìn)行UI布局復(fù)雜程度及冗余等分析

通過命令啟動(dòng)HierarchyViewer

Hierarchyviewer

接下來Hierarchy window窗口打開:

Hierarchy.png

一個(gè)Activity的View樹,通過這個(gè)樹可以分析出View嵌套的冗余層級(jí),以及每個(gè)View在繪制的使用時(shí)長(zhǎng)也有表示。

3.2 使用Lint進(jìn)行資源及冗余UI布局等優(yōu)化

冗余資源及邏輯等也可能會(huì)導(dǎo)致加載和執(zhí)行緩慢,這可以使用Link工具,發(fā)現(xiàn)代碼中的流暢度性能問題;

在AndroidStudio 1.4版本中使用Lint最簡(jiǎn)單的辦法:就是將鼠標(biāo)放在代碼區(qū)點(diǎn)擊右鍵->Analyze->Inspect Code–>界面選擇你要檢測(cè)的模塊->點(diǎn)擊確認(rèn)開始檢測(cè),等待一下后會(huì)發(fā)現(xiàn)如下結(jié)果:


image.png

如果存在冗余的UI層級(jí)嵌套,會(huì)進(jìn)行高亮顯示, 我們根據(jù)提示可以點(diǎn)擊跳進(jìn)去進(jìn)行優(yōu)化處理掉的。

3.3 DDMS內(nèi)存查看器

由于Android系統(tǒng)會(huì)依據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的GC操作,常見應(yīng)用開發(fā)中導(dǎo)致GC頻繁執(zhí)行的原因主要可能是因?yàn)槎虝r(shí)間內(nèi)有大量頻繁的對(duì)象創(chuàng)建與釋放操作,也就是俗稱的內(nèi)存抖動(dòng)現(xiàn)象,或者短時(shí)間內(nèi)已經(jīng)存在大量?jī)?nèi)存暫用介于閾值邊緣,接著每當(dāng)有新對(duì)象創(chuàng)建時(shí)都會(huì)導(dǎo)致超越閾值觸發(fā)GC操作


memory monitor.png

根據(jù)內(nèi)存抖動(dòng)現(xiàn)象,查看log日志進(jìn)行分析:

image.png

如果看到,這種不停的大面積打印GC導(dǎo)致所有線程暫停的操作必定會(huì)導(dǎo)致UI視覺的卡頓,所以我們要避免此類問題的出現(xiàn),具體的常見優(yōu)化方式如下:

  1. 檢查代碼,盡量避免有些頻繁觸發(fā)的邏輯方法中存在大量對(duì)象分配;

  2. 盡量避免在多次for循環(huán)中頻繁分配對(duì)象;

  3. 避免在自定義View的onDraw()方法中執(zhí)行復(fù)雜的操作及創(chuàng)建對(duì)象(譬如Paint的實(shí)例化操作不要寫在onDraw()方法中等);

  4. 對(duì)于并發(fā)下載等類似邏輯的實(shí)現(xiàn)盡量避免多次創(chuàng)建線程對(duì)象,而是交給線程池處理。

有了上面說明GC導(dǎo)致的性能后我們就該定位分析問題了,我們可以通過運(yùn)行DDMS->Allocation Tracker標(biāo)簽打開一個(gè)新窗口,然后點(diǎn)擊Start Tracing按鈕,接著運(yùn)行你想分析的代碼,運(yùn)行完畢后點(diǎn)擊GetAllocations按鈕就能夠看見一個(gè)已分配對(duì)象的列表,如下:


image.png

3.4 Tracer for OpenGL ES

可以記錄和分析APP每一幀的繪制過程,以及列出所有乃至的OpenGL ES 的繪制函數(shù)和耗時(shí);該工具操作后會(huì)生成一份記錄App繪制過程和gltrace文件。

3.5 BlockCanary分析android卡頓

在復(fù)雜的項(xiàng)目環(huán)境中,由于歷史代碼龐大,業(yè)務(wù)復(fù)雜,包含各種第三方庫,所以在出現(xiàn)了卡頓的時(shí)候,我們很難定位到底是哪里出現(xiàn)了問題,即便知道是哪一個(gè)Activity/Fragment,也仍然需要進(jìn)去里面一行一行看,動(dòng)輒數(shù)千行的類再加上跳來跳去調(diào)來調(diào)去的,結(jié)果就是不了了之隨它去了,實(shí)在不行了再優(yōu)化吧。于是一拖再拖,最后可能壓根就改不動(dòng)了,客戶端越來越卡。

Android應(yīng)用卡頓是非常普遍的現(xiàn)象,偶爾出現(xiàn)ANR。只有當(dāng)APP出現(xiàn)ANR,我們才能得到當(dāng)前堆棧信息。當(dāng)應(yīng)用只是卡頓或只是不太流暢的時(shí)候,我們能不能找出卡頓元兇呢?不依賴Debug和源碼的情況,能不能找出卡頓的堆棧信息呢?我們需要找到一種方法來檢測(cè)哪些函數(shù)可能會(huì)使應(yīng)用發(fā)生ANR,在開發(fā)階段就能找出卡頓元兇,提高應(yīng)用流暢度。

BlockCanary就是來解決這個(gè)問題的。告別打點(diǎn),告別Debug,哪里卡頓,一目了然,讓優(yōu)化代碼變得有的放矢。
具體使用方法請(qǐng)點(diǎn)擊:

BlockCanary介紹 地址
BlockCanary — 輕松找出Android App界面卡頓元兇

4.XX銀行-性能評(píng)測(cè)對(duì)比掉幀率案例

4.1 總覽

此次質(zhì)量開放平臺(tái)-評(píng)測(cè)中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html)的性能測(cè)試的流暢度測(cè)試主要是針對(duì)場(chǎng)景頁面的掉幀率數(shù)據(jù)采集進(jìn)行對(duì)比分析, 原理公式為:掉幀率=處理幀數(shù) / (處理幀數(shù) + 額外的垂直同步脈沖) * 60 計(jì)算(其中處理幀數(shù)常為128)。一般掉幀率超過10%,我們就認(rèn)為存在卡頓有必要進(jìn)行分析定位。

4.2 掉幀率對(duì)比分析案例

這里選取了同一家銀行的兩個(gè)APP與行業(yè)競(jìng)品進(jìn)行掉幀率對(duì)比分析,從掉幀率對(duì)比看,行業(yè)競(jìng)品均值為4.1%,90分位約13.1%,75分位約27.5%,中位數(shù)約39.6%,25分位約59.9%。


掉幀率對(duì)比.png

【福建農(nóng)信】掉幀率為1.783%,表現(xiàn)良好,打敗了行業(yè)90%以上的競(jìng)品
【榕商Bank】掉幀率為6.244%,表現(xiàn)良好,打敗了行業(yè)90%以上的競(jìng)品

整體得分對(duì)比分析:
從首頁啟動(dòng)到加載完成場(chǎng)景分析,【福建農(nóng)信】實(shí)際啟動(dòng)到首頁場(chǎng)景只有一個(gè)簡(jiǎn)單的未登錄頁,相比于豐富多樣的【榕商Bank】來說屬于非常簡(jiǎn)單的頁面,但是它的掉幀率與豐富資源的【榕商Bank】比較相差不遠(yuǎn)。

【福建農(nóng)信】首頁掉幀率問題分析:
單純從頁面表象觀察,【福建農(nóng)信】啟動(dòng)時(shí),未登錄頁是從APP背景頁下方飄進(jìn)漸漸上升在頁面中間,然后抖動(dòng)一下再靜止,有一種PPT飛入的動(dòng)態(tài)效果。
通過深入分析得出【福建農(nóng)信】應(yīng)用交互中主線程存在卡頓,存在 Activity(LoginActivity)切換過慢的現(xiàn)象:


image.png

cn.com.fjnx.mobilebank.per.activity.account.LoginActivity.onCreate(阻塞1639 ms)
com.yitong.fjnx.mbank.android.Splash.onCreate(阻塞1717 ms)
建議【福建農(nóng)信】?jī)?yōu)化啟動(dòng)時(shí)未登錄頁進(jìn)入的方式

【榕商Bank】首頁掉幀率問題分析:
首頁加載掉幀率為8.2%,通過GPU過度繪制調(diào)試發(fā)現(xiàn):com.pingan.fstandard.activity.MainActivity存在過度繪制。實(shí)際上是因?yàn)檫\(yùn)營(yíng)Banner位有輪播動(dòng)態(tài)效果,輪播間隔時(shí)間設(shè)置的比較長(zhǎng),導(dǎo)致評(píng)測(cè)時(shí)掉幀率偏高,但是這是合理的產(chǎn)品設(shè)計(jì),而且也不影響用戶體驗(yàn)。

綜上對(duì)比,【榕商Bank】流暢度表現(xiàn)優(yōu)于【福建農(nóng)信】,【福建農(nóng)信】掉幀率仍然有優(yōu)化空間。

5.App端卡頓問題排查思路

1)UI線程卡頓

問題:UI線程中有I/O讀寫、數(shù)據(jù)庫訪問等耗時(shí)操作,導(dǎo)致UI線程卡頓;
定位及解決:TraceView 尋找卡住主線程的地方,Systrace 獲取 app 運(yùn)行是線程的信息以及 API 的執(zhí)行情況,避免在主線程執(zhí)行 IO 操作。

2) 復(fù)雜、不合理的布局或過度繪制

問題:不合理的布局雖然可以完成功能,但隨著控件數(shù)量越多、布局嵌套層次越深,展開布局花費(fèi)的時(shí)間幾乎是線性增長(zhǎng),性能也就越差;
定位及解決: 避免OverDraw導(dǎo)致的性能損耗;可以參考《Android性能優(yōu)化(二)之布局優(yōu)化面面觀》

3)同一時(shí)間動(dòng)畫執(zhí)行的次數(shù)過多

問題:同一時(shí)間動(dòng)畫執(zhí)行的次數(shù)過多,導(dǎo)致CPU或GPU負(fù)載過重;

4) 內(nèi)存使用異常導(dǎo)致卡頓

問題:內(nèi)存抖動(dòng)、內(nèi)存泄漏都會(huì)導(dǎo)致:GC的次數(shù)越多、消耗在GC上的時(shí)間越長(zhǎng),CPU花在界面繪制上的時(shí)間相應(yīng)越短;
解決:節(jié)省內(nèi)存的分配空間,盡可能的降低GC的頻率,縮短GC的平均時(shí)間;CPU不被占用,卡頓的幾率就會(huì)更低; 可以參考《Android性能優(yōu)化(四)之內(nèi)存優(yōu)化實(shí)戰(zhàn)》

5) 冗余資源及邏輯等導(dǎo)致加載和執(zhí)行緩慢;

問題:對(duì)線程開啟方式的不同選擇以及不同配置都可能導(dǎo)致卡頓的發(fā)生;

解決:任何耗時(shí)操作正確的移到異步里,類如I/O讀寫、數(shù)據(jù)庫訪問等都應(yīng)該采用異步的方式,不能有“只是一個(gè)很小的文件”之類的想法,防微杜漸;

參考:
Android 性能模式 第一季
Android性能優(yōu)化典范 - 第1季
Android性能優(yōu)化之渲染篇
Android性能優(yōu)化系列——Profile GPU Rendering
Profile GPU Rendering Walkthrough
Android 顯示原理簡(jiǎn)介
Android 4.4 Graphic系統(tǒng)詳解(2) VSYNC的生成
理解 VSync
了解Android 4.1,之三:黃油項(xiàng)目 —— 運(yùn)作機(jī)理及新鮮玩意
Hierarchy Viewer使用詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容