欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化

 更新時(shí)間:2023年02月12日 14:04:58   作者:SugarTurboS?Team  
這篇文章主要為大家介紹了Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

某輪測(cè)試發(fā)現(xiàn),我們的設(shè)備運(yùn)行一個(gè)第三方的App時(shí),卡頓感非常明顯:

  • 界面加載很慢,菊花轉(zhuǎn)半天
  • 滑屏極度不跟手,目測(cè)觀感幀率低于15
  • 對(duì)比機(jī)(競(jìng)品)也會(huì)稍微一點(diǎn)卡,但是好很多,基本不會(huì)有很大感覺(jué)的卡頓

可以初步判定我們的設(shè)備存在性能問(wèn)題,亟需優(yōu)化,拉平到競(jìng)品水準(zhǔn)。

最后發(fā)現(xiàn),這個(gè)問(wèn)題實(shí)際上是應(yīng)用自身奇怪的實(shí)現(xiàn)(getResources()的重載),加上Binder過(guò)度調(diào)用(沉重的Binder耗時(shí))導(dǎo)致的。

本文 做記錄和分享。

其中對(duì)比機(jī)配置、Android版本均與本機(jī)不同,不做變量參考。

觀測(cè)

由于這個(gè)可愛(ài)的App是第三方的App(應(yīng)用市場(chǎng)下載的),我們沒(méi)有源碼,只能從系統(tǒng)端去干涉。先抓一份trace。

1. trace體現(xiàn)UI繪制操作嚴(yán)重耗時(shí)

trace一抓一看,顯然App主線程已經(jīng)陷入困境??梢钥吹剑?/p>

  • CPU使用率并不高
  • 主線程幾乎完全在執(zhí)行Traversal工作(mersure和layout)
  • measure和layout極度耗時(shí),顯然達(dá)不到合理的幀率要求(甚至連PPT幀率都趕不上)

可以看到,這份trace表明App的整個(gè)measure和layout工作存在整體性的不合理耗時(shí)。但并不能準(zhǔn)確提示細(xì)節(jié),也不能看出問(wèn)題部分??梢钥隙?,耗時(shí)工作位于App層(不是指耗時(shí)原因也來(lái)自App)。

2. 排查measure和layout慢的原因:可疑的多次binder

上面可以確認(rèn)繪制緩慢造成耗時(shí)。但是一來(lái)App不是自己的,二來(lái)這么復(fù)雜的調(diào)用,通過(guò)分析調(diào)用、跟代碼來(lái)定位慢方法、慢路徑顯然足夠低效。

定位到Traversal,統(tǒng)計(jì)一下Traversal各部分的耗時(shí)占比,可以大致定位出耗時(shí)部分可能是什么業(yè)務(wù)的:

可以看到,traversal意外地包含了數(shù)量巨大的binder調(diào)用,它占據(jù)總耗時(shí)的80%+,使得應(yīng)用層繪圖超出生命線10倍以上:

  • 這次doFrame->travesal耗時(shí)接近200ms,屬于"無(wú)法使用的垃圾"級(jí)別,不是性能問(wèn)題而是故障
  • binder調(diào)用(binder transaction)次數(shù)很多,在幾毫秒的時(shí)間里(預(yù)期的一次應(yīng)用層繪圖時(shí)間)進(jìn)行了194次IPC
  • binder耗時(shí)占比很高:83%左右
  • 還有一個(gè)ioctl調(diào)用次數(shù)也很多、很耗時(shí);由于binder驅(qū)動(dòng)調(diào)用talkWithDriver()需要使用ioctl,因此這里初步判斷ioctl是binder IPC的伴生,無(wú)礙

生命線:對(duì)于60Hz的屏幕,生命線為16ms左右。但是16ms為圖形棧全鏈路的極限時(shí)間,留給應(yīng)用層的時(shí)間更低

可以確認(rèn),過(guò)多的binder調(diào)用導(dǎo)致了這個(gè)惱火的性能問(wèn)題。

3. binder:在哪、誰(shuí)為、為何頻繁調(diào)用

通常應(yīng)用(和應(yīng)用集成的庫(kù)),出于一定的目的,會(huì)通過(guò)IBinder、AIDL、封裝組件(如startService)、直接調(diào)用驅(qū)動(dòng)節(jié)點(diǎn)(talkWithDriver)等方式來(lái)進(jìn)行一次Binder IPC。

性能問(wèn)題中,與Binder IPC相關(guān)的,最常見(jiàn)的主要如下:

  • 頻繁調(diào)用Binder
  • 關(guān)鍵、敏感、緊張的位置調(diào)用Binder
  • Binder對(duì)端響應(yīng)太慢,對(duì)端繁忙
  • Binder傳遞的數(shù)據(jù)太大
  • Binder客戶(hù)端線程數(shù)超限(發(fā)起請(qǐng)求的線程滿(mǎn))
  • Binder服務(wù)端線程數(shù)超限(處理請(qǐng)求的線程滿(mǎn))

對(duì)于Binder傳遞數(shù)據(jù)太大、線程數(shù)導(dǎo)致的性能問(wèn)題,由于應(yīng)用不是自己的(不好干涉、不關(guān)注),且對(duì)比機(jī)卡頓不那么明顯(可以粗略排除),因此不太值得去看。(另外我們是在滑屏的時(shí)候卡的,主線程UIHandler也做不到并發(fā)發(fā)出Binder IPC)

這里還是展示一下怎么分析。下列命令可以提供一些關(guān)于binder狀態(tài)、traction狀態(tài)、傳遞數(shù)據(jù)大小等內(nèi)容:

cat /sys/kernel/debug/binder/failed_transaction_log
cat /sys/kernel/debug/binder/transaction_log
cat /sys/kernel/debug/binder/transactions

同樣的,我們不好關(guān)注應(yīng)用為何調(diào)用binder(因?yàn)闆](méi)有App的代碼,最近也忙的不想逆向它;但實(shí)際上最后我們知道了為何調(diào)用),也很顯然是在哪調(diào)用的(在App UI線程 performTraversal時(shí)調(diào)用的),因此先來(lái)看看這群IPC的對(duì)端是誰(shuí)。

trace一看,binder調(diào)用確實(shí)很多(畫(huà)藍(lán)紫色線部分都是binder;本是細(xì)線,溢滿(mǎn)則剛):

上圖binder調(diào)用很多,其實(shí)很多是同一種類(lèi),各IPC都最終歸屬于一類(lèi)Binder。分類(lèi)看,數(shù)量巨大、占比最高的兩類(lèi)binder(稱(chēng)為第一部分binder和第二部分binder)是值得探討的主要耗時(shí)部分。

首先,分析第一部分binder的對(duì)端。跟蹤發(fā)現(xiàn)第一部分binder“飛”往SurafceFlinger,耗時(shí)較短,次數(shù)合理,評(píng)估正常,不再跟進(jìn),不貼圖展示。

第二部分binder,從次數(shù)、耗時(shí)來(lái)看,確實(shí)可疑。它從App進(jìn)程“飛”往System_server(Framework服務(wù)層):

4. binder:頻繁調(diào)用的具體定位

性能分析的其中一個(gè)關(guān)鍵方向是找到慢方法、慢路徑。上面一步已經(jīng)體現(xiàn)了,慢是因?yàn)锳pp在敏感且關(guān)鍵的位置調(diào)用了Binder,這個(gè)binder的對(duì)端是Framework。

從系統(tǒng)側(cè)分析這個(gè)binder的性能,難以像App那樣輕松定位——因?yàn)锳pp里面有多少個(gè)調(diào)用、系統(tǒng)里面暴露了多少個(gè)binder,在哪里觸發(fā)的,都不好搞。

因此直接來(lái)粗暴的方法,把所有binder調(diào)用抓堆棧下來(lái)。

多次復(fù)現(xiàn)、多次抓取,閱讀堆棧、總結(jié)分類(lèi),可以抓到蛛絲馬跡。由于最長(zhǎng)的堆棧高達(dá)33萬(wàn)行(包含合理的正常的binder和造成性能問(wèn)題的binder),且抓了好幾份,這里只能將問(wèn)題的關(guān)鍵點(diǎn)做個(gè)展示輸出。

ls
20230209.fk.trace  binder.20230209.2.fk.trace.log  binder.20230209.4.fk.trace.log  binder.20230209.fk.trace.log  binder.20230210.1.fk.trace.log
20230209.ok.trace  binder.20230209.3.fk.trace.log  binder.20230209.5.fk.trace.log  binder.20230209.ok.trace.log

fk表示口吐芬芳,即不正常情況下的binder堆棧;ok表示正常。

其中性能故障對(duì)應(yīng)的堆棧如下(幾類(lèi)有性能問(wèn)題的binder調(diào)用;僅截取關(guān)鍵位置):

第一個(gè)堆棧放全一些,可以看出,在正常的traversal過(guò)程中,View體系正常調(diào)用getResources(),binder發(fā)生在getResources()內(nèi)部:它調(diào)用了IWindowManager.getInitialDisplayDensity(),通過(guò)binder“飛”到system_server:

Count: 15
Trace: java.lang.Throwable
	at android.os.BinderProxy.transact(BinderProxy.java:547)
	at android.view.IWindowManager$Stub$Proxy.getInitialDisplayDensity(IWindowManager.java:3025)
	at java.lang.reflect.Method.invoke(Native Method)
	at refactor.common.base.FActivity.e5(FActivity.java:7)
	at refactor.common.base.FActivity.getResources(FActivity.java:7)
	at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:1)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1632)
	at android.widget.LinearLayout.measureVertical(LinearLayout.java:922)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:801)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:331)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1632)
	at android.widget.LinearLayout.measureVertical(LinearLayout.java:922)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:801)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:331)
	at com.android.internal.policy.DecorView.onMeasure(DecorView.java:763)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3665)
	at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2302)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2564)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2026)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8469)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
	at android.view.Choreographer.doCallbacks(Choreographer.java:796)
	at android.view.Choreographer.doFrame(Choreographer.java:731)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
	at android.os.Handler.handleCallback(Handler.java:938)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:223)
	at android.app.ActivityThread.main(ActivityThread.java:8024)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:605)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

其實(shí)這里已經(jīng)能看出問(wèn)題,并且看清問(wèn)題的嚴(yán)重性了。可以說(shuō),tarversal階段是一個(gè)App最緊張、最重要的階段之一,在這個(gè)關(guān)鍵時(shí)間窗口內(nèi),還調(diào)用了binder通信這一不可靠的方法(IPC是不可預(yù)期的),對(duì)性能影響很大。

該應(yīng)用的View實(shí)現(xiàn)喜歡在traversal階段調(diào)用上述Binder,包括但不限于如下幾個(gè):

Count: 5
Trace: java.lang.Throwable
	at android.os.BinderProxy.transact(BinderProxy.java:547)
	at android.view.IWindowManager$Stub$Proxy.getInitialDisplayDensity(IWindowManager.java:3025)
	at java.lang.reflect.Method.invoke(Native Method)
	at refactor.common.base.FActivity.e5(FActivity.java:7)
	at refactor.common.base.FActivity.getResources(FActivity.java:7)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:221)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1632)
	at android.widget.LinearLayout.measureVertical(LinearLayout.java:922)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:801)
	at android.view.View.measure(View.java:25597)
	at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1463)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:803)
	at android.view.View.measure(View.java:25597)
	...
Count: 20
Trace: java.lang.Throwable
	at android.os.BinderProxy.transact(BinderProxy.java:547)
	at android.view.IWindowManager$Stub$Proxy.getInitialDisplayDensity(IWindowManager.java:3025)
	at java.lang.reflect.Method.invoke(Native Method)
	at refactor.common.base.FActivity.e5(FActivity.java:7)
	at refactor.common.base.FActivity.getResources(FActivity.java:7)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:762)
	at android.view.View.measure(View.java:25597)
	at android.widget.RelativeLayout.measureChild(RelativeLayout.java:849)
	at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:652)
	at android.view.View.measure(View.java:25597)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7114)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:331)
	at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:21)
	at android.view.View.measure(View.java:25597)
    ...

到此,已經(jīng)定位到了慢路徑了:

  • App喜歡在View的關(guān)鍵回調(diào)里面調(diào)用IPC,產(chǎn)生巨大的性能問(wèn)題
  • 在measure和traversal階段不厭其煩地調(diào)用一個(gè)通常情況很少調(diào)用的接口getInitialDisplayDensity()
  • View的加載階段、traversal階段,在measure、layout階段因Binder IPC過(guò)度頻繁觸發(fā)了性能問(wèn)題

結(jié)論

App在多個(gè)不同的View(及其子類(lèi)ViewGroup和ViewGroup的子類(lèi)們),在不合適的時(shí)機(jī)頻繁調(diào)用了Binder,以很低的CPU占用,領(lǐng)先性地實(shí)現(xiàn)了很卡的效果。

雖然卡頓的貢獻(xiàn)來(lái)自不同的View調(diào)用的同名Binder,這個(gè)binder卻是同一個(gè)接口(不易變的getInitialDisplayDensity(),這意味著返回值可以被緩存下來(lái)并確保有效),而且觸發(fā)的直接原因是同一個(gè)——App在Context.getResources()方法內(nèi)部調(diào)用了這個(gè)binder,getResources()在App運(yùn)行時(shí)會(huì)被頻繁調(diào)用(尤其是View創(chuàng)建、繪制階段)。

Context.getResources()默認(rèn)實(shí)現(xiàn)是直接返回mResources,但是會(huì)有可愛(ài)的人會(huì)override它(或通過(guò)優(yōu)美的kotlin擴(kuò)展函數(shù)),往里面塞入耗時(shí)的慢方法。

清晰的定位到了慢方法、卡頓根因后,還有一個(gè)殘酷的問(wèn)題:對(duì)比機(jī)不卡。

回答這個(gè)問(wèn)題感覺(jué)像是對(duì)自己寫(xiě)出來(lái)的卡頓型代碼有點(diǎn)欲蓋彌彰的感覺(jué)。不過(guò)經(jīng)過(guò)分析,排除掉競(jìng)品的優(yōu)化、App在競(jìng)品的Android版本(Android版本和我們不一樣)上業(yè)務(wù)邏輯不同、競(jìng)品的系統(tǒng)原生邏輯就不一樣(Android版本原生邏輯差異)這三個(gè)變量因素后,結(jié)合代碼閱讀,發(fā)現(xiàn)我們的View Tree在Measure和Layout階段,我們自己添加的功能會(huì)比原生要調(diào)用更多次的getResources()方法。

這在大多數(shù)情況下非常正常(邏輯上也正常,因?yàn)檫@個(gè)方法只有一行直接返回Resources對(duì)象實(shí)例的代碼),碰到一個(gè)在超高頻方法里面加慢調(diào)用、不可靠IPC的App后只能傻眼認(rèn)栽。

方案

從App角度看,它錯(cuò)的很離譜。優(yōu)化方案也很簡(jiǎn)單,去掉一個(gè)多余的、過(guò)度的Binder調(diào)用,一般是將調(diào)用集中在關(guān)鍵位置(臨界區(qū))以外、緩存返回值(確保返回值沒(méi)有失效的前提下重用cache)、不在高頻方法里面加?xùn)|西、盡量不override sdk方法等等。

在系統(tǒng)側(cè),本著拉平甚至超越競(jìng)品的愿景,同樣有減少binder調(diào)用的優(yōu)化目標(biāo)。不論是對(duì)應(yīng)用自身問(wèn)題的解決,還是對(duì)競(jìng)品的競(jìng)爭(zhēng)性跟進(jìn),無(wú)所謂,都出手。主要有如下一些方案:

  • Framework可以實(shí)現(xiàn)緩存,在Binder IPC發(fā)出前檢查有效性,僅在失效后真正發(fā)出IPC
  • 把我們加進(jìn)去的額外的getResources()去掉、重構(gòu)
  • 在嚴(yán)酷的競(jìng)爭(zhēng)、高標(biāo)準(zhǔn)的要求下,會(huì)將考慮一些非標(biāo)準(zhǔn)的操作(魔改)

有效性,是指IPC對(duì)端返回的內(nèi)容沒(méi)有發(fā)生改變(本質(zhì)上是軟件維護(hù)的狀態(tài)并未發(fā)生改變)。比如,從未旋轉(zhuǎn)過(guò)屏幕,那么我們上一次獲取的屏幕寬高就仍然有效,不需要再次獲取,而應(yīng)復(fù)用緩存

最終方案2采用并取得良好效果:幀率提升幾倍、跟手了,還有一點(diǎn)卡卡的(App自己的+原生的getResources()調(diào)用),達(dá)到和競(jìng)品一致的水準(zhǔn)了。

以上就是Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化的詳細(xì)內(nèi)容,更多關(guān)于Android 卡頓優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論