Android性能優(yōu)化篇之UI渲染性能優(yōu)化

image

引言

1. Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存泄漏

2.Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存優(yōu)化分析工具

3.Android性能優(yōu)化篇之UI渲染性能優(yōu)化

4.Android性能優(yōu)化篇之計(jì)算性能優(yōu)化

5.Android性能優(yōu)化篇之電量優(yōu)化(1)——電量消耗分析

6.Android性能優(yōu)化篇之電量優(yōu)化(2)

7.Android性能優(yōu)化篇之網(wǎng)絡(luò)優(yōu)化

8.Android性能優(yōu)化篇之Bitmap優(yōu)化

9.Android性能優(yōu)化篇之圖片壓縮優(yōu)化

10.Android性能優(yōu)化篇之多線程并發(fā)優(yōu)化

11.Android性能優(yōu)化篇之?dāng)?shù)據(jù)傳輸效率優(yōu)化

12.Android性能優(yōu)化篇之程序啟動時間性能優(yōu)化

13.Android性能優(yōu)化篇之安裝包性能優(yōu)化

14.Android性能優(yōu)化篇之服務(wù)優(yōu)化

介紹

在用戶使用APP時,一方面想要華麗炫酷的動畫交互凡傅,一方面需要交互的的流暢運(yùn)行厌小,如何平衡設(shè)計(jì)和性能就需要我們不斷的學(xué)習(xí)和思考了桥温。
UI渲染功能是最普通的功能,那么怎么衡量渲染性能的好壞咕宿?可能出現(xiàn)性能瓶頸的地方有哪些染簇?造成卡頓的原因隧甚?如何解決卡頓?這些都是本章需要思考和解決的的問題峦树。

1.關(guān)于ANR

1.1 什么是ANR?

ANR全名Application Not Responding, 也就是"應(yīng)用無響應(yīng)".當(dāng)操作在一段時間內(nèi)系統(tǒng)無法處理時, 系統(tǒng)層面會彈出ANR對話框.

1.2 產(chǎn)生ANR的原因辣辫?

APP的響應(yīng)是Activity Manage和Window Manage來監(jiān)控的旦事,系統(tǒng)產(chǎn)生ANR的原因:

  • 5s內(nèi)無法響應(yīng)用戶輸入事件
  • BoradCastReceiver在10s內(nèi)沒有處理結(jié)束
    上面兩點(diǎn)的根本原因就是主線程有耗時操作。
1.3 如何避免急灭?
  1. 耗時操作放到子線程操作
  2. I/O操作放到子線程
  3. 避免內(nèi)存泄漏(內(nèi)存不夠也會造成ANR姐浮,當(dāng)時大多數(shù)情況是OOM)
1.4 ANR如何分析?

導(dǎo)出/data/anr/下的traces.txt,發(fā)現(xiàn)日志來定位問題

adb pull data/anr/traces.txt ./

2.怎么衡量渲染性能的好壞葬馋?

2.1 16ms

要知道Android系統(tǒng)每隔16ms就發(fā)出VSYNC信號重新繪制一次Activity,所以要在16ms內(nèi)能夠完成繪制卖鲤,這樣才能達(dá)到每秒60幀,然而這個每秒幀數(shù)的參數(shù)由手機(jī)硬件所決定畴嘶,現(xiàn)在大多數(shù)手機(jī)屏幕刷新率是60赫茲(赫茲是國際單位制中頻率的單位蛋逾,它是每秒中的周期性變動重復(fù)次數(shù)的計(jì)量),也就是說我們有16ms(1000ms/60次=16.66ms)的時間去完成每幀的繪制邏輯操作窗悯,就不會出現(xiàn)卡頓的現(xiàn)象区匣,如果沒有完成,則會丟幀導(dǎo)致卡頓蒋院。


image1.png

image2.png

3.關(guān)于渲染管線

Android系統(tǒng)的渲染管線分為兩個關(guān)鍵組件:CPU和GPU沉颂,它們共同工作,在屏幕上繪制圖片悦污,每個組件都有自身定義的特定流程铸屉。我們必須遵守這些特定的操作規(guī)則才能達(dá)到效果。


image3.png

CPU負(fù)責(zé)包括Measure切端,Layout彻坛,Record,Execute的計(jì)算操作踏枣,GPU負(fù)責(zé)Rasterization(柵格化)操作昌屉。
在CPU方面,最常見的性能問題是不必要的布局和失效茵瀑,這些內(nèi)容必須在視圖層次結(jié)構(gòu)中進(jìn)行測量间驮、清除并重新創(chuàng)建,引發(fā)這種問題通常有兩個原因:一是重建顯示列表的次數(shù)太多马昨,二是花費(fèi)太多時間作廢視圖層次并進(jìn)行不必要的重繪竞帽,這兩個原因在更新顯示列表或者其他緩存GPU資源時導(dǎo)致CPU工作過度。
在GPU方面鸿捧,最常見的問題是我們所說的過度繪制(overdraw)屹篓,通常是在像素著色過程中,通過其他工具進(jìn)行后期著色時浪費(fèi)了GPU處理時間匙奴。

3.1 GPU

了解Android中如何使用GPU進(jìn)行畫面的渲染可以幫助我們更好的理解性能問題堆巧。我們的布局文件是如何被繪制到屏幕上的?

image4.png

Resterization柵格化是繪制那些Button,Shape谍肤,Path啦租,String,Bitmap等組件最基礎(chǔ)的操作荒揣。它把那些組件拆分到不同的像素上進(jìn)行顯示刷钢。這是一個很費(fèi)時的操作,GPU的引入就是為了加快柵格化的操作乳附。


image5.png

GPU使用一些指定的基礎(chǔ)指令集内地,主要是多邊形和紋理,也就是圖片赋除,CPU在屏幕上繪制圖像前會向GPU輸入這些指令阱缓,這一過程通常使用的API就是Android的OpenGL ES,這就是說举农,在屏幕上繪制UI對象時無論是按鈕荆针、路徑或者復(fù)選框,都需要在CPU中首先轉(zhuǎn)換為多邊形或者紋理颁糟,然后再傳遞給GPU進(jìn)行格柵化航背。
UI對象轉(zhuǎn)換為一系列多邊形和紋理的過程肯定相當(dāng)耗時,從CPU上傳處理數(shù)據(jù)到GPU同樣也很耗時棱貌。所以很明顯玖媚,我們需要盡量減少對象轉(zhuǎn)換的次數(shù),以及上傳數(shù)據(jù)的次數(shù)婚脱,幸虧今魔,OpenGL ES API允許數(shù)據(jù)上傳到GPU后可以對數(shù)據(jù)進(jìn)行保存,當(dāng)我們下次繪制一個按鈕時障贸,只需要在GPU存儲器里引用它错森,然后告訴OpenGL如何繪制就可以了,一條經(jīng)驗(yàn)之談:

渲染性能的優(yōu)化就是盡可能地上傳數(shù)據(jù)到GPU篮洁,然后盡可能長地在不修改的情況下保存數(shù)據(jù)涩维,因?yàn)槊看紊蟼髻Y源到GPU時,我們都會浪費(fèi)寶貴的處理時間.

為了能夠使得App流暢袁波,我們需要在每幀16ms以內(nèi)處理完所有的CPU與GPU的計(jì)算瓦阐,繪制,渲染等等操作锋叨。

4.Hierarchy Viewer工具介紹

Hierarchy Viewer可以很直接的呈現(xiàn)布局的層次關(guān)系垄分,視圖組件的各種屬性。 我們可以通過紅娃磺,黃,綠三種不同的顏色來區(qū)分布局的Measure叫倍,Layout偷卧,Executive的相對性能表現(xiàn)如何豺瘤。

使用步驟?
(1).打開Android Device Monitor
image9.png
(2).選擇Hierarchy Viewer選項(xiàng)卡
image10.png
4.1 設(shè)備連接問題

如果你是用的模擬器或者開發(fā)版手機(jī)的話則可以直接進(jìn)行連接調(diào)試了听诸,如果不是的話坐求,官方提供了兩種方式,進(jìn)行連接真機(jī)調(diào)試:

第一種晌梨,通過第三方庫桥嗤,安裝和配置ViewServer,也是目前我在使用的方式,工具地址:點(diǎn)擊仔蝌,步驟如下:
(1).添加依賴
project 下的 build.gradle

        allprojects {  
            repositories {  
                jcenter()  
                maven { url "https://jitpack.io" }  
            }  
        }  

module 下的 build.gradle

        dependencies {  
            ...  
            compile 'com.github.romainguy:ViewServer:017c01cd512cac3ec054d9eee05fc48c5a9d2de'  
        }  

(2).申請權(quán)限

    <uses-permission android:name="android.permission.INTERNET"/>

(3).添加代碼

        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            ViewServer.get(this).addWindow(this);  
        }  
        public void onDestroy() {  
            super.onDestroy();  
            ViewServer.get(this).removeWindow(this);  
        }  

        public void onResume() {  
            super.onResume();  
            ViewServer.get(this).setFocusedWindow(this);  
        } 

第二種泛领,通過設(shè)置環(huán)境變量,export ANDROID_HVPROTO=ddm(可能對小米手機(jī)無用)

4.2 性能表示

這里我們主要關(guān)注下面的三個圓圈敛惊,從左到右依次渊鞋,代表View的Measure, Layout和Draw的性能,不同顏色代表不同的性能等級:

(1). 綠: 表示該View的此項(xiàng)性能比該View Tree中超過50%的View都要快瞧挤;例如,一個綠點(diǎn)的測量時間意味著這個視圖的測量時間快于樹中的視圖對象的50%锡宋。
(2). 黃: 表示該View的此項(xiàng)性能比該View Tree中超過50%的View都要慢;例如,一個黃點(diǎn)布局意味著這種觀點(diǎn)有較慢的布局時間超過50%的樹視圖對象特恬。
(3). 紅: 表示該View的此項(xiàng)性能是View Tree中最慢的执俩;例如,一個紅點(diǎn)的繪制時間意味著花費(fèi)時間最多的這一觀點(diǎn)在樹上畫所有的視圖對象。

5.問題分析以及解決方案

5.1 CPU

上面已經(jīng)分析過了癌刽,CPU常見的性能問題是不必要的布局和失效奠滑,引發(fā)這種問題通常有兩個原因:一是重建顯示列表的次數(shù)太多,二是花費(fèi)太多時間作廢視圖層次并進(jìn)行不必要的重繪妒穴。

5.1.1 布局失效優(yōu)化

Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識別并繪制的對象宋税。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息,還有執(zhí)行繪制操作的OpenGL命令列表讼油。在某個View第一次需要被渲染時杰赛,Display List會因此被創(chuàng)建,當(dāng)這個View要顯示到屏幕上時矮台,我們會執(zhí)行GPU的繪制指令來進(jìn)行渲染乏屯。
那么第二次渲染這個view會發(fā)生什么呢?

1.如果View的Property屬性發(fā)生了改變(例如移動位置)瘦赫,我們就僅僅需要Execute Display List就夠了.
image6.png
2.如果你修改了View中的某些可見組件的內(nèi)容辰晕,那么之前的DisplayList就無法繼續(xù)使用了,我們需要重新創(chuàng)建一個DisplayList并重新執(zhí)行渲染指令更新到屏幕上确虱。任何時候View中的繪制內(nèi)容發(fā)生變化時含友,都會需要重新創(chuàng)建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作窘问。這個流程的表現(xiàn)性能取決于你的View的復(fù)雜程度辆童,View的狀態(tài)變化以及渲染管道的執(zhí)行性能。
image7.png
3.如果某個View的大小需要增大到目前的兩倍惠赫,在增大View大小之前把鉴,需要通過父View重新計(jì)算并擺放其他子View的位置。修改View的大小會觸發(fā)整個HierarcyView的重新計(jì)算大小的操作儿咱。
image8.png
5.1.2 嵌套結(jié)構(gòu)優(yōu)

提升布局性能的關(guān)鍵點(diǎn)是盡量保持布局層級的扁平化庭砍,避免出現(xiàn)重復(fù)的嵌套布局。
我們先來看下列子混埠,然后再來總結(jié):
當(dāng)前頁面有兩個條目怠缸,上面條目是使用LinearLayout中嵌套LinearLayout實(shí)現(xiàn)的,下面條目使用一個RelativeLayout實(shí)現(xiàn)


image13.png

我們使用Hierarchy Viewer工具來看下:


image11.png

現(xiàn)在我們把上面一個條目改成和下面條目一樣的實(shí)現(xiàn)岔冀,看下優(yōu)化后的效果:


image12.png
5.1.2.1 結(jié)果分析

紅色節(jié)點(diǎn)是代表應(yīng)用性能慢的一個潛在問題凯旭,下面是幾個例子,如何來分析和解釋紅點(diǎn)的出現(xiàn)原因使套?

(1).如果在葉節(jié)點(diǎn)或者ViewGroup中罐呼,只有極少的子節(jié)點(diǎn),這可能反映出一個問題侦高,應(yīng)用可能在設(shè)備上運(yùn)行并不慢嫉柴,但是你需要指導(dǎo)為什么這個節(jié)點(diǎn)是紅色的,可以借助Systrace或者Traceview工具奉呛,獲取更多額外的信息
(2).如果一個視圖組里面有許多的子節(jié)點(diǎn)计螺,并且測量階段呈現(xiàn)為紅色,則需要觀察下子節(jié)點(diǎn)的繪制情況
(3).如果視圖層級結(jié)構(gòu)中的根視圖瞧壮,Messure階段為紅色登馒,Layout階段為紅色,Draw階段為黃色咆槽,這個是比較常見的陈轿,因?yàn)檫@個節(jié)點(diǎn)是所有其它視圖的父類
(4).如果視圖結(jié)構(gòu)中的一個葉子節(jié)點(diǎn),有20個視圖是紅色的Draw階段秦忿,這是有問題的麦射,需要檢查代碼里面的onDraw方法,不應(yīng)該在那里調(diào)用
5.1.2.2 優(yōu)化建議
(1).沒有用的父布局時指沒有背景繪制或者沒有大小限制的父布局灯谣,這樣的布局不會對UI效果產(chǎn)生任何影響潜秋。我們可以把沒有用的父布局,通過<merge/>標(biāo)簽合并來減少UI的層次
(2).使用線性布局LinearLayout排版導(dǎo)致UI層次變深胎许,如果有這類問題峻呛,我們就使用相對布局RelativeLayout代替LinearLayout,減少UI的層次
(3).不常用的UI被設(shè)置成GONE,比如異常的錯誤頁面罗售,如果有這類問題,我們需要用<ViewStub/>標(biāo)簽杀饵,代替GONE提高UI性能
5.1.3 常用的優(yōu)化示例
(1). include 標(biāo)簽

include標(biāo)簽常用于將布局中的公共部分提取出來供其他layout共用莽囤,以實(shí)現(xiàn)布局模塊化谬擦,這在布局編寫方便提供了大大的便利切距。

(2). viewstub 標(biāo)簽

viewstub標(biāo)簽同include標(biāo)簽一樣可以用來引入一個外部布局,不同的是惨远,viewstub引入的布局默認(rèn)不會擴(kuò)張谜悟,即既不會占用顯示也不會占用位置,從而在解析layout時節(jié)省cpu和內(nèi)存北秽。
viewstub常用來引入那些默認(rèn)不會顯示葡幸,只在特殊情況下顯示的布局,如進(jìn)度布局贺氓、網(wǎng)絡(luò)失敗顯示的刷新布局蔚叨、信息出錯出現(xiàn)的提示布局等。

    //第一種
    ViewStub stub = (ViewStub)findViewById(...)
    View stubView=  stub.inflate();
    //根據(jù)實(shí)際情況辙培,顯示
    stubView.setVisibility()

    //第二種
    View viewStub = findViewById(R.id.network_error_layout);
    viewStub.setVisibility(View.VISIBLE);   // ViewStub被展開后的布局所替換
注意:ViewStub所加載的布局是不可以使用<merge>標(biāo)簽的
(3). merge 標(biāo)簽

在使用了include后可能導(dǎo)致布局嵌套過多蔑水,多余不必要的layout節(jié)點(diǎn),從而導(dǎo)致解析變慢

merge標(biāo)簽可用于兩種典型情況:

(1). 布局頂結(jié)點(diǎn)是FrameLayout且不需要設(shè)置background或padding等屬性扬蕊,可以用merge代替搀别,因?yàn)锳ctivity內(nèi)容試圖的parent view就是個FrameLayout,所以可以用merge消除只剩一個尾抑。
(2). 某布局作為子布局被其他布局include時歇父,使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時頂結(jié)點(diǎn)會自動被忽略再愈,而將其子節(jié)點(diǎn)全部合并到主布局中榜苫。

5.2 GPU

在GPU方面,最常見的問題是我們所說的過度繪制(overdraw)翎冲,通常是在像素著色過程中垂睬,通過其他工具進(jìn)行后期著色時浪費(fèi)了GPU處理時間。

image14.png

過度繪制描述的是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次府适。在多層次重疊的UI結(jié)構(gòu)里面羔飞,如果不可見的UI也在做繪制的操作,會導(dǎo)致某些像素區(qū)域被繪制了多次檐春。這樣就會浪費(fèi)大量的CPU以及GPU資源逻淌。
當(dāng)設(shè)計(jì)上追求更華麗的視覺效果的時候,我們就容易陷入采用復(fù)雜的多層次重疊視圖來實(shí)現(xiàn)這種視覺效果的怪圈疟暖。這很容易導(dǎo)致大量的性能問題卡儒,為了獲得最佳的性能田柔,我們必須盡量減少Overdraw的情況發(fā)生。

幸運(yùn)的是骨望,我們可以通過手機(jī)設(shè)置里面的開發(fā)者選項(xiàng)硬爆,打開Show GPU Overdraw的選項(xiàng),觀察UI上的Overdraw情況擎鸠。


image15.png
GPU Profiling

從Android M系統(tǒng)開始缀磕,系統(tǒng)更新了GPU Profiling的工具來幫助我們定位UI的渲染性能問題。早期的CPU Profiling工具只能粗略的顯示出Process劣光,Execute袜蚕,Update三大步驟的時間耗費(fèi)情況。


image1.jpg

但是僅僅顯示三大步驟的時間耗費(fèi)情況绢涡,還是不太能夠清晰幫助我們定位具體的程序代碼問題牲剃,所以在Android M版本開始,GPU Profiling工具把渲染操作拆解成如下8個詳細(xì)的步驟進(jìn)行顯示雄可。


image2.jpg

舊版本中提到的Proces凿傅,Execute,Update還是繼續(xù)得到了保留数苫,他們的對應(yīng)關(guān)系如下:


image3.jpg
  • Sync & Upload:通常表示的是準(zhǔn)備當(dāng)前界面上有待繪制的圖片所耗費(fèi)的時間聪舒,為了減少該段區(qū)域的執(zhí)行時間,我們可以減少屏幕上的圖片數(shù)量或者是縮小圖片本身的大小文判。
  • Measure & Layout:這里表示的是布局的onMeasure與onLayout所花費(fèi)的時間过椎,一旦時間過長,就需要仔細(xì)檢查自己的布局是不是存在嚴(yán)重的性能問題戏仓。
  • Animation:表示的是計(jì)算執(zhí)行動畫所需要花費(fèi)的時間疚宇,包含的動畫有ObjectAnimator,ViewPropertyAnimator赏殃,Transition等等敷待。一旦這里的執(zhí)行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執(zhí)行的過程中是不是觸發(fā)了讀寫操作等等仁热。
  • Input Handling:表示的是系統(tǒng)處理輸入事件所耗費(fèi)的時間榜揖,粗略等于對于的事件處理方法所執(zhí)行的時間。一旦執(zhí)行時間過長抗蠢,意味著在處理用戶的輸入事件的地方執(zhí)行了復(fù)雜的操作举哟。
  • Misc/Vsync Delay:如果稍加注意,我們可以在開發(fā)應(yīng)用的Log日志里面看到這樣一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread迅矛。這意味著我們在主線程執(zhí)行了太多的任務(wù)妨猩,導(dǎo)致UI渲染跟不上vSync的信號而出現(xiàn)掉幀的情況。

上面八種不同的顏色區(qū)分了不同的操作所耗費(fèi)的時間秽褒,為了便于我們迅速找出那些有問題的步驟壶硅,GPU Profiling工具會顯示16ms的閾值線威兜,這樣就很容易找出那些不合理的性能問題,再仔細(xì)看對應(yīng)具體哪個步驟相對來說耗費(fèi)時間比例更大庐椒,結(jié)合上面介紹的細(xì)化步驟椒舵,從而快速定位問題,修復(fù)問題约谈。

5.2.1 優(yōu)化建議
(1).移除Window默認(rèn)的Background
    getWindow().setBackgroundDrawable(null);
(2).移除XML布局文件中非必需的Background
(3).按需顯示占位背景圖片

在給ImageView設(shè)置圖片時笔宿,判斷是否獲取到對應(yīng)的Bitmap,在獲取到圖像之后窗宇,把ImageView的Background設(shè)置為Transparent措伐,只有當(dāng)圖像沒有獲取到的時候才設(shè)置對應(yīng)的Background占位圖片特纤,這樣可以避免因?yàn)樵O(shè)置背景圖而導(dǎo)致的過度渲染军俊。

(4).剪輯不顯示的UI組件

對不可見的UI組件進(jìn)行繪制更新會導(dǎo)致Overdraw。例如Nav Drawer從前置可見的Activity滑出之后捧存,如果還繼續(xù)繪制那些在Nav Drawer里面不可見的UI組件粪躬,這就導(dǎo)致了Overdraw。為了解決這個問題昔穴,Android系統(tǒng)會通過避免繪制那些完全不可見的組件來盡量減少Overdraw镰官。那些Nav Drawer里面不可見的View就不會被執(zhí)行浪費(fèi)資源。


image16.png

但是不幸的是吗货,對于那些過于復(fù)雜的自定義的View(通常重寫了onDraw方法)泳唠,Android系統(tǒng)無法檢測在onDraw里面具體會執(zhí)行什么操作,系統(tǒng)無法監(jiān)控并自動優(yōu)化宙搬,也就無法避免Overdraw了笨腥。但是我們可以通過canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域。這個方法可以指定一塊矩形區(qū)域勇垛,只有在這個區(qū)域內(nèi)才會被繪制脖母,其他的區(qū)域會被忽視。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域闲孤。同時clipRect方法還可以幫助節(jié)約CPU與GPU資源谆级,在clipRect區(qū)域之外的繪制指令都不會被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件讼积,仍然會得到繪制肥照。
除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交勤众,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作舆绎。
下面我們來看個實(shí)例:


image17.png

代碼:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mCardList != null && mCardList.size() > 0) {
            for (int i = 0; i < mCardList.size(); i++) {
                mCardLeft = i * mCardSpacing;
                drawCard(canvas, mCardList.get(i), mCardLeft, 0);
            }
        }
    }

    private void drawCard(Canvas canvas, CardItem card, int left, int top) {
        Bitmap mBitmap = getBitmap(card.resId);
        canvas.drawBitmap(mBitmap, left, top, mPaint);
    }

    private Bitmap getBitmap(int resId) {
        return BitmapFactory.decodeResource(this.getResources(), resId);
    }

我們看到撲克牌有不可見的區(qū)域但是還是被繪制了,導(dǎo)致過度繪制决摧。下面我們進(jìn)行剪輯亿蒸。

剪輯過后效果:


image18.png

代碼:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mCardList != null && mCardList.size() > 0) {
            for (int i = 0; i < mCardList.size()-1; i++) {
                mCardLeft = i * mCardSpacing;
                canvas.save();
                canvas.clipRect(mCardLeft,
                        0f,
                        mCardLeft + mCardSpacing,
                        mCardList.get(i).getHeight());
                drawCard(canvas, mCardList.get(i), mCardLeft, 0);
                canvas.restore();
            }
            drawCard(canvas, mCardList.get(mCardList.size()-1), mCardLeft + mCardSpacing, 0);
        }
    }
注意:有些過度繪制對于運(yùn)行性能凑兰,可能是必要的也是可以接受的,比如說Android的ActionBar边锁,但是姑食,如果我們希望應(yīng)用體驗(yàn)更進(jìn)一步,我們可以考慮盡可能地減少過度繪制茅坛。
5.3 其他問題引起的卡頓分析
5.3.1 內(nèi)存抖動

內(nèi)存抖動是因?yàn)樵诙虝r間內(nèi)大量的對象被創(chuàng)建又馬上被釋放音半。瞬間產(chǎn)生大量的對象會嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值贡蓖,剩余空間不夠的時候曹鸠,會觸發(fā)GC從而導(dǎo)致剛產(chǎn)生的對象又很快被回收。即使每次分配的對象占用了很少的內(nèi)存斥铺,但是他們疊加在一起會增加Heap的壓力捣作,從而觸發(fā)更多其他類型的GC慰安。這個操作有可能會影響到幀率,并使得用戶感知到性能問題(卡頓)。


image19.png

解決上面的問題有簡潔直觀方法蔓钟,如果你在Memory Monitor里面查看到短時間發(fā)生了多次內(nèi)存的漲跌蕴轨,這意味著很有可能發(fā)生了內(nèi)存抖動词爬。


image20.png

同時我們還可以通過Allocation Tracker來查看在短時間內(nèi)镐躲,同一個棧中不斷進(jìn)出的相同對象。這是內(nèi)存抖動的典型信號之一岖常。

當(dāng)你大致定位問題之后驯镊,接下去的問題修復(fù)也就顯得相對直接簡單了。例如竭鞍,你需要避免在for循環(huán)里面分配對象占用內(nèi)存板惑,需要嘗試把對象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意笼蛛,每次屏幕發(fā)生繪制以及動畫執(zhí)行過程中洒放,onDraw方法都會被調(diào)用到,避免在onDraw方法里面執(zhí)行復(fù)雜的操作滨砍,避免創(chuàng)建對象往湿。對于那些無法避免需要創(chuàng)建對象的情況,我們可以考慮對象池模型惋戏,通過對象池來解決頻繁創(chuàng)建與銷毀的問題领追,但是這里需要注意結(jié)束使用之后,需要手動釋放對象池中的對象响逢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绒窑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子舔亭,更是在濱河造成了極大的恐慌些膨,老刑警劉巖蟀俊,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異订雾,居然都是意外死亡肢预,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門洼哎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烫映,“玉大人,你說我怎么就攤上這事噩峦《Ч担” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵识补,是天一觀的道長族淮。 經(jīng)常有香客問我,道長李请,這世上最難降的妖魔是什么瞧筛? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮导盅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揍瑟。我一直安慰自己白翻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布绢片。 她就那樣靜靜地躺著滤馍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪底循。 梳的紋絲不亂的頭發(fā)上巢株,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音熙涤,去河邊找鬼阁苞。 笑死,一個胖子當(dāng)著我的面吹牛祠挫,可吹牛的內(nèi)容都是我干的那槽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼等舔,長吁一口氣:“原來是場噩夢啊……” “哼骚灸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起慌植,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤甚牲,失蹤者是張志新(化名)和其女友劉穎义郑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丈钙,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魔慷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了著恩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片院尔。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喉誊,靈堂內(nèi)的尸體忽然破棺而出邀摆,到底是詐尸還是另有隱情,我是刑警寧澤伍茄,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布栋盹,位于F島的核電站,受9級特大地震影響敷矫,放射性物質(zhì)發(fā)生泄漏例获。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一曹仗、第九天 我趴在偏房一處隱蔽的房頂上張望榨汤。 院中可真熱鬧,春花似錦怎茫、人聲如沸收壕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜜宪。三九已至,卻和暖如春祥山,著一層夾襖步出監(jiān)牢的瞬間圃验,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工缝呕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澳窑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓岳颇,卻偏偏與公主長得像照捡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子话侧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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