性能優(yōu)化實(shí)踐(三)-卡頓優(yōu)化思考

一、基本概念

Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào)令漂,觸發(fā)對(duì)UI進(jìn)行渲染膝昆,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面所需要的60fps叠必,這也意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成荚孵。如果無法完成,則發(fā)生丟幀纬朝,上一幀畫面被重復(fù)顯示收叶,造成卡頓的視覺。


從整個(gè)視圖渲染流程看:

Surfaceflinger由init啟動(dòng)的獨(dú)立進(jìn)程共苛,提供合成視圖的系統(tǒng)服務(wù)判没。如果Surfaceflinger掛掉,會(huì)重啟zygote隅茎。

在Surfaceflinger的init方法中澄峰,實(shí)例化了HWComposer和兩個(gè)EventThread。

HWComposer:負(fù)責(zé)輸出硬件產(chǎn)生或軟件模擬的Vsync信號(hào)辟犀。

EventThread:負(fù)責(zé)分發(fā)vsync到Choreographer和SurfaceFlinger俏竞。其中mEventThread對(duì)應(yīng)Choreographer;而mSFEventThread:對(duì)應(yīng)SurfaceFlinger堂竟。

VSYNC信號(hào)主要的兩個(gè)訂閱者:SurfaceFlinger 和 Choreographer魂毁。

SurfaceFlinger:接收信號(hào)執(zhí)行合成Layer流程。

Choreographer:接收信號(hào)來控制同步處理輸入(Input)跃捣、動(dòng)畫(Animation)漱牵、繪制(Draw)三個(gè)UI操作夺蛇。

Choreographer通知應(yīng)用層繪制疚漆、SurfaceFlinger負(fù)責(zé)合成視圖、兩者之前加上了一定的offset,這樣能保證兩者步調(diào)一致娶聘。

在這個(gè)過程中闻镶,CPU負(fù)責(zé)把視圖加工為多邊形和紋理。GPU負(fù)責(zé)把多邊形和紋理做柵格化處理丸升,成為送顯的像素?cái)?shù)據(jù)铆农。

二、造成卡頓的原因
應(yīng)用層面:

1 視圖層面的問題

包括layout層級(jí)太深View太多狡耻、View太復(fù)雜墩剖、重復(fù)繪制、ListView沒優(yōu)化夷狰、動(dòng)畫設(shè)計(jì)不合理等等岭皂。

這是遇到卡頓問題首先需要排查的,部分問題可以通過開發(fā)階段的coding規(guī)范來避免的沼头。

1)layout層級(jí)太深View太多:可以通過Lint來檢測(cè)爷绘,優(yōu)化:通過合理容器的使用,優(yōu)先減少層級(jí)进倍,其次減少View數(shù)目土至,能重用的盡量重用。

2)View太復(fù)雜:如果是自定義View猾昆,那還是從視圖太深陶因、View太多兩個(gè)層面來考慮優(yōu)化。如果是成熟的View:比如WebView垂蜗、VideoView這種重量級(jí)的View坑赡,盡量復(fù)用和管理好生命周期。

3)重復(fù)繪制:通過Settings中打開GPU過度繪制 & GPU呈現(xiàn)模式可以了解當(dāng)前視圖層級(jí)關(guān)系么抗,當(dāng)然這部分與前面兩點(diǎn)也是分不開的毅否,最基本的要注意移除xml中非必須背景。

4)ListView優(yōu)化蝇刀,這部分主要是convertView的復(fù)用螟加,能減少View的創(chuàng)建;ViewHolder的使用吞琐,減少View的find和賦值捆探,加快加載速度;分頁加載:控制一次加載的數(shù)據(jù)量站粟,這樣加載速度會(huì)快黍图,內(nèi)存壓力也相對(duì)小。

5)動(dòng)畫:合理設(shè)計(jì)動(dòng)畫奴烙,能不用幀動(dòng)畫盡量不用助被,因?yàn)閳D片比較占內(nèi)存剖张,尤其是數(shù)量多的時(shí)候。另外針對(duì)屬性動(dòng)畫揩环,同一個(gè)view的一系列動(dòng)畫搔弄,可以使用Keyframe+PropertyValuesHolder組合方式達(dá)到只使用一個(gè)ObjectAnimator,多個(gè)view的動(dòng)畫用AnimatorSet進(jìn)行動(dòng)畫組合和排序丰滑。

2 消息相關(guān)耗時(shí)

我們都知道顾犹,耗時(shí)操作放到子線程做,通過handle返回主線程更新UI褒墨。但是消息本身也是會(huì)耗時(shí)的炫刷,主要分兩方面:1)消息本身執(zhí)行耗時(shí), 2)消息執(zhí)行被delay。消息本身執(zhí)行耗時(shí)那就是主線程耗時(shí)郁妈,消息執(zhí)行被delay柬唯,在messageQueue中,由于之前的Message太多或者執(zhí)行時(shí)間過長(zhǎng)圃庭,導(dǎo)致當(dāng)前需更新UI的操作得不到及時(shí)處理锄奢,尤其是16.6ms硬性標(biāo)準(zhǔn)下,一旦delay必然丟幀剧腻。

3 主線程耗時(shí)

這部分我要說的并不是在主線程做耗時(shí)操作了拘央,而是站在CPU調(diào)度的角度來看耗時(shí)問題,也就是說书在,比如主線程有500ms的耗時(shí)灰伟,要么Running了多久,是否存在Sleeping和Uninterruptible sleep等狀態(tài)儒旬,這段時(shí)間內(nèi)CPU被搶占了壓根就沒騰出功夫來執(zhí)行你這操作栏账。如果有現(xiàn)場(chǎng)的話,通過抓systrace能比較明顯看出來栈源。

4 Input事件本身耗時(shí)

在Android整個(gè)Input體系中有三個(gè)重要的成員:Eventhub挡爵,InputReader,InputDispatcher甚垦。它們分別擔(dān)負(fù)著各自不同的職責(zé)茶鹃,Eventhub負(fù)責(zé)監(jiān)聽/dev/input產(chǎn)生Input事件,InputReader負(fù)責(zé)從Eventhub讀取事件艰亮,并將讀取的事件發(fā)給InputDispatcher闭翩,InputDispatcher則根據(jù)實(shí)際的需要具體分發(fā)給當(dāng)前手機(jī)獲得焦點(diǎn)實(shí)際的Window,最終交給ActivityThread通過消息來處理迄埃。

系統(tǒng)角度:
InputDispatcher分發(fā)事件給Window這個(gè)過程是跨進(jìn)程通信疗韵,獲取對(duì)應(yīng)window本身可能存在耗時(shí)。

應(yīng)用角度:
客戶端接收事件的消息本身又可能存在耗時(shí)和delay的情況侄非,這又回到消息耗時(shí)的范疇了蕉汪。

5 持鎖耗時(shí)
這屬于業(yè)務(wù)邏輯層面的問題流译,最簡(jiǎn)單的就是主線程死鎖,亦或是主線程在等鎖肤无,然后當(dāng)前鎖被其他線程持有在做耗時(shí)操作等等先蒋。

6 頻繁GC

我們知道骇钦,執(zhí)行GC操作的時(shí)候宛渐,所有線程的任何操作都會(huì)需要暫停,等待GC操作完成之后眯搭,其他操作才能夠繼續(xù)運(yùn)行窥翩。通常來說,單個(gè)的GC并不會(huì)占用太多時(shí)間鳞仙,但是大量不停的GC操作則會(huì)顯著占用幀間隔時(shí)間(16ms)寇蚊。如果在幀間隔時(shí)間里面做了過多的GC操作,那么自然其他類似計(jì)算棍好,渲染等操作的可用時(shí)間就變得少了仗岸。

導(dǎo)致GC頻繁執(zhí)行有兩個(gè)原因:

1)內(nèi)存抖動(dòng),在memory monitor里能很明顯看出來借笙,短時(shí)間內(nèi)創(chuàng)建大量對(duì)象然后又迅速被釋放扒怖。

比如:在一個(gè)方法里for循環(huán)拼接String。會(huì)產(chǎn)生大量廢棄的String對(duì)象业稼,短時(shí)間內(nèi)又會(huì)被回收盗痒,所以容易造成抖動(dòng),可以用StringBuilder/StringBuffer來替代低散,它們實(shí)現(xiàn)是動(dòng)態(tài)數(shù)組俯邓,初始長(zhǎng)度128,不夠用了通過arraycopy來增加長(zhǎng)度熔号。對(duì)象統(tǒng)一管理稽鞭,不會(huì)短時(shí)間內(nèi)造成短時(shí)間內(nèi)大量創(chuàng)建和銷毀的問題,同時(shí)append與+相比更安全引镊。

String:適用于少量的字符串操作的情況
StringBuilder:適用于單線程下在字符緩沖區(qū)進(jìn)行大量操作的情況
StringBuffer:適用多線程下在字符緩沖區(qū)進(jìn)行大量操作的情況

2)瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域川慌,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候祠乃,也會(huì)觸發(fā)GC梦重。即使每次分配的對(duì)象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力亮瓷,從而觸發(fā)更多其他類型的GC琴拧。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問題嘱支。

系統(tǒng)層面:

1 內(nèi)存原因

在系統(tǒng)內(nèi)存非常低的情況下蚓胸,常規(guī)經(jīng)驗(yàn)是:MemAvailable 低于MemTotal 1/10的情況下挣饥,容易出現(xiàn)內(nèi)存引起的卡頓,原因無非就是在內(nèi)存低的情況下內(nèi)核在分配內(nèi)存時(shí)沛膳,很難從物理內(nèi)存(伙伴系統(tǒng))直接拿到合適大小的頁面扔枫,此時(shí)會(huì)觸發(fā)回收操作,如內(nèi)存整理(compact)锹安、回收匿名頁(swap)短荐、回收文件頁(dirty=回寫,clean=丟棄)等操作叹哭。這些回收操作較慢忍宋,因此耗時(shí)。這個(gè)過程主要體現(xiàn)在新啟一個(gè)應(yīng)用风罩,zygote fork進(jìn)程申請(qǐng)內(nèi)存的時(shí)候糠排。

2 系統(tǒng)服務(wù)持鎖耗時(shí)

應(yīng)用binder call請(qǐng)求系統(tǒng)服務(wù),一般來說超升,系統(tǒng)服務(wù)如AMS入宦、WMS對(duì)應(yīng)的方法,一上來先不管三七二十一,就是一把大鎖室琢,很多情況下乾闰,特定的操作會(huì)造成持鎖耗時(shí)的情況,具體問題具體分析研乒。

3 CPU調(diào)度問題

這類情況不太多見汹忠,但是也是存在的。在某個(gè)繪制周期中雹熬,CPU被搶占宽菜,無法及時(shí)開始繪制操作。這分幾部分來看竿报,首先是不是被某個(gè)進(jìn)程搶占的铅乡,比如dex2oat×揖或者看這段時(shí)間CPU使用率非常高阵幸,但是可能是大核跑滿了,但是小核相對(duì)比較閑芽世,這屬于系統(tǒng)調(diào)度有問題等等挚赊。

例如:dex2oat發(fā)生的時(shí)候,占用所有有CPU(默認(rèn)策略是有多少個(gè)核济瓢,就啟動(dòng)多少個(gè)線程)荠割,會(huì)將原文件中的dex文件抽出來,逐個(gè)指令的判斷,然后進(jìn)行翻譯蔑鹦,并生成大量的中間內(nèi)容夺克,這些在memory當(dāng)中是保存不下的,所以采用了swap機(jī)制, memory越少嚎朽,越容易發(fā)生交換铺纽,所以還可能引起IO上的瓶頸。

可以設(shè)置系統(tǒng)屬性:dalvik.vm.bg-dex2oat-threads 和 dalvik.vm.dex2oat-threads 哟忍,這兩個(gè)系統(tǒng)屬性是分別設(shè)置在前后臺(tái)執(zhí)行dex2oat限制的線程數(shù)狡门,對(duì)應(yīng)8核CPU來說,比如設(shè)置前后臺(tái)分別為4魁索,這樣dex2oat執(zhí)行時(shí)間會(huì)變長(zhǎng)融撞,但是卡頓會(huì)被緩解盼铁。

當(dāng)然還有一種情況是粗蔚,當(dāng)手機(jī)溫度過高,導(dǎo)致CPU降頻饶火,也會(huì)出現(xiàn)系統(tǒng)卡頓鹏控。

本文只是對(duì)卡頓分析提供一點(diǎn)不成熟的小思路。隨著學(xué)習(xí)的深入肤寝,我會(huì)持續(xù)更新当辐。
文中牽涉到的布局和重復(fù)繪制相關(guān)的內(nèi)容可以參考我的文章:布局優(yōu)化
文中牽扯到的相關(guān)性能優(yōu)化工具可以參考我的系列文章:性能優(yōu)化工具篇總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者鲤看。
  • 序言:七十年代末缘揪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子义桂,更是在濱河造成了極大的恐慌找筝,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慷吊,死亡現(xiàn)場(chǎng)離奇詭異袖裕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)溉瓶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門急鳄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人堰酿,你說我怎么就攤上這事疾宏。” “怎么了触创?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵坎藐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我嗅榕,道長(zhǎng)顺饮,這世上最難降的妖魔是什么吵聪? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮兼雄,結(jié)果婚禮上吟逝,老公的妹妹穿的比我還像新娘。我一直安慰自己赦肋,他們只是感情好块攒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佃乘,像睡著了一般囱井。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趣避,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天庞呕,我揣著相機(jī)與錄音,去河邊找鬼程帕。 笑死住练,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愁拭。 我是一名探鬼主播讲逛,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼岭埠!你這毒婦竟也來了盏混?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤惜论,失蹤者是張志新(化名)和其女友劉穎许赃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體来涨,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡图焰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹦掐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片技羔。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卧抗,靈堂內(nèi)的尸體忽然破棺而出藤滥,到底是詐尸還是另有隱情,我是刑警寧澤社裆,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布拙绊,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏标沪。R本人自食惡果不足惜榄攀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望金句。 院中可真熱鬧檩赢,春花似錦、人聲如沸违寞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趁曼。三九已至军浆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挡闰,已是汗流浹背乒融。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尿这,地道東北人簇抵。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓庆杜,卻偏偏與公主長(zhǎng)得像射众,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晃财,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容