大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因?yàn)殇秩拘阅芪⒎澹珹ndroid系統(tǒng)每隔大概16.6ms發(fā)出的VSYNC信號(hào)燕刻,觸發(fā)對(duì)UI進(jìn)行渲染,如果每次渲染都成功沫换,這樣就能夠達(dá)到流暢的畫面所需要的60fps类浪,為了能夠?qū)崿F(xiàn)60fps载城,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。
我們通常都會(huì)提到60fps與16ms费就,可是知道為何會(huì)是以程序是否達(dá)到60fps來作為APP性能的衡量標(biāo)準(zhǔn)嗎诉瓦?這是因?yàn)槿搜叟c大腦之間的寫作無法感知超過60fps的畫面更新。
12fps大概類似手動(dòng)快速翻動(dòng)的幀率力细,這明顯是可以感知到不夠順滑的垦搬。24fps使得人眼感知的是連續(xù)線性的運(yùn)動(dòng),這其實(shí)是歸功于運(yùn)動(dòng)模糊的效果艳汽,24fps是電影膠圈通常使用的幀率猴贰,因?yàn)檫@個(gè)幀率已經(jīng)足夠支撐大部分電影畫面需要表達(dá)的內(nèi)容,同時(shí)能夠最大的減少費(fèi)用支出河狐。但是低于30fps是無法順暢表現(xiàn)絢麗的畫面內(nèi)容的米绕,此時(shí)就需要用到60fps來達(dá)到想要的效果瑟捣,當(dāng)然超過60fps是沒有必要的。
開發(fā)app的性能目標(biāo)就是保持60fps栅干,這意味著每一幀你只有16ms=1000/60的時(shí)間來處理所有的任務(wù)迈套。
如果某個(gè)操作花費(fèi)時(shí)間是24ms,系統(tǒng)在得到VSYNV信號(hào)的時(shí)候就無法進(jìn)行正常渲染碱鳞,這樣就發(fā)生了丟幀現(xiàn)象桑李,那么用戶在32ms內(nèi)看到的會(huì)是同一幀畫面。
有很多原因可以導(dǎo)致丟幀窿给,一般主線程過多的UI繪制贵白、大量的IO操作或是大量操作占用CPU,都會(huì)導(dǎo)致App界面卡頓崩泡。
更詳細(xì)的描述請(qǐng)參考Android圖形顯示系統(tǒng)https://blog.csdn.net/a740169405/article/details/70548443
卡頓分析工具CPU Profile介紹
最基本的介紹參考官網(wǎng)https://developer.android.google.cn/studio/profile/cpu-profiler
實(shí)際的操作中如何使用呢禁荒,如果你發(fā)現(xiàn)你的某個(gè)操作有點(diǎn)卡頓,這時(shí)候你就可以使用這個(gè)工具進(jìn)行查找原因角撞,先點(diǎn)擊cpu profile按鈕呛伴,這時(shí)候有兩種配置方式
Sample Java Methods
對(duì)Java方法采樣:在應(yīng)用的Java代碼執(zhí)行期間,頻繁捕獲應(yīng)用的調(diào)用堆棧谒所,分析器會(huì)比較捕獲的數(shù)據(jù)集热康,以推導(dǎo)與應(yīng)用的Java代碼執(zhí)行有關(guān)的時(shí)間和資源使用信息。如果應(yīng)用在捕獲調(diào)用堆棧后進(jìn)入一個(gè)方法并在下次捕獲前退出該方法劣领,分析器將不會(huì)記錄該方法調(diào)用姐军。如果您想要跟蹤生命周期如此短的方法,應(yīng)使用檢測跟蹤剖踊。
Trace Java Methods
跟蹤 Java 方法:在運(yùn)行時(shí)檢測應(yīng)用庶弃,以在每個(gè)方法調(diào)用開始和結(jié)束時(shí)記錄一個(gè)時(shí)間戳衫贬。系統(tǒng)會(huì)收集并比較這
些時(shí)間戳德澈,以生成方法跟蹤數(shù)據(jù),包括時(shí)間信息和 CPU 使用率固惯。
可以根據(jù)自己的需要進(jìn)行選擇梆造,然后我們點(diǎn)擊Record進(jìn)行記錄,這時(shí)候我們進(jìn)行手機(jī)上面的操作葬毫,當(dāng)操作完成后點(diǎn)擊 Stop recording镇辉,這時(shí)候就會(huì)生成對(duì)應(yīng)的跟蹤信息
然后我們可以進(jìn)行分析,逐步排查卡頓的原因贴捡。
還有一種方式可以生成對(duì)應(yīng)的跟蹤信息忽肛,在開始記錄的位置使用startMethodTracingSampling(String tracePath, int bufferSize,int intervalUs)進(jìn)行采樣
,三個(gè)參數(shù)代表生成trace文件的位置烂斋,文件大小還有采樣的時(shí)間間隔
或者startMethodTracing(String tracePath, int bufferSize, int flags)進(jìn)行跟蹤,
三個(gè)參數(shù)代表生成trace文件的位置屹逛,文件大小還有一個(gè)標(biāo)志設(shè)置础废,bufferSize參數(shù)指定了trace文件的最大值。Trace信息可以使用很多空間罕模,你的存儲(chǔ)器可能是有限的评腺,所以嘗試去使用一個(gè)可接受的值(默認(rèn)8M)。安卓現(xiàn)在只定義一個(gè)標(biāo)志淑掌,Debug.TRACE_COUNT_ALLOCS.
設(shè)置了之后我們就可以在對(duì)應(yīng)的位置找到trace文件蒿讥,使用AS工具打開進(jìn)行問題查找了。
布局優(yōu)化
層級(jí)優(yōu)化
使用Layout Inspector
然后選擇需要查看的進(jìn)程與Activity:
Layout Inspector主要用來分析布局的層級(jí)結(jié)構(gòu)抛腕,減少不必要的層級(jí)芋绸,更加詳細(xì)的介紹參考官網(wǎng)https://developer.android.google.cn/studio/debug/layout-inspector?hl=zh_cn
使用merge標(biāo)簽
merge標(biāo)簽是用來幫助在視圖樹中減少重復(fù)布局的,當(dāng)某個(gè)layout布局被多次引用時(shí)兽埃,可以使用merge侥钳,減少嵌套
可參考文章https://blog.csdn.net/a740169405/article/details/50473909
使用ViewStub標(biāo)簽
當(dāng)我們布局中存在一個(gè)View/ViewGroup,在某個(gè)特定時(shí)刻才需要它的展示時(shí)柄错,可能會(huì)使用gone或者invisible舷夺,在需要顯示時(shí)再設(shè)置visible可見。
viewStub是一個(gè)輕量級(jí)的view售貌,它不可見给猾,不用占用資源,只有設(shè)置viewstub為visible或者調(diào)用其inflater()方法時(shí)颂跨,其對(duì)應(yīng)的布局文件才會(huì)被初始化敢伸。
可參考詳細(xì)的文章https://blog.csdn.net/a740169405/article/details/50351013
過度渲染
過度渲染是指系統(tǒng)在渲染單個(gè)幀的過程中多次在屏幕上繪制某一個(gè)像素,例如恒削,如果我們有若干界面卡片堆疊在一起池颈,每張卡片都會(huì)遮蓋其下面一張卡片的部分內(nèi)容,但是钓丰,系統(tǒng)仍然需要繪制堆疊中的卡片被遮蓋的部分躯砰。
GPU過度繪制檢查
手機(jī)開發(fā)者選項(xiàng)中能夠顯示過度渲染檢查功能,通過對(duì)界面進(jìn)行彩色編碼來幫我們識(shí)別過度繪制携丁,開啟步驟如下:
1.進(jìn)入開發(fā)者選項(xiàng)
2.找到調(diào)試GPU過度繪制
3.在彈出的對(duì)話框中琢歇,選擇顯示過度繪制區(qū)域
Android 將按如下方式為界面元素著色,以確定過度繪制的次數(shù):
更加詳細(xì)的介紹參考官網(wǎng)https://developer.android.google.cn/topic/performance/rendering/inspect-gpu-rendering?hl=zh_cn
解決過度繪制問題
可以通過以下方法來減少過度繪制
1.移除布局中不需要的背景
默認(rèn)情況下梦鉴,布局沒有背景李茫,這表示布局本身不會(huì)直接渲染任何內(nèi)容,但是當(dāng)布局具有背景時(shí)肥橙,其有可能會(huì)導(dǎo)致過度繪制
移除不必要的背景可以快速提高渲染性能魄宏,不必要的背景可能永遠(yuǎn)不可見,因?yàn)樗鼤?huì)被在該視圖上繪制的任何其他內(nèi)容完全覆蓋存筏,例如宠互,當(dāng)系統(tǒng)在父視圖上繪制子視圖時(shí)塔次,可能會(huì)完全覆蓋父視圖的背景。
2.使視圖層次結(jié)構(gòu)扁平化
可以通過優(yōu)化視圖層次結(jié)構(gòu)來減少重疊界面對(duì)象的數(shù)量名秀,從而提高性能励负。
3.降低透明度
對(duì)于不透明的view,只需要渲染一次就可以顯示出來匕得,但是如果這個(gè)view設(shè)置了alpha值继榆,則至少需要渲染兩次,這是因?yàn)槭褂昧薬lpha的view需要先知道混合view的下一層元素是什么汁掠,然后再結(jié)合上層的view進(jìn)行混色處理略吨。透明動(dòng)畫、淡入淡出和陰影等都涉及到某種透明度考阱,這就會(huì)造成了過度繪制翠忠。可以通過減少要渲染的透明對(duì)象的數(shù)量乞榨,來改善這些情況下的過度繪制秽之,例如,如需獲得灰色文本吃既,可以在TextView中繪制黑色文本,再為其設(shè)置半透明的透明度值,但是,簡單地通過用灰色繪制文本也能獲得同樣的效果,而且能夠大幅提升性能考榨。
布局加載優(yōu)化
LayoutInflater加載xml布局的過程會(huì)在主線程使用IO讀取XML布局文件進(jìn)行XML解析,再根據(jù)解析結(jié)果利用反射 創(chuàng)建布局中的View/ViewGroup對(duì)象鹦倚。這個(gè)過程隨著布局的復(fù)雜度上升河质,耗時(shí)自然也會(huì)隨之增大。Android為我們 提供了 Asynclayoutinflater 把耗時(shí)的加載操作在異步線程中完成震叙,最后把加載結(jié)果再回調(diào)給主線程掀鹅。
dependencies {
implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
}
new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
//......
} });
1、使用異步 inflate媒楼,那么需要這個(gè) layout 的 parent 的 generateLayoutParams 函數(shù)是線程安全的; 2乐尊、所有構(gòu)建的 View 中必須不能創(chuàng)建 Handler 或者是調(diào)用 Looper.myLooper;(因?yàn)槭窃诋惒骄€程中加載的,異
步線程默認(rèn)沒有調(diào)用 Looper.prepare );
3匣砖、AsyncLayoutInflater 不支持設(shè)置 LayoutInflater.Factory 或者 LayoutInflater.Factory2; 4科吭、不支持加載包含 Fragment 的 layout
5昏滴、如果 AsyncLayoutInflater 失敗猴鲫,那么會(huì)自動(dòng)回退到UI線程來加載布局