擺脫Android的“好朋友”卡頓ANR、不流暢、死機!
背景介紹
Android性能測試一直存在測試維度少,測試數據難收集,已收集數據難量化的特點,這些特點是因為Android手機版本碎片化、硬件多樣化、App功能復雜造成的。
移動端性能測試的特性,總的來說,可以分為卡頓ANR測試、流暢度測試、電量測試、流量測試。一個APP為什么需要性能測試,總的來說就是存在一些不嚴謹的代碼,在低端機型造成卡頓,對手機上有限電量的浪費,昂貴流量的浪費,造成用戶流失。測試人員需要通過性能測試來地發現和定位上述問題。
在穩定性測試章節中介紹的smartmonkey不僅是個穩定性測試工具,還集成了多個性能測試工具,下面重點介紹下卡頓ANR測試和流量度測試兩個工具的原理和應用。
卡頓ANR
卡頓ANR與Android就是天生的朋友,從Android第一天誕生直到現在的8核CPU,Android還是未能擺脫頁面不流暢,卡,死機的詬病,所以個人認為卡頓ANR測試是性能測試最主要的一塊。
卡頓簡單的來說,就是手機沒有及時響應、頁面延遲,出現丟幀的現象,或者點擊無響應。絕大多數的卡頓,稍等片刻系統就會恢復正常,但假如超過5S,就可能會引發手機ANR,造成更高級別的警告。如圖所示:
什么會引發ANR?
在Android里,應用程序的響應性是由ActivityManager和WindowManager系統服務監視的。當它監測到以下情況中的一個時,Android就會針對特定的應用程序顯示ANR:
ANR 一般有三種類型:
1.KeyDispatchTimeout(5 seconds) -- 主要類型按鍵或觸摸事件在特定時間內無響應
2.BroadcastTimeout(10 seconds) --BroadcastReceiver 在特定時間內無法處理完成
3.ServiceTimeout(20 seconds) -- 小概率類型 Service 在特定的時間內無法處理完成
這三種原因都會造成 ANR ,但是第一種情況基本上占了所有 ANR 的百分之九十以上,第三種的情況微乎其微。這三種 ANR 不是孤立的,有可能會相互影響。例如一個應用程序進程中同時有一個正在顯示的 Activity 和一個正在處理消息的 BroadcastReceiver ,它們都運行在這個進程的主線程中。如果 BR 的 onReceive 函數沒有返回,此時用戶點擊屏幕,而 onReceive 超過 5 秒仍然沒有返回,主線程無法處理用戶輸入事件,就會引起第 1 種 ANR 。如果繼續超過 10 秒沒有返回,又會引起第 2 種 ANR 。發生 ANR 的主要原因是潛在的耗時操作,例如網絡或數據庫操作,或者高耗時的計算如改變位圖尺寸,應該在子線程里(或者以數據庫操作為例,通過異步請求的方式)來完成。然而,不是說你的主線程阻塞在那里等待子線程的完成——也不是調用 Thread.wait() 或是 Thread.sleep() 。替代的方法是,主線程應該為子線程提供一個 Handler ,以便完成時能夠提交給主線程。以這種方式設計你的應用程序,將能保證你的主線程保持對輸入的響應性并能避免由于 5 秒輸入事件的超時引發的 ANR 對話框。
三種 ANR 發生時都會在 log 中輸出錯誤信息,你會發現各個應用進程和系統進程的函數堆棧信息都輸出到了一個 /data/anr/traces.txt 的文件中, ROOT 手機導出該文件后,分析此日志可以得出一些結論,但 traces 文件信息比較抽象,難理解。總的來說,日志難收集,結果難分析。
如何避免ANR?
1 、運行在主線程里的任何方法都盡可能少做事情。特別是, Activity 應該在它的關鍵生命周期方法(如 onCreate() 和 onResume() )里盡可能少的去做創建操作。(可以采用重新開啟子線程的方式,然后使用 Handler+Message 的方式做一些操作,比如更新主線程中的 ui 等)
2 、應用程序應該避免在 BroadcastReceiver 里做耗時的操作或計算。但不再是在子線程里做這些任務(因為 BroadcastReceiver 的生命周期短),替代的是,如果響應 Intent 廣播需要執行一個耗時的動作的話,應用程序應該啟動一個 Service 。(此處需要注意的是可以在廣播接受者中啟動 Service ,但是卻不可以在 Service 中啟動 broadcasereciver, 關于原因后續會有介紹,此處不是本文重點)
3 、避免在 IntentReceiver 里啟動一個 Activity ,因為它會創建一個新的畫面,并從當前用戶正在運行的程序上搶奪焦點。如果你的應用程序在響應 Intent 廣播時需要向用戶展示什么,你應該使用 Notification Manager 來實現。
TraceView 是 android SDK 中自帶的一個性能測試工具,可以在 Tools 目錄下找到。 TraceView 能很精確的查看到每一個類,每一個類方法的執行時間。 APP 卡頓我們只需要保留當時的 traceview 文件,通過查看該文件,就可以定位絕大部分問題。
通過 Debug.startMethodTracing ( StringFileName )和 Debug.stopMethodTracing() 來記錄一段時間內方法執行情況。
在主線程中不停的插入一個輕量級別的操作,如果該變量在指定的時間內,沒有改變,則說明此刻APP卡頓。卡頓工具工具就是根據 https://github.com/SalomonBrys/ANR-WatchDog 項目改進而成的。把ANR這種警告變成錯誤讓APP閃退,持久化當時信息。
原理圖:
實例:百度國際化瀏覽器ANR檢測和問題定位
問題背景:
百度國際化瀏覽器初次安裝App,點擊icon后,明顯卡頓或者ANR,QA手工測試無法定位,RD優化代碼多次依舊找不到問題的節點。
測試方法:
百度國際化瀏覽器加入卡頓工具jar,代碼中初始化。
測試結果分析:
在打開traceview 文件后,通過 realTime/Call 從大到小排序,找到對應的與代碼相關消耗時間最大的方法。
我們能夠看到很明顯的看出FrameWindow.initDataBase()方法占用CPU過長達到3S左右了,距離5S很接近,通過查看代碼,結合業務邏輯,得知此處為數據庫初始化,并且主要是標簽數據庫初始化。
從整個APP來看,啟動頁面初始化標簽數據庫并沒有錯,但是此刻本身邏輯就非常多,標簽數據庫初始化后并沒有馬上使用到,而是到二級頁面才有查詢動作,總的來說,就是增加資源緊張。
建議:標簽數據庫什么時候使用,什么時候初始化。建議放到二級頁面初始化,減少頁面App頁面啟動的負荷,減少冷啟動時間,避免卡頓和ANR。并且標簽數據庫初始化放在線程中。
流暢度測試
流暢度測試簡單的來說就是Android頁面繪制。Android系統每秒60hz,也就是大約每16ms刷新一次界面。但是在我們使用APP過程中,經常會看到頁面有卡頓,或者說丟幀的現象。也就是說可能此刻兩個頁面繪制的時間差超過0.1S(人眼視覺殘留0.1S)。
在確定衡量指標之前,我們先來研究一下Android的UI更新機制。
原理分析
在確定衡量指標之前,我們先來研究一下Android的UI更新機制。
1、Android如何繪制UI?
關于Android是如何更新UI,相信已經有很多文章介紹其中的步驟以及過程,大體上可以用下圖來展示:
從圖中可以看到無論那條路走下去始終都由SurfaceFlinger來控制最后的更新。
在Android版本更新過程中,發現在JellyBean中Google加入了一個Project Butter,用來解決嚴重影響Android口碑的問題之一“UI流暢性差”的問題。而Project Butter中主要引入了三個核心元素:VSYNC(垂直同步)、Triple Buffer和Choreographer。
2、從VSYNC開始
VSync是VerticalSynchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術,可以簡單的把它認為是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制。
上圖所示是VSync機制下的繪制過程。從上圖可以看出,CPU和GPU的處理時間都少于一個VSync的間隔,即16.6ms。如果每個間隔都有繪制的情況下,當前的FPS即為60幀。
當CPU和GPU處理時間都很慢,或因為其他的原因,如在主線程中干活太多,那么就會出現如下圖這樣的狀況。
從上圖可以看到,CPU和GPU的處理時間因為各種原因都大于一個VSync的間隔(16.6ms),所以在第二個VSync還在處理1區域的繪制時,不可能實現理論上的FPS60,同時也出現了丟幀(SF: Skipped Frame)情況。
為了便于理解,上圖用的是雙Buffer機制的情況,實際上Android4.1引入了Triple Buffer,所以當雙Buffer不夠用時,Triple Buffer丟幀的情況如下圖所示。
VSync機制就像是播放動畫片(60幀/s)。每次都會播放畫面,有的時候有人偷懶了,機器壞了,就會出現播放速度降低的狀況。我們把這個播放速度叫做流暢度。
3、從FPS&丟幀到流暢度(SM:SMoothness)
實際上在很多Android的App中,很少有需要不斷地去繪制的場景,很多時候頁面都是靜態的。也就是會出現這樣的狀況,雖然1s中VSync的60個Loop不是每個都在做繪制的工作,FPS會比較低,但并不代表這個時候程序不流暢(如我將App放著不動,實測FPS為1)。所以FPS較低并不能代表當前App在UI上界面不流暢,而1s內VSync這個Loop運行了多少次更加能說明當前App的流暢程度。所以,下面這2個指標比FPS更能代表當前的App是否處于流暢的狀態。同樣這2個指標更加能夠量化App卡頓的程度:
a)丟幀(SF:Skipped Frame):如上圖2所示情況應該在16.6ms完成工作卻因各種原因沒做完,占了后n個16.6ms的時間,相當于丟了n幀。
b) 流暢度(SM:SMoothness):和丟幀相對,在VSync機制中1s內Loop運行的次數。
和丟幀相對1s內有60個Loop因為某幾次工作時間超過了16.6ms(丟幀),這樣Loop就無法運行60次(理論最大值)。
當流暢度越小的時候說明當前程序越卡頓。
4、記數:如何得到流暢度(SM:SMoothness)
接著上面的結論,如果在這樣的機制下每次Loop運行之前進行通知,記個數就好了。
很幸運我們在新的Android的那一套機制中找到了一個畫圖的打雜工Choreographer這個對象。根據Google的官方API文檔描述中,它是用來協調animations、input以及drawing時序的,并且每個Loop共用一個Choreographer對象。
下圖為Choreographer的定義和結構。
結論
通過如上原理分析可以得出結論:
1. Android 4.1引入了VSync機制后,可以通過其Loop來了解當前App最高繪制能力。
固定每隔16.6ms執行一次(這個值是一個靜態變量,會根據系統版本不同而采用不同的值,目前測試版本是16.6ms這樣最高的刷新的幀率就控制在60FPS以內);
如果沒有以上事件的時候同樣也會運行這樣一個Loop;
這個Loop在1s之內運行了多少次,即可以表示當前App繪制的最高的能力,也就是Android App卡頓的程度;
另外,在一次Loop時如果執行時間超過了16.6ms,那么用多于16.6ms的時間除以16.6ms,即是當前App的丟幀(SF:Skipped Frame)。
2. 可以在Choreographer的回調FrameCallback中,按秒計數表示當前App的流暢程度,即流暢度SM(SMoothness)。
采用這樣方式就可以在App內部觀測當前App的流暢度了。并且在丟幀的地方打印traceView,就可以知道丟幀的大概原因,大概位置,定位代碼問題。
來自:http://qa.baidu.com/academy/detail/article/122