編輯:關於android開發
近期讀到《Speed up your app》一文。這是一篇關於Android APP性能分析、優化的文章。在這篇文章中,作者介紹他的APP分析優化規則、使用的工具和方法。我覺得值得大家借鑒。英文好的讀者可讀原文(鏈接:http://blog.udinic.com/2015/09/15/speed-up-your-app)。
作者每次著手處理或尋找性能問題時,遵循下列規則:
在更新APP前後,用測試工具軟件多檢測幾次APP性能,可快速得到測試數據。這些數字是不會說謊的。而僅僅用眼睛觀察APP性能是肯定不行的。如你在觀察幾次相同的動畫效果後,你就想象它運行的夠快了,從而忽略一下問題。
如今硬件性能在不斷的提升,如果僅在最新設備運行APP,可能不能充分暴露出APP中存在的性能問題。另外,盡管用戶設備換手率已經很高了,但仍然不是所有用戶都是使用最新的和最強功能的設備。所以應該使用低端設備上,運行APP,這可以幫助你更有效地發現問題。
性能優化是要綜合各方面因素進行評判、權衡。因為優化一項性能可能是要以犧牲另一項性能為代價的。分析、查找、修復是要花費很多時間。你要准備好自我犧牲。
作者采用自頂向下方法,從手機運行的概況開始,逐級深入分析:方法的性能、內存使用情況、GPU渲染效果、視圖層次、圖形疊加繪制、圖像透明值;解釋Honeycomb引入的硬件加速以及視圖層。
手機實際就是一台功能強大的計算機,同時間做很多事情。Systrace能夠展示手機運行的概況。
作者在Systrace中選取一個Alert,做為例子,講解分析發現問題的方法:
1)由Alert找到函數(如:long View#draw()),再展開“Inflation during ListView recycling”
2)可以查看到函數的耗時,更詳細觀察分析其中哪項花費時間較長。
3)選擇一幀查看它花費多久時間。如有一幀繪制用時超過19ms。展開“Scheduling delay”
4)它的值(Wall duration和CPU duration之間的差異)表明有很長時間CPU沒有安排這個線程。
這就需要查查在整個這段時間裡CPU都做了什麼?但是Systrace只能查看運行概況,還不能得到更深層次的分析。為了找到CPU運行繁忙的真正原因,作者使用另一個工具:Traceview。
Traceview是性能分析工具,可以顯示每個方法運行時間。可從Android Device Monitor中啟動,也可從代碼中啟動。
作者以“滾屏”動作為例說明Traceview分析方法。在“滾屏”動作的跟蹤記錄中,找到getView()方法,發現它被調用12次,每次CPU用時約3ms。但是每次完成整個調用卻用時162ms!這就是個問題!
作者就繼續查看getView()的子方法,查看各個子方法所用時間在總時間中比例。他發現Thread.join()的Inclusive Real Time占用約98%。他順籐摸瓜,找啟動子方法的Thread.run()方法(它是創建一個新線程時所調用的方法),逐個跟著,直到找到“元凶”:BgService.doWork()方法。
另外,GC(Garbage Collector – 垃圾收集器)不定期運行清理不用的對象。GC的頻繁運行也會使得APP運行慢下來。為此,作者的下一步就是針對內存進行分析。
Android Studio逐步在改善,有越來越多的工具可以幫助我們找到和分析性能問題。作者用其分析內存使用情況。
Heap Dump可以看到Heap中依據類名排序實例的直方圖。每個都有分配對象的總數,實例的大小和留在內存中對象的大小。後者告訴我們這些實例釋放後,能夠釋放多少內存。這可幫助我們識別出大的數據結構和對象關系。這些信息可以幫助我們構建更有效的數據結構,解開對象之間聯系以減少內存駐留,最終盡可能的減少內存占用。
在分析中,可發現“內存洩漏”。解決方法就是要記得在活動即將被銷毀時調用onDestory()方法刪除引用。
內存洩漏和較大對象的heap空間占用,使得有效內存減少,頻繁引發GC事件,試圖釋放更多的heap空間。這些GC事件占用CPU,降低了APP性能。如果有效的內存數量不足與滿足APP,且heap空間不能在擴大,就引發 —— OutOfMemortException —— APP崩潰。
Eclipse MAT(Eclipse Memory Analyze Tool)也是不錯的內存分析工具。
Allocation Tracker可生成在跟蹤期間內存分配給所有實例的情況報告,報告可按類分組或按方法分組。它以很好的可視化方式展示哪個實例所獲得內存最多。
使用這些信息,可以找出分配大內存的方法,和可能頻繁觸發GC的事件。
作者給出一些技巧:
一直是討論性能的熱門話題。枚舉比普通常數占用更多的內存空間嗎?是的。但這肯定是壞事嗎?未必。如在編寫代碼庫,需要強類型安全性,這就應該使用它。如果有一組可以歸結在一起的常數,此時使用枚舉也許不不合適。怎樣決定,你需要權衡考慮。
是自動將原始數據類型轉換為其對應的對象表示(如:int 到 Integer)。每次原始數據類型被“裝箱”到對象,就會產生一個新的對象(我知道這令人震驚)。如果有許多這樣的操作,那麼GC就頻繁地運行。由於將原始類型數據賦值到對象時,自動進行auto-boxing的,就很容易忽視它的數量。解決方案就是盡量使數據類型保持一致。如果要在整個APP中使用原始數據類型,就盡量避免在沒有實際需要時進行Auto-boxing。使用內存分析工具可以找到許多對象是表示原始數據類型。也可以用Traceview尋找到Integer.valueOf(),Long.valueOf()等等。
在Auto-boxing相關問題中,使用HashMap時,就要求用對象作為鍵值。如果在APP中用原始int類型,那麼在使用HashMap時就需要將int轉化到Integer。這種情況也許就需要用SparesIntArray。如果在鍵值仍然需要對象類型的情況下,也可以改用ArrayMap類。它非常類似HashMap,只是在其內部工作方式不同,是以降低速度為代價,使用較少的內存。這兩者占用內存都比HashMap小,但檢索所花費的時間略高於HashMap。如果數據項少於1000,它們的運行時沒有什麼差別。
Activity內存比較容易洩漏。由於它們保持UI的所有視圖層次,占用大量的空間,所以它們的洩漏也是非常“昂貴的”。許多操作都要求Context對象,你發起Activity。假如引用被緩存,並且該對象的存活期要長於你的Activity,如果沒有清理它的引用,你就產生了內存洩漏。
創建一個非靜態內部類,並實例化它,就創建對外部類的隱式引用。如果內部類實例需要的時間比外部類長,這外部類就要在內存中保留,即使它不再需要了。例如,在Activity類內部,創建一個擴展AsyncTask的非靜態類,然後著手啟動異步任務,在它運行時,銷毀活動。該異步任務在其運行期間,都保持這一Activity運行。解決方案就是不要這樣做。如果需要這樣,就聲明一個靜態內部類。
Android Studio 1.4增加一項新功能:分析GPU渲染功能。作者詳細講解這一新功能的分析方法。
在GPU選項卡下,可以在屏幕上看到圖形化顯示的渲染每幀所花費的時間。圖形中每條都表示被渲染的一幀。顏色表示進程的不同周期:
表示View#onDraw()方法。那部分建立/更改DisplayList對象,然後轉換成GPU能夠理解的OpenGL命令。高的條形可能是視圖復雜,而要求更多的時間繪制它們的顯示列表,而許多視圖在短時間內就失效了。
在Lollipop中,加入另一個線程,以幫助UI線程渲染更快。這個線程叫:RenderThread。它的責任是轉換顯示列表為OpenGL命令,再發送給GPU。這樣在渲染過程中,UI線程可以開始處理下一個幀。這時UI線程將所有資源傳送給RenderThread。如果有許多資源要傳遞(如許多/繁重顯示列表),這一步可能需要較長時間。
執行顯示列表產生OpenGL命令。由於需要視圖重繪,如果有許多/復雜顯示列表要執行轉換,這一步可能需要較長時間。當視圖無效或是移動時,都要要重繪視圖。
發送OpenGL命令到GPU。由於CPU發送這些緩存的命令到GPU,並期待收回干淨緩存,這就阻塞調用了。緩存數量有限,並且GPU也很忙 —— CPU會發現自己必須先等待緩存釋放。因此,如果在這一步我們見高的條形,就可能意味著GPU在繪制UI時非常忙,這個繪制在短時間內太復雜了。
具體操作實例見原文。
作者喜愛這個工具。他對許多開發者根本不使用這工具感到失望。
使用Hierarchy Viewer,可以完整地觀察到屏幕視圖層次和所有視圖的屬性。還可以導出主題(theme)數據,查看到每個樣式的所有屬性。但是,這只是在Hierarchy Viewer獨立運行時,才能查看這些數據。而不可以從Android監控器中查看。
作者在設計布局和優化布局時使用這個工具。
作者認為有時間,可以對每張視圖都測量以及它的所有子視圖。顏色表示視圖與樹中其他視圖的比較情況,很容易找出最薄弱的環節。由於我們可以預覽視圖,這樣就可通過視圖樹,跟蹤找出可刪除的冗余步驟。這其中,對性能影響最大的,被稱為Overdraw。
如果GPU需要在屏幕上繪制很多內容,繪制每幀都需要增加時間,這樣執行周期就拉長了,在圖形中以黃色表示。在一些圖形上再疊加繪制,如在紅色背景上繪制黃色按鈕,這就發生Overdraw。這種情況下,GPU需要先繪制紅色背景,再在其上繪制黃色按鈕,Overdraw就不可避免了。如果有太多的Overdraw層,這就使得GPU超負荷運行,偏離16ms的目標。
設置“Debug GPU Overdraw”開發者選項,所有Overdraw的嚴重程度都以顏色表示出來。1~2倍的Overdraw算好的,甚至有些小的紅色區也不壞。但是如果在屏幕上有許多紅色,這就有問題了。但是都被紅色覆蓋。這就是問題了。作者建議這時僅用一種顏色設置背景來解決這個問題。
注意:默認主題聲明一個全屏窗口背景顏色。如果有不透明布局的Activity覆蓋在整個屏幕上,可以通過刪除窗口的背景色消除這層Overdraw。
Hierarchy Viewer能夠輸出所有層次到PSD文件中,用Photoshop中打開。在Photoshop中研究不同的層就可展示布局中的所有Overdraw。刪除冗余的Overdraw,努力性能提高到藍色上。
使用透明效果也會影響性能。為什麼?
ImageView相互重疊。用setAlpha()設置alpha值,這將傳遞給所有的子視圖,對幀緩沖區進行繪制。結果都重疊混在一起。幸好,OS有這個問題的解決方案。布局數據被復制到off-screen緩沖區,用alpha值對off-screen緩沖區進行處理後,再復制到幀緩沖區中。效果就好了些。但是,我們為此付出了代價:把“幀”緩沖區改為off-screen緩沖區,實際上增加了一個隱含的Overdraw層。OS就不知道處理了,所以默認情況下經常要進行復雜地操作。
不過還是有方法設置alpha值,避免off-screen緩沖區增加的復雜性:
用setTextColor()替代setAlpha()。使用文本顏色的alpha通道,就可直接用它來繪制的文本。
用setImageAlpha()替代setAlpha()。理由同TextView。
如果自定義視圖不支持覆蓋視圖,這復雜行為是無關緊要的。可通過重寫hasOverlappingRendering()方法,讓其返回false,通知OS直接繪制自定義的視圖。還可以通過重寫onSetAlpha()方面,讓其返回true,選擇手動處理設置,各alpha值對應的操作。
在Honeycomb(蜂巢,Android 3.x)引入硬件加速後,在屏幕上渲染APP可以以新的繪制模型(http://developer.android.com/guide/topics/graphics/hardware-accel.html)進行。新模型引入DisplayList結構,記錄視圖渲染繪制命令。還有另一個很好的特性,時常被開發人員忽視或不正確地使用 — 視圖層。
使用視圖層,我們能夠非屏幕緩沖區(如前面所見,應用alpha通道)渲染視圖,並且能按照我們的意願操控它。由於利用這一特性能夠更快地繪制復雜動畫視圖,所以它主要用於動畫。沒有這些層次,在改變動畫屬性(如:x坐標、縮放、alpha值等等)後,動畫視圖將無效。對於復雜視圖,這個無效效果都傳遞到所有子視圖,且重繪的成本很高。在硬件支持下,使用視圖層時,GPU會為視圖創建紋理。有幾個操作可以用於紋理,而不會破壞它,如:X / Y位置、旋轉、alpha等等。所有都意味著在動畫期間,可以在屏幕上繪制復雜動畫視圖,而完全不會破壞它。這使得動畫更加順暢。
提出在使用硬件層時需要記住幾件事:
作者為說明性能分析、優化,准備很多代碼來模擬情景。這些代碼可以在Github代碼庫(https://github.com/Udinic/PerformanceDemo) 或是 Google Play(https://play.google.com/store/apps/details?id=com.udinic.perfdemo) 找到。他將不同的場景分別放到不同的Activity中,並它們編寫文檔,盡可能幫助理解使用這些Activity會遇到哪方面的問題。可以用工具和運行APP來閱讀Activity的javadoc。
作者還推薦一些學習交流方法:
作者希望大家已獲得足夠資料和更強的自信。從今天開始優化自己的APP!
作者關於性能優化的演講視頻在這裡:http://www.youtube.com/embed/v3DlGOQAIbw?color=white&theme=light
android高仿京東垂直循環滾動新聞欄 京東的垂直滾動新聞欄的實現原理: 就是一個自定義的LinearLayout,並且textView能夠循環垂直滾動,而且條目可以
微信小程序:原生熱布局終將改變世界,微信小程序布局 最近朋友圈已經被微信小程序刷屏了,這也難怪,騰訊的產品擁有廣泛的影
Android 善用Okio簡化處理I/O操作 Okio庫是一個由square公司開發的,它補充了java.io和java.nio的不足,以便能夠更加方便,快速的訪問、
Android基礎部分再學習---activity的狀態保存 學習Activity的生命周期,我們知道,當Activity進入到paused或者stopped狀態後,這個