本文基本翻譯自Facebook工程師的文章
Speed up your app型豁,也加入了自己的一些內(nèi)容。
會(huì)介紹以下幾個(gè)主題
- Systrace
- Traceview
- Memory Profiling
- Allocation Tracker
- GPU Profiling
- Hierarchy Viewer
- Overdraw
- Alpha
- Hardware Acceleration
Systrace
Systrace的功能可以在AS的DDMS中找到笆制,但不太穩(wěn)定,所以這里只介紹命令行模式赖条。
另外Systrace只能分析概況如孝,不能定位問題位置宪哩,不太感興趣的朋友也可以直接跳到第二節(jié)Traceview。
在終端(我的環(huán)境是MAC)中先進(jìn)入sdk/platform-tools/systrace目錄
cd /Users/apple/Documents/Android/sdk/platform-tools/systrace
然后執(zhí)行Systrace的命令是
python systrace.py --time=10 -a com.duotin.fm -o mynewtrace.html sched gfx view wm
--time=10 表示記錄10秒
更多參數(shù)說明請(qǐng)查看官網(wǎng)
執(zhí)行完打開生成的mynewtrace.html文件
呈現(xiàn)這樣的界面
點(diǎn)擊第一列的三角形警示圖標(biāo)或者第二列的圓形警示圖標(biāo)第晰,都可以查看警示詳情锁孟,點(diǎn)擊效果分別如下
三角形警示圖標(biāo)彬祖,每個(gè)圖標(biāo)代表一個(gè)警示,點(diǎn)擊后查看警示詳情品抽。
圓形警示圖標(biāo)储笑,每個(gè)圖標(biāo)代表一個(gè)frame,顯示紅色或者黃色表示此frame的時(shí)間已經(jīng)超過16.6 millisecond per frame的標(biāo)準(zhǔn)圆恤,會(huì)導(dǎo)致界面失幀突倍。點(diǎn)擊后查看這個(gè)frame所有的警示。
此時(shí)按"M"快捷鍵可以高亮當(dāng)前選中的frame盆昙∮鹄“A"和"D"分別為左移和右移視圖,"W"放大淡喜,"S"縮小秕磷。
此外,點(diǎn)擊各顏色塊炼团,可以查看各顏色塊的詳情澎嚣。
點(diǎn)擊上圖中的第一個(gè)Alert: Inflation during ListView recycling
顯示詳情
可以看出Inflation during ListView recycling的執(zhí)行時(shí)間是32ms(遠(yuǎn)遠(yuǎn)超過了16ms的限制),共5個(gè)item,平均到每個(gè)item為6ms瘟芝。通過點(diǎn)擊該frame范圍內(nèi)的顏色塊易桃,可以查看各個(gè)方法的詳情。
我們?cè)冱c(diǎn)擊一個(gè)圓形警示圖標(biāo)并高亮
頂部顯示此frame耗時(shí)19ms, 點(diǎn)擊右下方的Alert锌俱,顯示有 Scheduled delay晤郑。
Scheduled delay 是指告訴CPU執(zhí)行任務(wù),但CPU太忙了嚼鹉,任務(wù)被延遲執(zhí)行了贩汉。
點(diǎn)擊下面的一個(gè)顏色塊,顯示如下
Wall duration 是指此顏色塊代表的方法從開始到結(jié)束的耗時(shí)
CPU duration 是指 CPU的執(zhí)行時(shí)間
可以發(fā)現(xiàn)CPU duration只有4ms锚赤,但Wall duration有18ms。
延遲這么嚴(yán)重褐鸥,我們來看看原因线脚。
在選中的顏色塊上方,我們看到四個(gè)CPU都被顏色塊填充叫榕,表示此時(shí)4個(gè)CPU都有活干浑侥,很忙。
我們選中一個(gè)CPU的顏色塊
可以發(fā)現(xiàn)占用CPU的應(yīng)用是com.udinic.keepbusyapp
恩晰绎,對(duì)Systrace的介紹到此結(jié)束了寓落,雖然還有些沒講,但Systrace的確只能看個(gè)概覽荞下。
且慢伶选,再加送一個(gè)tip, 點(diǎn)擊右邊欄的Alerts, 你能看到所有的Alerts.
通過這張圖我分析出
inefficient view alpha usage數(shù)量最多史飞。Inflation during ListView recycling影響時(shí)間最長(zhǎng),耗時(shí)長(zhǎng)達(dá)52ms仰税。
inefficient view alpha usage是因?yàn)檎{(diào)用具體View的setAlpha方法,而View的setAlpha在Android中是很昂貴的操作构资。解決方法是用ARGB設(shè)置color代替直接調(diào)用setAlpha;如果是ImageView陨簇,調(diào)用ImageView#setImageAlpha吐绵;如果是自定義View,覆蓋hasOverlappingRendering()或者onSetAlpha()或者通過paint.setAlpha實(shí)現(xiàn)河绽。詳細(xì)請(qǐng)參考文章1己单,文章2, 文章3
Traceview
Traceview能夠在方法層面上分析APP的性能,非常強(qiáng)大耙饰。
可以通過荷鼠,命令行或者GUI啟動(dòng),我用的是GUI啟動(dòng)榔幸,點(diǎn)擊AS的Android Device Monitor, 點(diǎn)擊Devices欄目下面的 Start Method Profilling的圖標(biāo) , 在對(duì)話框中選擇允乐,(我選的是trace Based Profilling,表示實(shí)時(shí)分析,會(huì)比較慢削咆,但分析結(jié)果詳細(xì))牍疏,操作APP, 分析結(jié)束的時(shí)候,點(diǎn)擊同一個(gè)圖標(biāo)即可拨齐。更多操作請(qǐng)?jiān)L問官網(wǎng)
先看下界面
列名 | 意義 |
---|---|
Name | 方法名鳞陨,每個(gè)方法的顏色都不一樣。 |
Inclusive CPU Time | 此方法占用CPU的時(shí)間瞻惋,Inclusive指包括調(diào)用的方法所 |
Exclusive CPU Time | 此方法所占用CPU的時(shí)間厦滤,Exclusive 指不包括調(diào)用的方法 |
Inclusive / Exclusive Real Time | Real Time指方法從開始到結(jié)束消耗的時(shí)間,跟Systrace中的Wall duration一個(gè)意思歼狼。 |
Calls+Recursion | 此方法被調(diào)用了多少次+多少次是遞歸調(diào)用 |
Calls / Total | 子方法被此父方法調(diào)用的次數(shù)/子方法被調(diào)用的總次數(shù) |
點(diǎn)擊某條目下的parent 或者 child 方法時(shí)掏导,會(huì)跳到該方法的條目。
想找出最影響性能的方法羽峰,可以點(diǎn)擊Exclusive CPU time一欄趟咆,找出消耗時(shí)間最長(zhǎng)的幾個(gè)方法。如果是應(yīng)用的方法梅屉,直接看可不可優(yōu)此方法值纱。如果是系統(tǒng)方法,通過查看其父方法坯汤,追溯至應(yīng)用方法虐唠。
而查看子方法,可以看出此方法到底做了什么惰聂。
如果要找UI卡頓的原因疆偿,可以從 具體Adaper類#getView 具體View#ondraw, 具體View#onMeasure等方法入手咱筛。
方法執(zhí)行時(shí)占用了CPU,所以執(zhí)行時(shí)間過長(zhǎng)會(huì)造成UI渲染被延遲,從而應(yīng)用不流暢翁脆。而GC同樣會(huì)占用CPU眷蚓,AS也同樣提供了查看GC的工具:
(譯者注:
Android性能分析工具Systrace和TraceView的使用)
Memory Profiling
點(diǎn)擊AS中的Android Monitor, 選中Memory | CPU 一欄, 界面如下
如圖所示,小幅的內(nèi)存下降一般就是發(fā)生了GC反番。
點(diǎn)擊左側(cè)的Heap dump沙热,會(huì)生成內(nèi)存中的所有對(duì)象的快照。
列名 | 意義 |
---|---|
Total Counts | 內(nèi)存中該類的對(duì)象個(gè)數(shù) |
Heap Count | 在該堆中該類的對(duì)象個(gè)數(shù)罢缸,左上角可以選擇App heap或Zygote heap |
Sizeof | 單個(gè)對(duì)象占用的Shallow Size |
Shallow Size | 所有對(duì)象所占用的Shallow Size |
Retained Size | 所有對(duì)象所占用的Retained Size,即GC后會(huì)釋放的內(nèi)存 |
instance | 該類一個(gè)具體的對(duì)象 |
Reference Tree | 引用這個(gè)對(duì)象的父對(duì)象篙贸,點(diǎn)擊父對(duì)象,展開這些父對(duì)象的父對(duì)象 |
什么是Shallow Size 和Retained Size
選中一行枫疆,點(diǎn)擊右側(cè)的一個(gè)instance,可以在下方看到Refrence Tree界面
在圖中可以看出MemoryActivity的一個(gè)instance在ListenerManager中被引用了爵川。如果MemoryActivity已經(jīng)不在Activity棧中了,這樣的引用就是內(nèi)存泄漏息楔。另外一個(gè)檢查內(nèi)存泄漏的工具是leakcanary
通過查看Retained Size和Reference Tree寝贡,我們可以知道哪些對(duì)象占用了較多的內(nèi)存,對(duì)象間的引用關(guān)系值依,進(jìn)而分析是否可以優(yōu)化數(shù)據(jù)結(jié)構(gòu)圃泡,減少引用關(guān)系,以減少內(nèi)存占用和GC頻率愿险。
Allocation Tracker
Memory | CPU一欄左側(cè)的另一個(gè)按鈕Allocation Tracker也是用于分析內(nèi)存占用颇蜡。點(diǎn)擊一次表示開始記錄,再次點(diǎn)擊表示停止記錄辆亏。
在結(jié)果頁面的左上方點(diǎn)擊餅狀圖风秤。
可以選擇 group by Allocator,即按對(duì)象劃分扮叨。
或者 group by method
Allocator下面的餅狀圖最外圍的是具體的類缤弦,內(nèi)部的是包名。圖中可以看出包或者對(duì)象占用的內(nèi)存大小或者個(gè)數(shù)甫匹,面積越大甸鸟,占用或者個(gè)數(shù)越多。選擇size可以查看占用內(nèi)存最多的對(duì)象兵迅,選擇count可以查看以及個(gè)數(shù)最多的對(duì)象。前者我們可以試著優(yōu)化類薪贫,后者我們可以嘗試建立一個(gè)Object pool來復(fù)用對(duì)象恍箭。
從group by method可以看出,decode方法占用的總內(nèi)存達(dá)10.91M, 就有可能是方法內(nèi)新建了太多對(duì)象瞧省,可以往這方面優(yōu)化扯夭。
內(nèi)存方面的tips:
1. Enums
Enums比int占的內(nèi)存大得多鳍贾,而且有替換方案@IntDef, 所以除了某些情況,比如你需要強(qiáng)制指定類型交洗,不然的話int會(huì)更節(jié)省內(nèi)存
2. 自動(dòng)裝箱
自動(dòng)裝箱指從基本類型自動(dòng)轉(zhuǎn)換到對(duì)象形式的(比如int到Integer)骑科,鑒于基本類型使用的場(chǎng)景和次數(shù)都較多,所以需要盡量避免使用其自動(dòng)裝箱的形式构拳。
3. HashMap vs ArrayMap / Sparse*Array
如果我們需要使用int作為Map的value咆爽,可以使用SparseIntArray,比起使用HashMap對(duì)int自動(dòng)裝箱置森,要省內(nèi)存的多斗埂。如果要使用Object作為Map的key,除了HashMap凫海,你也可以考慮使用ArrayMap,功能和HashMap一樣呛凶,但更省內(nèi)存,點(diǎn)擊了解原理行贪。盡管時(shí)間性能上HashMap更勝一籌漾稀,但除非你要存儲(chǔ)1000個(gè)以上對(duì)象,否則他們使用起來幾乎一樣快建瘫。
4. 注意Context對(duì)象
因?yàn)镃ontext在開發(fā)中的使用場(chǎng)景較多崭捍,所以最容易造成內(nèi)存泄漏。Activity本身是一個(gè)heavy的對(duì)象暖混,為了避免內(nèi)存泄漏缕贡,可以穿ApplicationContext的話,就不要傳Activity了拣播。
5. 避免非靜態(tài)內(nèi)部類
非靜態(tài)內(nèi)部類隱式持有外部類的引用晾咪,所以如果外部類不再被需要,但內(nèi)部類仍在使用狀態(tài)贮配,就造成了內(nèi)存泄漏谍倦。特別是Activity類,在定義內(nèi)部類的時(shí)候盡量定義成static的泪勒。
GPU Profiling
首先在手機(jī)的開發(fā)者選項(xiàng)頁面昼蛀,點(diǎn)擊GPU呈現(xiàn)模式分析(Profile GPU rendering),選中“In adb shell dumpsys gfxinfo” 然后在AS的Android Monitor 界面選中GPU一欄圆存,確保左上方的暫停按鈕沒有選中叼旋,此時(shí)AS就開始按照選定的包名顯示GPU情況了。每一個(gè)條直線表示UI渲染中的一幀沦辙,不同的顏色表示不同的繪制階段夫植。
- Draw(藍(lán)色) 執(zhí)行的是View#onDraw()方法。這個(gè)階段的工作是創(chuàng)建DisplayList對(duì)象油讯,這些對(duì)象稍后將被轉(zhuǎn)換成OpenGL命令详民,傳送到GPU延欠。如果藍(lán)色較長(zhǎng),一般是因?yàn)檩^復(fù)雜的View, 或者短時(shí)間內(nèi)invalidate了較多的View
- Prepare (紫色) Lollipop才引入的階段沈跨,用于加快UI渲染由捎,在線程RenderThread中執(zhí)行。這個(gè)階段的任務(wù)是將第一步產(chǎn)生的display lists轉(zhuǎn)換成OpenGL命令饿凛,并傳送到GPU狞玛。此時(shí)UI thread將繼續(xù)處理下一幀。UI Thread給RenderThread傳遞所需的資源產(chǎn)生的耗時(shí)也記錄在此階段中笤喳。當(dāng)有大量的資源要傳遞为居,比如很多/很heavy的display lists,這個(gè)階段耗費(fèi)的時(shí)間會(huì)增多杀狡。
- Process(紅色) 處理display lists蒙畴,產(chǎn)生OpenGL命令,較多或者較復(fù)雜的display lists會(huì)使此階段耗時(shí)增加呜象,因?yàn)楹芏郪iew將被redraw膳凝。View被redraw的情況有invalidate或者之前覆蓋在上面的View現(xiàn)在被移走了。
- Execute (黃色) 將OpenGL命令發(fā)送給GPU, 這是個(gè)阻塞方法恭陡,因?yàn)镃PU通過buffer將OpenGL命令發(fā)送給GPU, 當(dāng)處理完畢返回空的buffer蹬音。buffer的數(shù)量有限,所以當(dāng)GPU很忙休玩,buffer也用完了著淆,CPU就需要等待GPU處理完返回一個(gè)空的buffer,才能繼續(xù)發(fā)送OpenGL命令。因此如果這個(gè)階段耗時(shí)較多拴疤,一般是因?yàn)樵诶L制復(fù)雜的View永部。
在 Marshmallow 版本,增加了更多的顏色
根據(jù)谷歌工程師John Reck提供的信息,
圖中的Animation 是指所有通過Choreographer 注冊(cè)的CALLBACK_ANIMATION呐矾,包括
Choreographer#postFrameCallback View#postOnAnimation苔埋。這兩個(gè)函數(shù)在 view.animate(), ObjectAnimator, Transitions等場(chǎng)景中有用到。systrace中的Animation也是這個(gè)意思蜒犯。
misc指的是接收到的vsync的時(shí)間戳和當(dāng)前時(shí)間的延遲组橄。
很多人都看到過Choreographer的log "Missed vsync by。罚随。玉工。ms skipping 。淘菩。瓮栗。 frames",這就是misc瞄勾。換句話說费奸,就是在記錄幀狀態(tài)時(shí)INTENDED_VSYNC和VSYNC的差別
要使用這個(gè)功能,你需要在手機(jī)的開發(fā)者選項(xiàng)中開啟GPU rendering
此工具原理是ADB命令
adb shell dumpsys gfxinfo <PACKAGE_NAME>
如果你自己手動(dòng)敲此命令进陡,也會(huì)得到如下相關(guān)信息
如果我們的項(xiàng)目有自動(dòng)化UI測(cè)試工具愿阐,就可以在構(gòu)建服務(wù)器上在一些UI交互后(列表滑動(dòng),復(fù)雜動(dòng)畫)運(yùn)行此命令趾疚,查看“Janky Frames”等值是否有變化缨历。這樣做能夠幫我們確定最近的幾次提交(commite)是否影響了性能,在產(chǎn)品發(fā)布前發(fā)現(xiàn)和解決此問題糙麦。如果我們使用framestats作為關(guān)鍵字辛孵,還能獲得更多詳細(xì)的渲染信息。
我們還能以其他方式展示此圖
在“Profile GPU Rendering”選項(xiàng)里赡磅,有“On screen as bars”這個(gè)選項(xiàng)魄缚,選中它,在手機(jī)屏幕上就會(huì)出現(xiàn)三個(gè)圖像焚廊,分別代表StatusBar冶匹,NavBar和當(dāng)前程序的Activity的GPU Rending信息,以綠線指示16ms的渲染閾值咆瘟。
在右側(cè)這張圖嚼隘,我們看到有些幀超過了綠線,即說明渲染時(shí)間超過了16ms袒餐。這些“越界”的幀大部分是藍(lán)色飞蛹,我們大概可以認(rèn)為是因?yàn)槔L制了太多或者太復(fù)雜的View。我滑動(dòng)了一下此界面的信息流灸眼,的確有多種類型的View.有些會(huì)被重繪卧檐,有些比較復(fù)雜。所以那些“越界”的幀可能是因?yàn)檎诶L制復(fù)雜的View.
Hierarchy Viewer
我非常喜歡這個(gè)工具幢炸,很遺憾好多人都沒有使用過泄隔。
Hierarchy Viewer能顯示性能情況,屏幕上完整的View結(jié)構(gòu)宛徊,以及View的屬性佛嬉。如果你單獨(dú)運(yùn)行Hierarchy Viewer,而不是從Android Monitor啟動(dòng)闸天,你也能獲取主題信息暖呕,所有的style屬性女轿。當(dāng)我設(shè)計(jì)以及優(yōu)化布局時(shí)就會(huì)這么做悲立。
在上圖中間习绢,我們能看到樹形的View結(jié)構(gòu)。View結(jié)構(gòu)可以很寬氧映,但是如果View層數(shù)太深(比如10層左右),就會(huì)增加昂貴的layout和measurement操作拗秘。測(cè)量View時(shí)調(diào)用View.onMeasure, 布局子View時(shí)調(diào)用View.onLayout签舞。這兩個(gè)命令會(huì)向子View傳遞。有些布局會(huì)調(diào)用兩次這兩個(gè)命令戚揭,比如RelativeLayout和某些LinearLayout诱告,如果View是嵌套的,命令傳遞的次數(shù)就會(huì)以指數(shù)增長(zhǎng)民晒。
在右下角精居,是布局實(shí)際的效果,標(biāo)注了每個(gè)View放置的位置潜必,我們可以在此圖或者樹形圖中選中一個(gè)View, 然后在左邊查看所有的屬性靴姿。當(dāng)設(shè)計(jì)布局時(shí),我有時(shí)候不能確定為什么某個(gè)View會(huì)被布局在那里磁滚。有了這個(gè)工具佛吓,我就能在樹形圖上找到它,選中恨旱,然后就能在預(yù)覽圖中看到它的位置辈毯。在設(shè)計(jì)有趣的動(dòng)畫時(shí),我會(huì)查看屏幕中View的最終測(cè)量數(shù)據(jù)搜贤,據(jù)此精確的移動(dòng)View谆沃。我也能發(fā)現(xiàn)被無意蓋掉而看不見的View。
對(duì)于每一個(gè)View,我們都可以獲知它本身以及子View的measure/layout/draw時(shí)間仪芒。顏色指示它與其他View比較時(shí)唁影,性能情況如何,有助于找出View繪制過程中最慢的一環(huán)掂名。而且我們能看到View的預(yù)覽据沈,能在樹形圖中看到此View創(chuàng)建的步驟,找出和刪除冗余的步驟饺蔑。影響性能的一個(gè)重要原因是Overdraw锌介。
(譯者注:
Android Studio的
File——Settings——Inspections——Android Lint可以設(shè)置檢測(cè)layout的深度和寬度:
勾選 Layout has too many views 和 Layout hierarchy is too deep
Layout has too many views表示控件太多,默認(rèn)超過80個(gè)控件會(huì)提示該問題猾警。
Layout hierarchy is too deep表示布局太深孔祸,默認(rèn)層級(jí)超過10層會(huì)提示該問題,可以自定義環(huán)境變量ANDROID_LINT_MAX_DEPTH來修改发皿。
布局層數(shù)過深會(huì)導(dǎo)致StackOverflowErrors崔慧,因?yàn)閐raw() dispatchDraw() drawChild()等方法都會(huì)消耗stack空間)
Overdraw
正如GPU Profiling所呈現(xiàn)的,如果GPU需要在屏幕上進(jìn)行大量的繪制穴墅,Execute階段(GPU中的黃色部分)會(huì)花費(fèi)更多的時(shí)間惶室,繪制每一幀所需的時(shí)間也就拉長(zhǎng)了温自。在已繪制的屏幕上再度繪制,就叫做過度繪制皇钞,比如在紅色的背景上繪制一個(gè)黃色的按鈕悼泌。GPU需要先繪制紅色的背景,然后繪制黃色的按鈕鹅士,就造成了過度繪制券躁。如果是很多層的過度繪制,GPU的工作負(fù)荷就很重掉盅,就會(huì)影響到16ms的性能指標(biāo)。
啟用開發(fā)者選項(xiàng)中的“調(diào)試GPU過度繪制”以舒,所有的過度繪制就會(huì)按照嚴(yán)重程度用不同的顏色展示趾痘。1~2層的過度繪制算合理,有小范圍的淺紅也還能接受蔓钟,但是如果紅色的區(qū)域太多永票,就需要注意了。舉幾個(gè)例子:
左圖中滥沫,有一個(gè)綠色的列表侣集,通常意味著還行,但頂部的覆蓋區(qū)域顯示為紅色兰绣,就需要解決了世分。右圖中,整個(gè)列表都是淺紅缀辩。兩個(gè)圖的列表都不透明臭埋,都有2~3層的過度繪制。一個(gè)可能的原因是持有Activity/Fragment的窗口(window)臀玄,ListView瓢阴,以及每個(gè)ListView的item都有各自的背景。解決的辦法是只設(shè)置一個(gè)背景健无。
注意:默認(rèn)的主題設(shè)置了窗口的背景色荣恐,如果你的Activity包含的不透明的布局能覆蓋全屏幕,你就可以通過移除窗口的背景色來減少過度繪制累贤〉拢可以在主題中設(shè)置,或者在onCreate方法中調(diào)用 getWindow().setBackgroundDrawable(null)
利用 Hierarchy Viewer, 你能導(dǎo)出所有層級(jí)到 PSD 文件畦浓。用Photoshop打開此文件痹束,查看不同的層級(jí),你能發(fā)現(xiàn)布局中所有的過度繪制. 請(qǐng)利用這些信息去掉冗余的過度繪制讶请,不要調(diào)試GPU過度繪制時(shí)顯示綠色就覺得可以了祷嘶,爭(zhēng)取藍(lán)色屎媳。
Alpha
使用透明屬性可能會(huì)影響性能。為了理解這句話论巍,我們來看看當(dāng)給View設(shè)置alpha屬性時(shí)烛谊,會(huì)發(fā)生些什么?初始布局如下圖所示
此布局包含三個(gè)互相有重疊的ImageView嘉汰。如果對(duì)布局設(shè)置了Alpha屬性丹禀,也就是調(diào)用 setAlpha(),“直接/簡(jiǎn)單粗暴的方案”是對(duì)各個(gè)子View(在此例中是三個(gè)ImageView)設(shè)置Alpha鞋怀。這樣三個(gè)ImageView會(huì)依據(jù)設(shè)置的Alpha重新繪制并進(jìn)入幀緩沖双泪。結(jié)果如下:
這不是我們想要的。
因?yàn)槊總€(gè)Image都設(shè)置了alpha密似,重疊的ImageView就混合在一起了焙矛。幸運(yùn)的是,安卓系統(tǒng)找到了解決方案残腌。布局將會(huì)被復(fù)制到離屏緩沖村斟,并對(duì)緩沖區(qū)整體設(shè)置Alpha,處理結(jié)果將會(huì)復(fù)制回幀緩沖抛猫。結(jié)果如下:
但是蟆盹。。這樣做是有代價(jià)的闺金。
先在離屏緩存區(qū)繪制View,然后再繪制幀緩存逾滥,實(shí)際上是增加了一層未被GPU Profiling檢測(cè)到的過度繪制。安卓系統(tǒng)不知道何時(shí)使用此方案掖看,何時(shí)使用之前提到的方案匣距,所以只能默認(rèn)使用此方案。但我們?nèi)杂修k法設(shè)置Alpha并且避免離屏緩存造成的復(fù)雜性哎壳。
- TextViews - 調(diào)用 setTextColor() 而不是 setAlpha(). 使用alpha 通道設(shè)置字體顏色, 繪制文本時(shí)會(huì)直接使用此alpha毅待。
- ImageView - 使用 setImageAlpha() 而不是 setAlpha() 。理由同上归榕。
- 自定義 View - 如果你的自定義View不支持重疊子View, 就無須理會(huì)此合成操作尸红, 因?yàn)樽覸iew不會(huì)像上面的例子混合在一起. 覆寫hasOverlappingRendering() 方法,并返回 false,我們就在告訴安卓系統(tǒng):使用“直接/簡(jiǎn)單粗暴的方案”。如果要自己處理alpha屬性姆蘸,就覆寫onSetAlpha()方法裂问,并返回true。詳細(xì)請(qǐng)參考文章1甜孤,文章2, 文章3
(譯者注:
其他過度繪制優(yōu)化實(shí)踐
Android性能優(yōu)化之減少UI過度繪制
實(shí)戰(zhàn) Android中的UI過度繪制
)
Hardware Acceleration
安卓蜂巢版引進(jìn)了硬件加速功能铭污,我們由此有了新的繪制模型來渲染APP。硬件加速引入了DisplayList結(jié)構(gòu)喻鳄,通過記錄 View 的繪制命令來加快渲染再菊。但是有一個(gè)很重要的功能是開發(fā)者往往遺漏或者沒有正確的使用的—View layers。
使用View layer竿奏,我們能在離屏緩存中渲染View(就像上面應(yīng)用Alpha通道的例子)袄简,并隨意操作View。這個(gè)功能在動(dòng)畫的時(shí)候很有用泛啸,能讓復(fù)雜的View在動(dòng)畫時(shí)更順暢绿语。 不使用layer的話, 對(duì)View執(zhí)行動(dòng)畫時(shí)會(huì)先改變其屬性(比如 x 坐標(biāo),伸縮比候址,透明度等)然后invalidate. 對(duì)于復(fù)雜的View, invalidate 操作會(huì)傳遞到各個(gè)子View, 各個(gè)子View都會(huì)重繪吕粹,是個(gè)昂貴的操作。而Hardware提供的View layer 則是在GPU中為View生成一個(gè)紋理(Texture)岗仑。利用texture匹耕,某些操作(改變x/y軸坐標(biāo),旋轉(zhuǎn)荠雕,alpha等)就不需要invalidate了稳其。這一切意味著,我們能在一個(gè)復(fù)雜的View上執(zhí)行動(dòng)畫炸卑,而不需要invalidate! 這會(huì)讓動(dòng)畫順暢很多既鞠。下面的例子將告訴你如何做:
// Using the Object animator
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
objectAnimator.start();
// Using the Property animator
view.animate().translationX(20f).withLayer().start();
很簡(jiǎn)單,是不是盖文?
不過使用hardware layer時(shí)需要注意以下幾點(diǎn):
Hardware layer 占用了GPU有限的內(nèi)存嘱蛋,所以請(qǐng)只在動(dòng)畫等需要的場(chǎng)合使用Hardware layer,使用完后及時(shí)清理五续。上例中洒敏,使用ObjectAnimator時(shí),我設(shè)置了一個(gè)監(jiān)聽器疙驾,在動(dòng)畫結(jié)束時(shí)移除layer凶伙。使用Property animator時(shí), 我使用了withLayers()方法, 此方法會(huì)在動(dòng)畫開始時(shí)自動(dòng)創(chuàng)建layer并在動(dòng)畫結(jié)束時(shí)移除。
如果你在應(yīng)用了hardware layer以后改變了View的屬性, 就會(huì)invalidate hardware layer 并在離屏緩存重新渲染一遍View荆萤。這種情況在執(zhí)行Hardware Layer未優(yōu)化的操作時(shí)會(huì)發(fā)生 ( 到目前為止, 被優(yōu)化的操作包括: 旋轉(zhuǎn), 伸縮, 坐標(biāo)設(shè)置, 坐標(biāo)偏移, pivot(樞軸) 和 透明度)镊靴。比如 , 你對(duì)使用了hardware layer的View執(zhí)行動(dòng)畫 ,一邊位移一邊更新 View 的背景色链韭,就會(huì)導(dǎo)致hardware layer不停的更新. 在這種情況下偏竟,更新hardware layer的開銷會(huì)抵消掉使用它帶來的好處。
在第二種情況下敞峭,我們可以查看hardware layer更新的情況踊谋。即在開發(fā)者選項(xiàng)中啟用 “顯示硬件層更新”
啟用后,View在更新其hardware layer時(shí)會(huì)以綠光閃爍旋讹。不久前我的一個(gè)ViewPager滑動(dòng)起來不流暢時(shí)我就啟動(dòng)了此選項(xiàng). 下圖是我當(dāng)時(shí)所看到的:
在整個(gè)的滑動(dòng)過程中殖蚕,兩個(gè)Page都顯示綠色轿衔!
這意味著有hardware layer被創(chuàng)建,并且滑動(dòng)的過程中頁面被重新渲染睦疫。我的確在滑動(dòng)時(shí)更新了頁面害驹,對(duì)背景使用了視差效果,并且頁面中的內(nèi)容也有漸進(jìn)的動(dòng)畫效果蛤育。但是我并沒有主動(dòng)創(chuàng)建hardware layer宛官。在閱讀了ViewPager的源碼后,我發(fā)現(xiàn)當(dāng)用戶滑動(dòng)時(shí)瓦糕,會(huì)為兩個(gè)頁面創(chuàng)建hardware layer底洗,并且在滑動(dòng)停止時(shí)移除。
盡管滑動(dòng)頁面時(shí)創(chuàng)建hardware layer是合理的行為咕娄,但不適合我的這種情形亥揖。通常情況下,我們并不會(huì)在滑動(dòng)頁面的同時(shí)更新頁面圣勒,而頁面內(nèi)容也往往很復(fù)雜—?jiǎng)?chuàng)建hardware layer就可以幫助渲染的更快费变。在我的這個(gè)應(yīng)用中,卻并不是這樣的圣贸,因此我hack了一下胡控,移掉了hardware layer。
hardware layer并不是銀彈旁趟,理解它是如何工作的,并恰當(dāng)?shù)氖褂煤苤匾诱溃駝t你會(huì)可能會(huì)發(fā)現(xiàn)得不償失锡搜。
DIY
為了演示我提到的這些例子,我寫了很多代碼來模擬情景瞧掺。你能在 Github repository和 Google Play找到所有的演示例子耕餐。
更多的信息
隨著Android 操作系統(tǒng)的演進(jìn),你優(yōu)化APP的方式也得跟著變化辟狈。新的Android SDK會(huì)引入新的工具和功能(比如hardware layers)肠缔。跟上趨勢(shì),在改動(dòng)代碼時(shí)權(quán)衡利弊哼转,這很重要明未。
YouTube上有一個(gè)很棒的播放列表, Android Performance Patterns壹蔓,是一系列谷歌出品的小視頻趟妥,闡述了性能優(yōu)化的各個(gè)方面。比如比較不同的數(shù)據(jù)結(jié)構(gòu)(HashMap vs ArrayMa)佣蓉,如何優(yōu)化Bitmaps 披摄,甚至是優(yōu)化網(wǎng)絡(luò)請(qǐng)求亲雪。我強(qiáng)烈建議把播放列表都看一遍。
請(qǐng)加入 Android Performance Patterns Google+ community 疚膊,一起和谷歌工程師在內(nèi)的人士討論性能問題义辕,分享觀點(diǎn),文章和問題寓盗。
更多有趣的鏈接:
- 了解安卓圖形架構(gòu)如何運(yùn)行灌砖。文章完整的解釋了如下主題:
Android是如何渲染UI的,不同系統(tǒng)組件的差異和通信(比如SurfaceFlinger)贞让。文章很長(zhǎng)周崭,但值得一讀。 -
Google IO 2012的一則演講
闡述了繪制模型的工作原理喳张,以及UI失幀的原因续镇。 - Android Performance Workshop talk,來自Devoxx 2013销部,展示了Android 4.4 引入的繪制模型的優(yōu)化摸航,以及用于優(yōu)化性能的不同工具(Systrace, Overdraw等)
- 關(guān)于防御性優(yōu)化的好文章,解釋了其與過早優(yōu)化的區(qū)別舅桩。
很多開發(fā)者不想優(yōu)化代碼酱虎,因?yàn)樗麄冇X得造成的影響不易覺察。請(qǐng)記住擂涛,積少成多會(huì)導(dǎo)致大問題读串。如果有機(jī)會(huì)做一個(gè)小的優(yōu)化,盡管微小撒妈,但請(qǐng)別放棄恢暖。 -
Memory managment in Android
Google IO 2011的一個(gè)視頻,有點(diǎn)老舊狰右,但仍然有價(jià)值杰捂。說明了安卓如何管理內(nèi)存,以及檢測(cè)工具如Eclipse MAT -
Case study
Romain Guy的作品棋蚌,內(nèi)容是優(yōu)化Twitter客戶端嫁佳。介紹了他如何發(fā)現(xiàn)APP的性能問題,也推薦了解決方案谷暮。這里有個(gè) follow-up post蒿往,繼續(xù)跟進(jìn)了此APP的性能優(yōu)化問題。
我希望你們已經(jīng)獲取了足夠的信息和自信來優(yōu)化你們的APP坷备,并從今天就開始優(yōu)化熄浓。
trace用起來,開發(fā)者選項(xiàng)用起來,你已經(jīng)走在優(yōu)化的路上了赌蔑。你的任何新發(fā)現(xiàn)俯在,都可以在這留言,或者發(fā)表在Android Performance Patterns Google+ community