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