Android卡顿检测及优化
前言
之前在項目中做過一些Android卡頓以及性能優化的工作,但是一直沒時間總結,趁著這段時間把這部分總結一下。
卡頓
在應用開發中如果留意到log的話有時候可能會發下下面的log信息:
I/Choreographer(1200): Skipped 60 frames! The application may be doing too much work on its main thread.在大部分Android平臺的設備上,Android系統是16ms刷新一次,也就是一秒鐘60幀。要達到這種刷新速度就要求在ui線程中處理的任務時間必須要小于16ms,如果ui線程中處理時間長,就會導致跳過幀的渲染,也就是導致界面看起來不流暢,卡頓。如果用戶點擊事件5s中沒反應就會導致ANR。
幀率
即 Frame Rate,單位 fps,是指 gpu 生成幀的速率,60fps,Android中更幀率相關的類是SurfaceFlinger。
SurfaceFlinger
surfaceflinger作用是接受多個來源的圖形顯示數據,將他們合成,然后發送到顯示設備。比如打開應用,常見的有三層顯示,頂部的statusbar底部或者側面的導航欄以及應用的界面,每個層是單獨更新和渲染,這些界面都是有surfaceflinger合成一個刷新到硬件顯示。
在顯示過程中使用到了bufferqueue,surfaceflinger作為consumer方,比如windowmanager管理的surface作為生產方產生頁面,交由surfaceflinger進行合成。
VSync
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術,可以簡單的把它認為是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制,用來同步渲染,讓UI和SurfaceFlinger可以按硬件產生的VSync節奏進行工作。
安卓系統中有 2 種 VSync 信號:
1、屏幕產生的硬件 VSync: 硬件 VSync 是一個脈沖信號,起到開關或觸發某種操作的作用。
2、由 SurfaceFlinger 將其轉成的軟件 Vsync 信號:經由 Binder 傳遞給 Choreographer。
除了Vsync的機制,Android還使用了多級緩沖的手段以優化UI流程度,例如雙緩沖(A+B),在顯示buffer A的數據時,CPU/GPU就開始在buffer B中準備下一幀數據:但是不能保證每一幀CPU、GPU都運行狀態良好,可能由于資源搶占等性能問題導致某一幀GPU掉鏈子,vsync信號到來時buffer B的數據還沒準備好,而此時Display又在顯示buffer A的數據,導致后面CPU/GPU沒有新的buffer著手準備數據,導致卡頓(jank)。
卡頓原因
從系統層面上看主要以下幾個方面的原因會導致卡頓:
1. SurfaceFlinger 主線程耗時
SurfaceFlinger 負責 Surface 的合成 , 一旦 SurfaceFlinger 主線程調用超時 , 就會產生掉幀 .
SurfaceFlinger 主線程耗時會也會導致 hwc service 和 crtc 不能及時完成, 也會阻塞應用的 binder 調用, 如 dequeueBuffer \ queueBuffer 等.
2. 后臺活動進程太多導致系統繁忙
后臺進程活動太多,會導致系統非常繁忙, cpu \ io \ memory 等資源都會被占用, 這時候很容易出現卡頓問題 , 這也是系統這邊經常會碰到的問題。
dumpsys cpuinfo 可以查看一段時間內 cpu 的使用情況:
3.主線程調度不到 , 處于 Runnable 狀態
當線程為 Runnable 狀態的時候 , 調度器如果遲遲不能對齊進行調度 , 那么就會產生長時間的 Runnable 線程狀態 , 導致錯過 Vsync 而產生流暢性問題。
4、System 鎖
system_server 的 AMS 鎖和 WMS 鎖 , 在系統異常的情況下 , 會變得非常嚴重 , 如下圖所示 , 許多系統的關鍵任務都被阻塞 , 等待鎖的釋放 , 這時候如果有 App 發來的 Binder 請求帶鎖 , 那么也會進入等待狀態 , 這時候 App 就會產生性能問題 ; 如果此時做 Window 動畫 , 那么 system_server 的這些鎖也會導致窗口動畫卡頓
5、Layer過多導致 SurfaceFlinger Layer Compute 耗時
Android P 修改了 Layer 的計算方法 , 把這部分放到了 SurfaceFlinger 主線程去執行, 如果后臺 Layer 過多, 就會導致 SurfaceFlinger 在執行 rebuildLayerStacks 的時候耗時 , 導致 SurfaceFlinger 主線程執行時間過長。
從應用層來看以下會導致卡頓:
1、主線程執行時間長
主線程執行 Input \ Animation \ Measure \ Layout \ Draw \ decodeBitmap 等操作超時都會導致卡頓 。
- 1、Measure \ Layout 耗時\超時
- 2、draw耗時
- 3、Animation回調耗時
- 4、View 初始化耗時
- 5、List Item 初始化耗時
- 6、主線程操作數據庫
2、主線程 Binder 耗時
Activity resume 的時候, 與 AMS 通信要持有 AMS 鎖, 這時候如果碰到后臺比較繁忙的時候, 等鎖操作就會比較耗時, 導致部分場景因為這個卡頓, 比如多任務手勢操作。
3、WebView 性能不足
應用里面涉及到 WebView 的時候, 如果頁面比較復雜, WebView 的性能就會比較差, 從而造成卡頓
4、幀率與刷新率不匹配
如果屏幕幀率和系統的 fps 不相符 , 那么有可能會導致畫面不是那么順暢. 比如使用 90 Hz 的屏幕搭配 60 fps 的動畫。
卡頓檢測
卡頓檢測可以使用以下多種方法同時進行:
1、使用dumpsys gfxinfo
2、使用Systrace獲取相關信息
3、使用LayoutInspect 檢測布局層次
4、使用BlockCanary
5、利用Choreographer。
6、使用嚴格模式(StrictMode )。
1、使用dumpsys gfxinfo
在開發過程中發現有卡頓發生時可以使用下面的命令來獲取卡頓相關的信息:
adb shell dumpsys gfxinfo [PACKAGE_NAME]輸入這個命令后可能會打印下面的信息:
Applications Graphics Acceleration Info: Uptime: 102809662 Realtime: 196891968 ** Graphics info for pid 31148 [com.android.settings] ** Stats since: 524615985046231ns Total frames rendered: 8325 Janky frames: 729 (8.76%) 90th percentile: 13ms 95th percentile: 20ms 99th percentile: 73ms Number Missed Vsync: 294 Number High input latency: 47 Number Slow UI thread: 502 Number Slow bitmap uploads: 44 Number Slow issue draw commands: 135上面參數說明:
Graphics info for pid 31148 [com.android.settings]: 表明當前dump的為設置界面的幀信息,pid為31148
Total frames rendered: 8325 本次dump搜集了8325幀的信息
Janky frames :729 (8.76%)出現卡頓的幀數有729幀,占8.76%
Number Missed Vsync: 294 垂直同步失敗的幀
Number Slow UI thread: 502 因UI線程上的工作導致超時的幀數
Number Slow bitmap uploads: 44 因bitmap的加載耗時的幀數
Number Slow issue draw commands: 135 因繪制導致耗時的幀數
2、使用systrace
上面使用的dumpsys是能發現問題或者判斷問題的嚴重性,但無法定位真正的原因。如果要定位原因,應當配合systrace工具使用。
systrace使用
Systrace可以幫助分析應用是如何設備上運行起來的,它將系統和應用程序線程集中在一個共同的時間軸上,分析systrace的第一步需要在程序運行的時間段中抓取trace log,在抓取到的trace文件中,包含了這段時間中想要的關鍵信息,交互情況。
圖1顯示的是當一個app在滑動時出現了卡頓的現象,默認的界面下,橫軸是時間,縱向為trace event,trace event 先按進程分組,然后再按線程分組.從上到下的信息分別為Kernel,SurfaceFlinger,應用包名。通過配置trace的分類,可以根據配置情況記錄每個應用程序的所有線程信息以及trace event的層次結構信息。
Android studio中使用systrace
1、在android設備的 設置 – 開發者選項 – 監控 – 開啟traces。
2、選擇要追中的類別,并且點擊確定。
完成以上配置后,開始抓trace文件
$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html分析trace文件
抓到trace.html文件后,通過web瀏覽器打開
檢查Frames
每個應用程序都有一排代表渲染幀的圓圈,通常為綠色,如果繪制的時間超過16.6毫秒則顯示黃色或紅色。通過“W”鍵查看幀。
trace應用程序代碼
在framework中的trace marker并沒有覆蓋到所有代碼,因此有些時候需要自己去定義trace marker。在Android4.3之后,可以通過Trace類在代碼中添加標記,這樣將能夠看到在指定時間內應用的線程在做哪些工作,當然,trace 的begin和end操作也會增加一些額外的開銷,但都只有幾微秒左右。
通過下面的例子來說明Trace類的 用法。
3 、使用BlockCanary
BlockCanary是國內開發者MarkZhai開發的一套性能監控組件,它對主線程操作進行了完全透明的監控,并能輸出有效的信息,幫助開發分析、定位到問題所在,迅速優化應用。
其特點有:
1、非侵入式,簡單的兩行就打開監控,不需要到處打點,破壞代碼優雅性。
2、精準,輸出的信息可以幫助定位到問題所在(精確到行),不需要像Logcat一樣,慢慢去找。
3、目前包括了核心監控輸出文件,以及UI顯示卡頓信息功能
BlockCanary基本原理
android應用程序只有一個主線程ActivityThread,這個主線程會創建一個Looper(Looper.prepare),而Looper又會關聯一個MessageQueue,主線程Looper會在應用的生命周期內不斷輪詢(Looper.loop),從MessageQueue取出Message 更新UI。
public static void loop() {...for (;;) {...// This must be in a local variable, in case a UI event sets the loggerPrinter logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}msg.target.dispatchMessage(msg);if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}...} }BlockCanary主要是檢測msg.target.dispatchMessage(msg);之前的>>>>> Dispatching to 和之后的<<<<< Finished to的間隔時間。
應用發生卡頓,一定是在dispatchMessage中執行了耗時操作。通過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,如果超出閥值,表示發生卡頓,則dump出各種信息,提供開發者分析性能瓶頸。
4、使用Choreographer
Android 主線程運行的本質,其實就是 Message 的處理過程,我們的各種操作,包括每一幀的渲染操作 ,都是通過 Message 的形式發給主線程的 MessageQueue ,MessageQueue 處理完消息繼續等下一個消息。
Choreographer 的引入,主要是配合 Vsync ,給上層 App 的渲染提供一個穩定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統通過對 Vsync 信號周期的調整,來控制每一幀繪制操作的時機. 目前大部分手機都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系統為了配合屏幕的刷新頻率,將 Vsync 的周期也設置為 16.6 ms,每個 16.6 ms , Vsync 信號喚醒 Choreographer 來做 App 的繪制操作 ,這就是引入 Choreographer 的主要作用。
Choreographer 兩個主要作用
1、承上:負責接收和處理 App 的各種更新消息和回調,等到 Vsync 到來的時候統一處理。比如集中處理 Input(主要是 Input 事件的處理) 、Animation(動畫相關)、Traversal(包括 measure、layout、draw 等操作) ,判斷卡頓掉幀情況,記錄 CallBack 耗時等。
2、啟下:負責請求和接收 Vsync 信號。接收 Vsync 事件回調(通過 FrameDisplayEventReceiver.onVsync );請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .
使用Choreographer 計算幀率
Choreographer 處理繪制的邏輯核心在 Choreographer.doFrame 函數中,從下圖可以看到,FrameDisplayEventReceiver.onVsync post 了自己,其 run 方法直接調用了 doFrame 開始一幀的邏輯處理:
Choreographer周期性的在UI重繪時候觸發,在代碼中記錄上一次和下一次繪制的時間間隔,如果超過16ms,就意味著一次UI線程重繪的“丟幀”。丟幀的數量為間隔時間除以16,如果超過3,就開始有卡頓的感知。
使用Choreographer檢測幀的代碼如下:
卡頓優化
由上面的分析可知對象分配、垃圾回收(GC)、線程調度以及Binder調用 是Android系統中常見的卡頓原因,因此卡頓優化主要以下幾種方法,更多的要結合具體的應用來進行:
1、布局優化
- 通過減少冗余或者嵌套布局來降低視圖層次結構。比如使用約束布局代替線性布局和相對布局。
- 用 ViewStub 替代在啟動過程中不需要顯示的 UI 控件。
- 使用自定義 View 替代復雜的 View 疊加。
2、減少主線程耗時操作
- 主線程中不要直接操作數據庫,數據庫的操作應該放在數據庫線程中完成。
- sharepreference盡量使用apply,少使用commit,可以使用MMKV框架來代替sharepreference。
- 網絡請求回來的數據解析盡量放在子線程中,不要在主線程中進行復制的數據解析操作。
- 不要在activity的onResume和onCreate中進行耗時操作,比如大量的計算等。
3、減少過度繪制
過度繪制是同一個像素點上被多次繪制,減少過度繪制一般減少布局背景疊加等方式,如下圖所示右邊是過度繪制的圖片。
4、列表優化
- RecyclerView使用優化,使用DiffUtil和notifyItemDataSetChanged進行局部更新等。
5、對象分配和回收優化
自從Android引入 ART 并且在Android 5.0上成為默認的運行時之后,對象分配和垃圾回收(GC)造成的卡頓已經顯著降低了,但是由于對象分配和GC有額外的開銷,它依然又可能使線程負載過重。 在一個調用不頻繁的地方(比如按鈕點擊)分配對象是沒有問題的,但如果在在一個被頻繁調用的緊密的循環里,就需要避免對象分配來降低GC的壓力。
- 減少小對象的頻繁分配和回收操作。
參考文獻
1、https://source.android.google.cn/devices/graphics/implement-vsync
2、https://www.bradcypert.com/what-is-androids-surfaceflinger/
3、https://ashishb.net/tech/demystifying-android-rendering/
4、https://devblogs.microsoft.com/xamarin/tips-for-creating-a-smooth-and-fluid-android-ui/
5、https://developer.android.com/training/testing/performance
6、https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/
7、https://developer.android.com/tools/help/systrace.html
8、https://zhuanlan.zhihu.com/p/87954949
總結
以上是生活随笔為你收集整理的Android卡顿检测及优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java基础面试题及答案
- 下一篇: Android指南针应用