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

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

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

背景

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

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

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

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

本文 做記錄和分享。

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

觀測

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

對于Binder傳遞數(shù)據(jù)太大、線程數(shù)導(dǎo)致的性能問題,由于應(yīng)用不是自己的(不好干涉、不關(guān)注),且對比機(jī)卡頓不那么明顯(可以粗略排除),因此不太值得去看。(另外我們是在滑屏的時候卡的,主線程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(因為沒有App的代碼,最近也忙的不想逆向它;但實際上最后我們知道了為何調(diào)用),也很顯然是在哪調(diào)用的(在App UI線程 performTraversal時調(diào)用的),因此先來看看這群IPC的對端是誰。

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

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

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

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

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

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

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

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

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

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表示正常。

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

第一個堆棧放全一些,可以看出,在正常的traversal過程中,View體系正常調(diào)用getResources(),binder發(fā)生在getResources()內(nèi)部:它調(diào)用了IWindowManager.getInitialDisplayDensity(),通過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)

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

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

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)生巨大的性能問題
  • 在measure和traversal階段不厭其煩地調(diào)用一個通常情況很少調(diào)用的接口getInitialDisplayDensity()
  • View的加載階段、traversal階段,在measure、layout階段因Binder IPC過度頻繁觸發(fā)了性能問題

結(jié)論

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

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

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

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

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

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

方案

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

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

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

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

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

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

相關(guān)文章

最新評論