天天看點

Android 性能優化探究

  • 使用ViewStub動态載入布局。避免一些不常常的視圖長期握住引用:

    ViewStub的一些特點:

    1. ViewStub僅僅能Inflate一次,之後ViewStub對象被置空:某個被ViewStub指定的布局被Inflate後,就不會夠再通過ViewStub來控制它了。

    2. ViewStub僅僅能用來Inflate一個布局檔案,而不是某個詳細的View。當然也能夠把View寫在某個布局檔案裡。

    基于以上的特點。那麼能夠考慮使用ViewStub的情況有:

    1. 在程式的運作期間,某個布局在Inflate後,就不會有變化。除非又一次啟動。

    由于ViewStub僅僅能Inflate一次,之後會被置空,是以無法指望後面接着使用ViewStub來控制布局。

    是以當須要在運作時不止一次的顯示和隐藏某個布局,那麼ViewStub是做不到的。這時就僅僅能使用View的可見性來控制了。

    2. 想要控制顯示與隐藏的是一個布局檔案。而非某個View。

    由于設定給ViewStub的僅僅能是某個布局檔案的Id,是以無法讓它來控制某個View。

  • 硬體加速:android:hardwareAccelerated
<application
        android:name="com.tchip.carlauncher.MyApplication"
        android:icon="@drawable/ic_launcher"
        android:label="Car Launcher"
        android:hardwareAccelerated="true"
        android:largeHeap="true"
        android:theme="@android:style/Theme.Holo.Light" >           
  • View緩存:setDrawingCache
hsvMain = (HorizontalScrollView) findViewById(R.id.hsvMain);
    hsvMain.setDrawingCacheEnabled(true);           
  • 将Acitivity 中的Window 的背景圖設定為空:
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setBackgroundDrawable(null);
        setContentView(R.layout.activity_main);
        }           
以下内容轉自:hukai

Android性能優化典範(一)

Android 性能優化探究

2015新年伊始,Google公布了關于Android性能優化典範的專題。一共16個短視訊。每一個3-5分鐘,幫助開發人員建立更快更優秀的Android App。

課程專題不僅僅介紹了Android系統中有關性能問題的底層工作原理,同一時候也介紹了怎樣通過工具來找出性能問題以及提升性能的建議。主要從三個方面展開,Android的渲染機制。記憶體與GC,電量優化。以下是對這些問題和建議的總結梳理。

0)Render Performance

大多數使用者感覺到的卡頓等性能問題的最主要根源都是由于渲染性能。從設計師的角度,他們希望App能夠有很多其它的動畫。圖檔等時尚元素來實作流暢的使用者體驗。可是Android系統非常有可能無法及時完畢那些複雜的界面渲染操作。Android系統每隔16ms發出VSYNC信号,觸發對UI進行渲染,假設每次渲染都成功,這樣就能夠達到流暢的畫面所須要的60fps。為了能夠實作60fps。這意味着程式的大多數操作都必須在16ms内完畢。

Android 性能優化探究

假設你的某個操作花費時間是24ms。系統在得到VSYNC信号的時候就無法進行正常渲染,這樣就發生了丢幀現象。那麼使用者在32ms内看到的會是同一幀畫面。

Android 性能優化探究

使用者easy在UI運作動畫或者滑動ListView的時候感覺到卡頓不流暢。是由于這裡的操作相對複雜,easy發生丢幀的現象,進而感覺卡頓。

有非常多原因能夠導緻丢幀。或許是由于你的layout太過複雜。無法在16ms内完畢渲染,有可能是由于你的UI上有層疊太多的繪制單元,還有可能是由于動畫運作的次數過多。

這些都會導緻CPU或者GPU負載過重。

我們能夠通過一些工具來定位問題,比方能夠使用HierarchyViewer來查找Activity中的布局是否過于複雜。也能夠使用手機設定裡面的開發人員選項,打開Show GPU Overdraw等選項進行觀察。你還能夠使用TraceView來觀察CPU的運作情況,更加快捷的找到性能瓶頸。

1)Understanding Overdraw

Overdraw(過度繪制)描寫叙述的是螢幕上的某個像素在同一幀的時間内被繪制了多次。

在多層次的UI結構裡面,假設不可見的UI也在做繪制的操作,這就會導緻某些像素區域被繪制了多次。

這就浪費大量的CPU以及GPU資源。

Android 性能優化探究

當設計上追求更華麗的視覺效果的時候,我們就easy陷入採用越來越多的層疊元件來實作這樣的視覺效果的怪圈。這非常easy導緻大量的性能問題。為了獲得最佳的性能。我們必須盡量降低Overdraw的情況發生。

幸運的是,我們能夠通過手機設定裡面的開發人員選項,打開Show GPU Overdraw的選項,能夠觀察UI上的Overdraw情況。

Android 性能優化探究

藍色。淡綠,淡紅。深紅代表了4種不同程度的Overdraw情況,我們的目标就是盡量降低紅色Overdraw,看到很多其它的藍色區域。

Overdraw有時候是由于你的UI布局存在大量重疊的部分,還有的時候是由于非必須的重疊背景。比如某個Activity有一個背景。然後裡面的Layout又有自己的背景,同一時候子View又分别有自己的背景。僅僅是通過移除非必須的背景圖檔。這就能夠降低大量的紅色Overdraw區域。添加藍色區域的占比。這一措施能夠顯著提升程式性能。

2)Understanding VSYNC

為了了解App是怎樣進行渲染的,我們必須了解手機硬體是怎樣工作,那麼就必須了解什麼是VSYNC。

在解說VSYNC之前,我們須要了解兩個相關的概念:

  • Refresh Rate:代表了螢幕在一秒内重新整理螢幕的次數。這取決于硬體的固定參數。比如60Hz。
  • Frame Rate:代表了GPU在一秒内繪制操作的幀數。比如30fps,60fps。
    Android 性能優化探究

不幸的是,重新整理頻率和幀率并非總能夠保持同樣的節奏。假設發生幀率與重新整理頻率不一緻的情況,就會easy出現Tearing的現象(畫面上下兩部分顯示内容發生斷裂,來自不同的兩幀資料發生重疊)。

Android 性能優化探究
Android 性能優化探究

了解圖像渲染裡面的雙重與三重緩存機制。這個概念比較複雜,請移步檢視這裡:

http://source.android.com/devices/graphics/index.html

http://article.yeeyan.org/view/37503/304664

通常來說,幀率超過重新整理頻率僅僅是一種理想的狀況。在超過60fps的情況下,GPU所産生的幀資料會由于等待VSYNC的重新整理資訊而被Hold住,這樣能夠保持每次重新整理都有實際的新的資料能夠顯示。可是我們遇到很多其它的情況是幀率小于重新整理頻率。

Android 性能優化探究

在這樣的情況下,某些幀顯示的畫面内容就會與上一幀的畫面同樣。

糟糕的事情是,幀率從超過60fps突然掉到60fps以下,這樣就會發生LAG,JANK,HITCHING等卡頓掉幀的不順滑的情況。這也是使用者感受不好的原因所在。

3)Tool:Profile GPU Rendering

性能問題如此的麻煩,幸好我們能夠有工具來進行調試。打開手機裡面的開發人員選項,選擇Profile GPU Rendering,選中On screen as bars的選項。

Android 性能優化探究

選擇了這樣以後。我們能夠在手機畫面上看到豐富的GPU繪制圖形資訊,分别關于StatusBar。NavBar,激活的程式Activity區域的GPU Rending資訊。

Android 性能優化探究

随着界面的重新整理。界面上會滾動顯示垂直的柱狀圖來表示每幀畫面所須要渲染的時間。柱狀圖越高表示花費的渲染時間越長。

Android 性能優化探究

中間有一根綠色的橫線,代表16ms,我們須要確定每一幀花費的總時間都低于這條橫線,這樣才幹夠避免出現卡頓的問題。

Android 性能優化探究

每一條柱狀線都包括三部分,藍色代表測量繪制Display List的時間,紅色代表OpenGL渲染Display List所須要的時間,黃色代表CPU等待GPU處理的時間。

4)Why 60fps?

我們通常都會提到60fps與16ms,可是知道為何會是以程式是否達到60fps來作為App性能的衡量标準嗎?這是由于人眼與大腦之間的協作無法感覺超過60fps的畫面更新。

12fps大概相似手動高速翻動書籍的幀率,這明顯是能夠感覺到不夠順滑的。24fps使得人眼感覺的是連續線性的運動,這事實上是歸功于運動模糊的效果。24fps是電影膠圈通常使用的幀率。由于這個幀率已經足夠支撐大部分電影畫面須要表達的内容。同一時候能夠最大的降低費用支出。

可是低于30fps是無法順暢表現絢麗的畫面内容的,此時就須要用到60fps來達到想要的效果,當然超過60fps是沒有必要的。

開發app的性能目标就是保持60fps。這意味着每一幀你僅僅有16ms=1000/60的時間來處理全部的任務。

5)Android, UI and the GPU

了解Android是怎樣利用GPU進行畫面渲染有助于我們更好的了解性能問題。

那麼一個最實際的問題是:activity的畫面是怎樣繪制到螢幕上的?那些複雜的XML布局檔案又是怎樣能夠被識别并繪制出來的?

Android 性能優化探究

Resterization栅格化是繪制那些Button。Shape,Path,String,Bitmap等元件最基礎的操作。

它把那些元件拆分到不同的像素上進行顯示。

這是一個非常費時的操作,GPU的引入就是為了加快栅格化的操作。

CPU負責把UI元件計算成Polygons,Texture紋理。然後交給GPU進行栅格化渲染。

Android 性能優化探究

然而每次從CPU轉移到GPU是一件非常麻煩的事情,所幸的是OpenGL ES能夠把那些須要渲染的紋理Hold在GPU Memory裡面,在下次須要渲染的時候直接進行操作。是以假設你更新了GPU所hold住的紋理内容,那麼之前儲存的狀态就丢失了。

在Android裡面那些由主題所提供的資源,比如Bitmaps,Drawables都是一起打包到統一的Texture紋理其中。然後再傳遞到GPU裡面。這意味着每次你須要使用這些資源的時候。都是直接從紋理裡面進行擷取渲染的。當然随着UI元件的越來越豐富。有了很多其它演變的形态。

比如顯示圖檔的時候。須要先經過CPU的計算載入到記憶體中,然後傳遞給GPU進行渲染。文字的顯示更加複雜。須要先經過CPU換算成紋理,然後再交給GPU進行渲染。回到CPU繪制單個字元的時候,再又一次引用經過GPU渲染的内容。

動畫則是一個更加複雜的操作流程。

為了能夠使得App流暢。我們須要在每一幀16ms以内處理全然部的CPU與GPU計算,繪制,渲染等等操作。

6)Invalidations, Layouts, and Performance

順滑精妙的動畫是app設計裡面最重要的元素之中的一個,這些動畫能夠顯著提升使用者體驗。

以下會解說Android系統是怎樣處理UI元件的更新操作的。

通常來說,Android須要把XML布局檔案轉換成GPU能夠識别并繪制的對象。這個操作是在DisplayList的幫助下完畢的。

DisplayList持有全部将要交給GPU繪制到螢幕上的資料資訊。

在某個View第一次須要被渲染時。DisplayList會是以而被建立。當這個View要顯示到螢幕上時,我們會運作GPU的繪制指令來進行渲染。假設你在興許有運作相似移動這個View的位置等操作而須要再次渲染這個View時,我們就僅僅須要額外操作一次渲染指令就夠了。

然而假設你改動了View中的某些可見元件,那麼之前的DisplayList就無法繼續使用了,我們須要回頭又一次建立一個DisplayList而且又一次運作渲染指令并更新到螢幕上。

須要注意的是:不論什麼時候View中的繪制内容發生變化時,都會又一次運作建立DisplayList,渲染DisplayList,更新到螢幕上等一系列操作。這個流程的表現性能取決于你的View的複雜程度,View的狀态變化以及渲染管道的運作性能。舉個樣例,假設某個Button的大小須要增大到眼下的兩倍,在增大Button大小之前,須要通過父View又一次計算并擺放其它子View的位置。改動View的大小會觸發整個HierarcyView的又一次計算大小的操作。

假設是改動View的位置則會觸發HierarchView又一次計算其它View的位置。

假設布局非常複雜,這就會非常easy導緻嚴重的性能問題。我們須要盡量降低Overdraw。

Android 性能優化探究

我們能夠通過前面介紹的Monitor GPU Rendering來檢視渲染的表現性能怎樣,另外也能夠通過開發人員選項裡面的Show GPU view updates來檢視視圖更新的操作,最後我們還能夠通過HierarchyViewer這個工具來檢視布局,使得布局盡量扁平化,移除非必需的UI元件。這些操作能夠降低Measure。Layout的計算時間。

7)Overdraw, Cliprect, QuickReject

引起性能問題的一個非常重要的方面是由于過多複雜的繪制操作。我們能夠通過工具來檢測并修複标準UI元件的Overdraw問題。可是針對高度自己定義的UI元件則顯得有些力不從心。

有一個竅門是我們能夠通過運作幾個APIs方法來顯著提升繪制操作的性能。前面有提到過。非可見的UI元件進行繪制更新會導緻Overdraw。

比如Nav Drawer從前置可見的Activity滑出之後,假設還繼續繪制那些在Nav Drawer裡面不可見的UI元件,這就導緻了Overdraw。

為了解決這個問題,Android系統會通過避免繪制那些全然不可見的元件來盡量降低Overdraw。那些Nav Drawer裡面不可見的View就不會被運作浪費資源。

Android 性能優化探究

可是不幸的是。對于那些過于複雜的自己定義的View(重寫了onDraw方法),Android系統無法檢測詳細在onDraw裡面會運作什麼操作,系統無法監控并自己主動優化。也就無法避免Overdraw了。

可是我們能夠通過canvas.clipRect()來幫助系統識别那些可見的區域。這種方法能夠指定一塊矩形區域。僅僅有在這個區域内才會被繪制,其它的區域會被忽視。這個API能夠非常好的幫助那些有多組重疊元件的自己定義View來控制顯示的區域。

同一時候clipRect方法還能夠幫助節約CPU與GPU資源,在clipRect區域之外的繪制指令都不會被運作,那些部分内容在矩形區域内的元件,仍然會得到繪制。

Android 性能優化探究

除了clipRect方法之外,我們還能夠使用canvas.quickreject()來推斷是否沒和某個矩形相交,進而跳過那些非矩形區域内的繪制操作。做了那些優化之後,我們能夠通過上面介紹的Show GPU Overdraw來檢視效果。

8)Memory Churn and performance

盡管Android有自己主動管理記憶體的機制,可是對記憶體的不恰當使用仍然easy引起嚴重的性能問題。在同一幀裡面建立過多的對象是件須要特别引起注意的事情。

Android系統裡面有一個Generational Heap Memory的模型,系統會依據記憶體中不同的記憶體資料類型分别運作不同的GC操作。

比如,近期剛配置設定的對象會放在Young Generation區域,這個區域的對象通常都是會高速被建立而且非常快被銷毀回收的。同一時候這個區域的GC操作速度也是比Old Generation區域的GC操作速度更快的。

Android 性能優化探究

除了速度差異之外,運作GC操作的時候。全部線程的不論什麼操作都會須要暫停,等待GC操作完畢之後。其它操作才幹夠繼續運作。

Android 性能優化探究

通常來說,單個的GC并不會占用太多時間,可是大量不停的GC操作則會顯著占用幀間隔時間(16ms)。假設在幀間隔時間裡面做了過多的GC操作,那麼自然其它相似計算,渲染等操作的可用時間就變得少了。

導緻GC頻繁運作有兩個原因:

Memory Churn記憶體抖動。記憶體抖動是由于大量的對象被建立又在短時間内立即被釋放。

瞬間産生大量的對象會嚴重占用Young Generation的記憶體區域,當達到閥值。剩餘空間不夠的時候,也會觸發GC。即使每次配置設定的對象占用了非常少的記憶體,可是他們疊加在一起會添加Heap的壓力。進而觸發很多其它其它類型的GC。這個操作有可能會影響到幀率,并使得使用者感覺到性能問題。

Android 性能優化探究

解決上面的問題有簡潔直覺方法,假設你在Memory Monitor裡面檢視到短時間發生了多次記憶體的漲跌。這意味着非常有可能發生了記憶體抖動。

Android 性能優化探究

同一時候我們還能夠通過Allocation Tracker來檢視在短時間内,同一個棧中不斷進出的同樣對象。這是記憶體抖動的典型信号之中的一個。

當你大緻定位問題之後,接下去的問題修複也就顯得相對直接簡單了。

比如。你須要避免在for循環裡面配置設定對象占用記憶體,須要嘗試把對象的建立移到循環體之外,自己定義View中的onDraw方法也須要引起注意。每次螢幕發生繪制以及動畫運作過程中,onDraw方法都會被調用到,避免在onDraw方法裡面運作複雜的操作。避免建立對象。

對于那些無法避免須要建立對象的情況,我們能夠考慮對象池模型。通過對象池來解決頻繁建立與銷毀的問題,可是這裡須要注意結束使用之後。須要手動釋放對象池中的對象。

9)Garbage Collection in Android

JVM的回收機制給開發人員帶來非常大的優點,不用時刻處理對象的配置設定與回收,能夠更加專注于更加進階的代碼實作。相比起Java,C與C++等語言具備更高的運作效率,他們須要開發人員自己關注對象的配置設定與回收。可是在一個龐大的系統其中,還是免不了常常發生部分對象忘記回收的情況,這就是記憶體洩漏。

原始JVM中的GC機制在Android中得到了非常大程度上的優化。

Android裡面是一個三級Generation的記憶體模型。近期配置設定的對象會存放在Young Generation區域。當這個對象在這個區域停留的時間達到一定程度,它會被移動到Old Generation,最後到Permanent Generation區域。

Android 性能優化探究

每一個級别的記憶體區域都有固定的大小。此後不斷有新的對象被配置設定到此區域,當這些對象總的大小快達到這一級别記憶體區域的閥值時。會觸發GC的操作,以便騰出空間來存放其它新的對象。

Android 性能優化探究

前面提到過每次GC發生的時候。全部的線程都是暫停狀态的。GC所占用的時間和它是哪一個Generation也有關系,Young Generation的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。

運作時間的長短也和目前Generation中的對象數量有關,周遊查找20000個對象比起周遊50個對象自然是要慢非常多的。

盡管Google的project師在盡量縮短每次GC所花費的時間,可是特别注意GC引起的性能問題還是非常有必要。假設不小心在最小的for循環單元裡面運作了建立對象的操作,這将非常easy引起GC并導緻性能問題。

通過Memory Monitor我們能夠檢視到記憶體的占用情況,每一次瞬間的記憶體降低都是由于此時發生了GC操作,假設在短時間内發生大量的記憶體上漲與降低的事件,這說明非常有可能這裡有性能問題。

我們還能夠通過Heap and Allocation Tracker工具來檢視此時記憶體中配置設定的究竟有哪些對象。

10)Performance Cost of Memory Leaks

盡管Java有自己主動回收的機制,可是這不意味着Java中不存在記憶體洩漏的問題,而記憶體洩漏會非常easy導緻嚴重的性能問題。

記憶體洩漏指的是那些程式不再使用的對象無法被GC識别,這樣就導緻這個對象一直留在記憶體其中,占用了寶貴的記憶體空間。顯然。這還使得每級Generation的記憶體區域可用空間變小,GC就會更easy被觸發。進而引起性能問題。

尋找記憶體洩漏并修複這個漏洞是件非常棘手的事情,你須要對運作的代碼非常熟悉。清晰的知道在特定環境下是怎樣運作的,然後細緻排查。比如。你想知道程式中的某個activity退出的時候。它之前所占用的記憶體是否有完整的釋放幹淨了?首先你須要在activity處于前台的時候使用Heap Tool擷取一份目前狀态的記憶體快照。然後你須要建立一個差點兒不這麼占用記憶體的空白activity用來給前一個Activity進行跳轉,其次在跳轉到這個空白的activity的時候主動調用System.gc()方法來確定觸發一個GC操作。

最後,假設前面這個activity的記憶體都有全部正确釋放。那麼在空白activity被啟動之後的記憶體快照中應該不會有前面那個activity中的不論什麼對象了。

Android 性能優化探究

假設你發如今空白activity的記憶體快照中有一些可疑的沒有被釋放的對象存在,那麼接下去就應該使用Alocation Track Tool來細緻查找詳細的可疑對象。我們能夠從空白activity開始監聽,啟動到觀察activity。然後再回到空白activity結束監聽。這樣操作以後。我們能夠細緻觀察那些對象。找出記憶體洩漏的真兇。

Android 性能優化探究

11)Memory Performance

通常來說,Android對GC做了大量的優化操作。盡管運作GC操作的時候會暫停其它任務,可是大多數情況下。GC操作還是相對非常安靜而且高效的。可是假設我們對記憶體的使用不恰當,導緻GC頻繁運作,這樣就會引起不小的性能問題。

為了尋找記憶體的性能問題。Android Studio提供了工具來幫助開發人員。

Memory Monitor:檢視整個app所占用的記憶體,以及發生GC的時刻,短時間内發生大量的GC操作是一個危急的信号。

Allocation Tracker:使用此工具來追蹤記憶體的配置設定。前面有提到過。

Heap Tool:檢視目前記憶體快照。便于對照分析哪些對象有可能是洩漏了的,請參考前面的Case。

12)Tool - Memory Monitor

Android Studio中的Memory Monitor能夠非常好的幫助我們檢視程式的記憶體使用情況。

Android 性能優化探究
Android 性能優化探究
Android 性能優化探究

13)Battery Performance

電量事實上是眼下手持裝置最寶貴的資源之中的一個,大多數裝置都須要不斷的充電來維持繼續使用。不幸的是,對于開發人員來說,電量優化是他們最後才會考慮的的事情。可是能夠确定的是,千萬不能讓你的應用成為消耗電量的大戶。

Purdue University研究了最受歡迎的一些應用的電量消耗。平均僅僅有30%左右的電量是被程式最核心的方法比如繪制圖檔,擺放布局等等所使用掉的,剩下的70%左右的電量是被上報資料。檢查位置資訊,定時檢索背景廣告資訊所使用掉的。怎樣平衡這兩者的電量消耗,就顯得非常重要了。

有以下一些措施能夠顯著降低電量的消耗:

我們應該盡量降低喚醒螢幕的次數與持續的時間。使用WakeLock來處理喚醒的問題,能夠正确運作喚醒操作并依據設定及時關閉操作進入睡眠狀态。

某些非必須立即運作的操作,比如上傳歌曲,圖檔處理等。能夠等到裝置處于充電狀态或者電量充足的時候才進行。

觸發網絡請求的操作,每次都會保持無線信号持續一段時間,我們能夠把零散的網絡請求打包進行一次操作,避免過多的無線信号引起的電量消耗。關于網絡請求引起無線信号的電量消耗,還能夠參考這裡:

http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html

我們能夠通過手機設定選項找到相應App的電量消耗統計資料。我們還能夠通過Battery Historian Tool來檢視詳細的電量消耗。

Android 性能優化探究

假設發現我們的App有電量消耗過多的問題,我們能夠使用JobScheduler API來對一些任務進行定時處理。比如我們能夠把那些任務重的操作等到手機處于充電狀态,或者是連接配接到WiFi的時候來處理。 關于JobScheduler的很多其它知識能夠參考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html

14)Understanding Battery Drain on Android

電量消耗的計算與統計是一件麻煩而且沖突的事情,記錄電量消耗本身也是一個費電量的事情。唯一可行的方案是使用第三方監測電量的裝置,這樣才幹夠擷取到真實的電量消耗。

當裝置處于待機狀态時消耗的電量是極少的,以N5為例。打開飛行模式,能夠待機接近1個月。

可是點亮螢幕,硬體各個子產品就須要開始工作。這會須要消耗非常多電量。

使用WakeLock或者JobScheduler喚醒裝置處理定時的任務之後,一定要及時讓裝置回到初始狀态。每次喚醒無線信号進行資料傳遞。都會消耗非常多電量,它比WiFi等操作更加的耗電,詳情請關注

Android 性能優化探究

修複電量的消耗是另外一個非常大的課題,這裡就不展開繼續了。

15)Battery Drain and WakeLocks

高效的保留很多其它的電量與不斷促使使用者使用你的App會消耗電量,這是沖突的選擇題。隻是我們能夠使用一些更好的辦法來平衡兩者。

假設你的手機裡面裝了大量的社交類應用,即使手機處于待機狀态,也會常常被這些應用喚醒用來檢查同步新的資料資訊。Android會不斷關閉各種硬體來延長手機的待機時間。首先螢幕會逐漸變暗直至關閉,然後CPU進入睡眠。這一切操作都是為了節約寶貴的電量資源。可是即使在這樣的睡眠狀态下,大多數應用還是會嘗試進行工作。他們将不斷的喚醒手機。一個最簡單的喚醒手機的方法是使用PowerManager.WakeLock的API來保持CPU工作并防止螢幕變暗關閉。這使得手機能夠被喚醒。運作工作。然後回到睡眠狀态。知道怎樣擷取WakeLock是簡單的,可是及時釋放WakeLock也是非常重要的,不恰當的使用WakeLock會導緻嚴重錯誤。比如網絡請求的資料傳回時間不确定,導緻本來僅僅須要10s的事情一直等待了1個小時,這樣會使得電量白白浪費了。這也是為何使用帶逾時參數的wakelock.acquice()方法是非常關鍵的。

可是僅僅設定逾時并不足夠解決這個問題,比如設定多長的逾時比較合适?什麼時候進行重試等等?

解決上面的問題,正确的方式可能是使用非精準定時器。通常情況下,我們會設定一個時間進行某個操作。可是動态改動這個時間或許會更好。比如。假設有另外一個程式須要比你設定的時間晚5分鐘喚醒,最好能夠等到那個時候。兩個任務捆綁一起同一時候進行,這就是非精确定時器的核心工作原理。

我們能夠定制計劃的任務,可是系統假設檢測到一個更好的時間。它能夠推遲你的任務。以節省電量消耗。

Android 性能優化探究

這正是JobScheduler API所做的事情。它會依據目前的情況與任務,組合出理想的喚醒時間,比如等到正在充電或者連接配接到WiFi的時候。或者集中任務一起運作。

我們能夠通過這個API實作非常多免費的排程算法。

從Android 5.0開始公布了Battery History Tool,它能夠檢視程式被喚醒的頻率。又誰喚醒的。持續了多長的時間,這些資訊都能夠擷取到。

請關注程式的電量消耗,使用者能夠通過手機的設定選項觀察到那些耗電量大戶。并可能決定解除安裝他們。是以盡量降低程式的電量消耗是非常有必要的。

Android性能優化典範(二)

Android 性能優化探究

Google前幾天剛公布了Android性能優化典範第2季的課程,一共20個短視訊,包括的内容大緻有:電量優化,網絡優化。Wear上怎樣做優化。使用對象池來提高效率,LRU Cache,Bitmap的縮放,緩存,重用,PNG壓縮。自己定義View的性能,提升設定alpha之後View的渲染性能,以及Lint。StictMode等等工具的使用技巧。 以下是對這些課程的總結摘要,認知有限,了解偏差的地方請多多不吝賜教!

1)Battery Drain and Networking

對于手機程式。網絡操作相對來說是比較耗電的行為。優化網絡操作能夠顯著節約電量的消耗。

在性能優化第1季裡面有提到過。手機硬體的各個子產品的耗電量是不一樣的。其中移動蜂窩子產品對電量消耗是比較大的,另外蜂窩子產品在不同工作強度下,對電量的消耗也是有差異的。當程式想要運作某個網絡請求之前,須要先喚醒裝置。然後發送資料請求。之後等待傳回資料,最後才慢慢進入休眠狀态。

這個流程例如以下圖所看到的:

Android 性能優化探究

在上面那個流程中,蜂窩子產品的電量消耗差異例如以下圖所看到的:

Android 性能優化探究

從圖示中能夠看到,激活瞬間,發送資料的瞬間,接收資料的瞬間都有非常大的電量消耗,是以。我們應該從怎樣傳遞網絡資料以及何時發起網絡請求這兩個方面來着手優化。

1.1)何時發起網絡請求

首先我們須要區分哪些網絡請求是須要及時傳回結果的,哪些是能夠延遲運作的。

比如。使用者主動下拉重新整理清單,這樣的行為須要立即觸發網絡請求,并等待資料傳回。可是對于上傳使用者操作的資料,同步程式設定等等行為則屬于能夠延遲的行為。我們能夠通過Battery Historian這個工具來檢視關于移動蜂窩子產品的電量消耗(關于這部分的細節,請點選Android性能優化之電量篇)。在Mobile Radio那一行會顯示蜂窩子產品的電量消耗情況,紅色的部分代表子產品正在工作,中間的間隔部分代表子產品正在休眠狀态,假設看到有一段區間,紅色與間隔頻繁的出現。那就說明這裡有能夠優化的行為。例如以下圖所看到的:

Android 性能優化探究

對于上面能夠優化的部分。我們能夠有針對性的把請求行為捆綁起來。延遲到某個時刻統一發起請求。例如以下圖所看到的:

Android 性能優化探究

經過上面的優化之後。我們再回頭使用Battery Historian導出電量消耗圖,能夠看到喚醒狀态與休眠狀态是連續大塊間隔的。這樣的話,總體電量的消耗就會變得更少。

Android 性能優化探究

當然,我們甚至能夠把請求的任務延遲到手機網絡切換到WiFi。手機處于充電狀态下再運作。

在前面的描寫叙述過程中,我們會遇到的一個難題是怎樣把網絡請求延遲。并批量進行運作。還好。Android提供了JobScheduler來幫助我們達成這個目标。

1.2)怎樣傳遞網絡資料

關于這部分主要會涉及到Prefetch(預取)與Compressed(壓縮)這兩個技術。對于Prefetch的使用。我們須要預先推斷使用者在此次操作之後,興許零散的請求是否非常有可能會立即被觸發,能夠把後面5分鐘有可能會使用到的零散請求都一次集中運作完畢。對于Compressed的使用,在上傳與下載下傳資料之前。使用CPU對資料進行壓縮與解壓,能夠非常大程度上降低網絡傳輸的時間。

想要知道我們的應用程式中網絡請求發生的時間,每次請求的資料量等等資訊。能夠通過Android Studio中的Networking Traffic Tool來檢視詳細的資料,例如以下圖所看到的:

Android 性能優化探究

2)Wear & Sensors

在Android Wear上會大量的使用Sensors來實作某些特殊功能,怎樣在盡量節約電量的前提下利用好Sensor會是我們須要特别注意的問題。以下會介紹一些在Android Wear上的最佳實踐典範。

盡量降低重新整理請求,比如我們能夠在不須要某些資料的時候盡快登出監聽,減小重新整理頻率,對Sensor的資料做批量處理等等。

那麼怎樣做到這些優化呢?

首先我們須要盡量使用Android平台提供的既有運動資料,而不是自己去實作監聽採集資料。由于大多數Android Watch自身記錄Sensor資料的行為是有經過做電量優化的。

其次在Activity不須要監聽某些Sensor資料的時候須要盡快釋放監聽注冊。

還有我們須要盡量控制更新的頻率,僅僅在須要重新整理顯示資料的時候才觸發擷取最新資料的操作。

另外我們能夠針對Sensor的資料做批量處理,待資料累積一定次數或者某個程度的時候才更新到UI上。

最後當Watch與Phone連接配接起來的時候,能夠把某些複雜操作的事情交給Phone來運作,Watch僅僅須要等待傳回的結果。

更對關于Sensors的知識,能夠點選這裡

3)Smooth Android Wear Animation

Android Material Design風格的應用採用了大量的動畫來進行UI切換,優化動畫的性能不僅能夠提升使用者體驗還能夠降低電量的消耗,以下會介紹一些簡單易行的方法。

在Android裡面一個相對操作比較繁重的事情是對Bitmap進行旋轉,縮放,裁剪等等。比如在一個圓形的鐘表圖上。我們把時鐘的指針摳出來當做單獨的圖檔進行旋轉會比旋轉一張完整的圓形圖的所形成的幀率要高56%。

Android 性能優化探究

另外盡量降低每次重繪的元素能夠極大的提升性能,假如某個鐘表界面上有非常多須要顯示的複雜元件,我們能夠把這些元件做拆分處理,比如把背景圖檔單獨拎出來設定為一個獨立的View。通過setLayerType()方法使得這個View強制用Hardware來進行渲染。至于界面上哪些元素須要做拆分。他們各自的更新頻率是多少,須要有針對性的單獨讨論。

怎樣使用Systrace等工具來檢視某些View的渲染性能,在前面的章節裡面有提到過,感興趣的能夠點選這裡

對于大多數應用中的動畫,我們會使用PropertyAnimation或者ViewAnimation來操作實作,Android系統會自己主動對這些Animation做一定的優化處理,在Android上面學習到的大多數性能優化的知識同樣也适用于Android Wear。

想要擷取很多其它關于Android Wear中動畫效果的優化。請點選WatchFace這個範例。

4)Android Wear Data Batching

在Android Training裡面有關于Wear上面怎樣利用Wearable API與Phone進行溝通協作的課程(詳情請點選這裡)。由于Phone的CPU與電量都比Wear要強大,另外Phone還能夠直接接入網絡,而Wear要接入網絡則相對更加困難,是以我們在開發Wear應用的時候須要盡量做到把複雜的操作交給Phone來運作。

比如我們能夠讓Phone來擷取天氣資訊,然後把資料傳回Wear進行顯示。更進一步,在之前的性能優化課程裡面我們有學習過怎樣使用JobScheduler來延遲批量處理任務,假設Phone收到來自Wear的其中一個任務是每隔5分鐘檢查一次天氣情況。那麼Phone使用JobScheduler運作檢查天氣任務之後。先推斷這次傳回的結果和之前是否有差異,僅僅當天氣發生變化的時候,才有必要把結果通知到Wear,或者僅僅把變化的某一項資料通知給Wear。這樣能夠更大程度上降低Wear的電量消耗。

以下我們總結一下怎樣優化Wear的性能與電量:

  • 僅僅在真正須要重新整理界面的時候才送出請求
  • 盡量把計算複雜操作的任務交給Phone來處理
  • Phone僅僅在資料發生變化的時候才通知到Wear
  • 把零碎的資料請求捆綁一起再進行操作

5)Object Pools

在程式裡面常常會遇到的一個問題是短時間内建立大量的對象,導緻記憶體緊張,進而觸發GC導緻性能問題。對于這個問題。我們能夠使用對象池技術來解決它。通常對象池中的對象可能是bitmaps,views,paints等等。關于對象池的操作原理,不展開述說了。請看以下的圖示:

Android 性能優化探究

使用對象池技術有非常多優點,它能夠避免記憶體抖動。提升性能。可是在使用的時候有一些内容是須要特别注意的。通常情況下,初始化的對象池裡面都是空白的,當使用某個對象的時候先去對象池查詢是否存在,假設不存在則建立這個對象然後添加對象池,可是我們也能夠在程式剛啟動的時候就事先為對象池填充一些即将要使用到的資料,這樣能夠在須要使用到這些對象的時候提供更快的首次載入速度,這樣的行為就叫做預配置設定。使用對象池也有不好的一面,程式猿須要手動管理這些對象的配置設定與釋放,是以我們須要謹慎地使用這項技術。避免發生對象的記憶體洩漏。

為了確定全部的對象能夠正确被釋放,我們須要保證添加對象池的對象和其它外部對象沒有互相引用的關系。

6)To Index or Iterate?

周遊容器是程式設計裡面一個常常遇到的場景。在Java語言中。使用Iterate是一個比較常見的方法。

可是在Android開發團隊中,大家卻盡量避免使用Iterator來運作周遊操作。

以下我們看下在Android上可能用到的三種不同的周遊方法:

Android 性能優化探究
Android 性能優化探究
Android 性能優化探究

使用上面三種方式在同一台手機上,使用同樣的資料集做測試,他們的表現性能例如以下所看到的:

Android 性能優化探究

從上面能夠看到for index的方式有更好的效率。可是由于不同平台編譯器優化各有差異,我們不妨針對實際的方法做一下簡單的測量比較好,拿到資料之後,再選擇效率最高的那個方式。

7)The Magic of LRU Cache

這小節我們要讨論的是緩存算法。在Android上面最常常使用的一個緩存算法是LRU(Least Recently Use),關于LRU算法,不展開述說。用以下一張圖示範下含義:

Android 性能優化探究

LRU Cache的基礎建構使用方法例如以下:

Android 性能優化探究

為了給LRU Cache設定一個比較合理的緩存大小值,我們一般是用以下的方法來做界定的:

Android 性能優化探究

使用LRU Cache時為了能夠讓Cache知道每一個添加的Item的詳細大小,我們須要Override以下的方法:

Android 性能優化探究

使用LRU Cache能夠顯著提升應用的性能,可是也須要注意LRU Cache中被淘汰對象的回收。否者會引起嚴重的記憶體洩露。

8)Using LINT for Performance Tips

Lint是Android提供的一個靜态掃描應用源代碼并找出其中的潛在問題的一個強大的工具。

Android 性能優化探究

比如。假設我們在onDraw方法裡面運作了new對象的操作。Lint就會提示我們這裡有性能問題,并提出相應的建議方案。Lint已經內建到Android Studio中了。我們能夠手動去觸發這個工具,點選工具欄的Analysis -> Inspect Code,觸發之後。Lint會開始工作,并把結果輸出究竟部的工具欄。我們能夠逐個檢視原因并依據訓示做相應的優化改動。

Lint的功能非常強大,他能夠掃描各種問題。

當然我們能夠通過Android Studio設定找到Lint,對Lint做一些定制化掃描的設定,能夠選擇忽略掉那些不想Lint去掃描的選項,我們還能夠針對部分掃描内容改動它的提示優先級。

建議把與記憶體有關的選項中的嚴重程度标記為紅色的Error,對于Layout的性能問題标記為黃色Warning。

9)Hidden Cost of Transparency

這小節會介紹怎樣降低透明區域對性能的影響。通常來說。對于不透明的View,顯示它僅僅須要渲染一次就可以。可是假設這個View設定了alpha值,會至少須要渲染兩次。

原因是包括alpha的view須要事先知道混合View的下一層元素是什麼,然後再結合上層的View進行Blend混色處理。

在某些情況下。一個包括alpha的View有可能會觸發改View在HierarchyView上的父View都被額外重繪一次。以下我們看一個樣例。下圖示範的ListView中的圖檔與二級标題都有設定透明度。

Android 性能優化探究

大多數情況下,螢幕上的元素都是由後向前進行渲染的。在上面的圖示中,會先渲染背景圖(藍,綠。紅),然後渲染人物頭像圖。

假設後渲染的元素有設定alpha值,那麼這個元素就會和螢幕上已經渲染好的元素做blend處理。非常多時候。我們會給整個View設定alpha的來達到fading的動畫效果。假設我們圖示中的ListView做alpha逐漸減小的處理,我們能夠看到ListView上的TextView等等元件會逐漸融合到背景色上。可是在這個過程中,我們無法觀察到它事實上已經觸發了額外的繪制任務。我們的目标是讓整個View逐漸透明,可是期間ListView在不停的做Blending的操作,這樣會導緻不少性能問題。

怎樣渲染才幹夠得到我們想要的效果呢?我們能夠先依照通常的方式把View上的元素依照從後到前的方式繪制出來,可是不直接顯示到螢幕上,而是使用GPU預處理之後,再又GPU渲染到螢幕上,GPU能夠對界面上的原始資料直接做旋轉,設定透明度等等操作。使用GPU進行渲染,盡管第一次操作相比起直接繪制到螢幕上更加耗時。可是一旦原始紋理資料生成之後,接下去的操作就比較省時省力。

Android 性能優化探究

怎樣才幹夠讓GPU來渲染某個View呢?我們能夠通過setLayerType的方法來指定View應該怎樣進行渲染,從SDK 16開始,我們還能夠使用ViewPropertyAnimator.alpha().withLayer()來指定。例如以下圖所看到的:

Android 性能優化探究

另外一個樣例是包括陰影區域的View,這樣的類型的View并不會出現我們前面提到的問題,由于他們并不存在層疊的關系。

Android 性能優化探究

為了能夠讓渲染器知道這樣的情況。避免為這樣的View占用額外的GPU記憶體空間,我們能夠做以下的設定。

Android 性能優化探究

通過上面的設定以後,性能能夠得到顯著的提升,例如以下圖所看到的:

Android 性能優化探究

10)Avoiding Allocations in onDraw()

我們都知道應該避免在onDraw()方法裡面運作導緻記憶體配置設定的操作,以下解說下為何須要這樣做。

首先onDraw()方法是運作在UI線程的,在UI線程盡量避免做不論什麼可能影響到性能的操作。盡管配置設定記憶體的操作并不須要花費太多系統資源。可是這并不意味着是免費無代價的。裝置有一定的重新整理頻率,導緻View的onDraw方法會被頻繁的調用,假設onDraw方法效率低下,在頻繁重新整理累積的效應下,效率低的問題會被擴大,然後會對性能有嚴重的影響。

Android 性能優化探究

假設在onDraw裡面運作記憶體配置設定的操作,會easy導緻記憶體抖動,GC頻繁被觸發。盡管GC後來被改進為運作在另外一個背景線程(GC操作在2.3曾經是同步的。之後是并發)。可是頻繁的GC的操作還是會影響到CPU,影響到電量的消耗。

那麼簡單解決頻繁配置設定記憶體的方法就是把配置設定操作移動到onDraw()方法外面,通常情況下。我們會把onDraw()裡面new Paint的操作移動到外面。如以下所看到的:

Android 性能優化探究

11)Tool: Strict Mode

UI線程被堵塞超過5秒。就會出現ANR,這太糟糕了。

防止程式出現ANR是非常重要的事情,那麼怎樣找出程式裡面潛在的坑。預防ANR呢?非常多大部分情況下運作非常快的方法,可是他們有可能存在巨大的隐患,這些隐患的爆發就非常easy導緻ANR。

Android提供了一個叫做Strict Mode的工具,我們能夠通過手機設定裡面的開發人員選項,打開Strict Mode選項,假設程式存在潛在的隐患,螢幕就會閃現紅色。我們也能夠通過StrictMode API在代碼層面做細化的跟蹤,能夠設定StrictMode監聽那些潛在問題。出現故障時怎樣提醒開發人員,能夠對螢幕閃紅色,也能夠輸出錯誤日志。以下是官方的代碼示範樣例:

public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
}           

12)Custom Views and Performance

Android系統有提供超過70多種标準的View,比如TextView。ImageView,Button等等。在某些時候,這些标準的View無法滿足我們的須要。那麼就須要我們自己來實作一個View,這節會介紹怎樣優化自己定義View的性能。

通常來說,針對自己定義View,我們可能犯以下三個錯誤:

  • Useless calls to onDraw():我們知道調用View.invalidate()會觸發View的重繪,有兩個原則須要遵守,第1個是僅僅在View的内容發生改變的時候才去觸發invalidate方法,第2個是盡量使用ClipRect等方法來提高繪制的性能。
  • Useless pixels:降低繪制時不必要的繪制元素。對于那些不可見的元素。我們須要盡量避免重繪。
  • Wasted CPU cycles:對于不在螢幕上的元素,能夠使用Canvas.quickReject把他們給剔除,避免浪費CPU資源。另外盡量使用GPU來進行UI的渲染,這樣能夠極大的提高程式的總體表現性能。

    最後請時刻牢記。盡量提高View的繪制性能,這樣才幹保證界面的重新整理幀率盡量的高。很多其它關于這部分的内容,能夠看這裡

13)Batching Background Work Until Later

優化性能時大多數時候讨論的都是怎樣降低不必要的操作,可是選擇何時去運作某些操作同樣也非常重要。

在第1季以及上一期的性能優化之電量篇裡面,我們有提到過移動蜂窩子產品的電量消耗模型。

為了避免我們的應用程式過多的頻繁消耗電量。我們須要學習怎樣把背景任務打包批量,并選擇一個合适的時機進行觸發運作。

下圖是每一個應用程式各自運作背景任務導緻的電量消耗示意圖:

Android 性能優化探究

由于像上面那樣做會導緻浪費非常多電量。我們須要做的是把部分應用的任務延遲處理,等到一定時機,這些任務一并進行處理。

結果如以下的示意圖:

Android 性能優化探究

運作延遲任務,通常有以下三種方式:

1)AlarmManager

使用AlarmManager設定定時任務,能夠選擇精确的間隔時間,也能夠選擇非精确時間作為參數。

除非程式有非常強烈的須要使用精确的定時喚醒,否者一定要避免使用他,我們應該盡量使用非精确的方式。

2)SyncAdapter

我們能夠使用SyncAdapter為應用添加設定賬戶,這樣在手機設定的賬戶清單裡面能夠找到我們的應用。

這樣的方式功能很多其它,可是實作起來比較複雜。

我們能夠從這裡看到官方的教育訓練課程:http://developer.android.com/training/sync-adapters/index.html

3)JobSchedulor

這是最簡單高效的方法,我們能夠設定任務延遲的間隔,運作條件,還能夠添加重試機制。

14)Smaller Pixel Formats

常見的png,jpeg,webp等格式的圖檔在設定到UI上之前須要經過解碼的過程,而解壓時能夠選擇不同的解碼率。不同的解碼率對記憶體的占用是有非常大差別的。在不影響到畫質的前提下盡量降低記憶體的占用,這能夠顯著提升應用程式的性能。

Android的Heap空間是不會自己主動做相容壓縮的,意思就是假設Heap空間中的圖檔被收回之後。這塊區域并不會和其它已經回收過的區域做又一次排序合并處理。那麼當一個更大的圖檔須要放到heap之前。非常可能找不到那麼大的連續空暇區域。那麼就會觸發GC,使得heap騰出一塊足以放下這張圖檔的空暇區域。假設無法騰出。就會發生OOM。

例如以下圖所看到的:

Android 性能優化探究

是以為了避免載入一張超大的圖檔。須要盡量降低這張圖檔所占用的記憶體大小,Android為圖檔提供了4種解碼格式。他們分别占用的記憶體大小例如以下圖所看到的:

Android 性能優化探究

随着解碼占用記憶體大小的降低,清晰度也會有損失。我們須要針對不同的應用場景做不同的處理,大圖和小圖能夠採用不同的解碼率。在Android裡面能夠通過以下的代碼來設定解碼率:

Android 性能優化探究

15)Smaller PNG Files

盡量降低PNG圖檔的大小是Android裡面非常重要的一條規範。相比起JPEG,PNG能夠提供更加清晰無損的圖檔,可是PNG格式的圖檔會更大,占用很多其它的磁盤空間。究竟是使用PNG還是JPEG。須要設計師細緻衡量。對于那些使用JPEG就能夠達到視覺效果的。能夠考慮採用JPEG就可以。我們能夠通過Google搜尋到非常多關于PNG壓縮的工具,例如以下圖所看到的:

Android 性能優化探究

這裡要介紹一種新的圖檔格式:Webp,它是由Google推出的一種既保留png格式的優點,又能夠降低圖檔大小的一種新型圖檔格式。關于Webp的很多其它細節,請點選這裡

16)Pre-scaling Bitmaps

對bitmap做縮放,這也是Android裡面最遇到的問題。對bitmap做縮放的意義非常明顯。提示顯示性能,避免配置設定不必要的記憶體。

Android提供了現成的bitmap縮放的API,叫做createScaledBitmap(),使用這種方法能夠擷取到一張經過縮放的圖檔。

Android 性能優化探究

上面的方法能夠高速的得到一張經過縮放的圖檔。可是這種方法能夠運作的前提是,原圖檔須要事先載入到記憶體中,假設原圖檔過大,非常可能導緻OOM。以下介紹其它幾種縮放圖檔的方式。

inSampleSize能夠等比的縮放顯示圖檔。同一時候還避免了須要先把原圖載入進記憶體的缺點。我們會使用相似像以下一樣的方法來縮放bitmap:

Android 性能優化探究
Android 性能優化探究

另外,我們還能夠使用inScaled,inDensity。inTargetDensity的屬性來對解碼圖檔做處理,源代碼例如以下圖所看到的:

Android 性能優化探究

另一個常常使用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖檔,能夠事先擷取到圖檔的大小而不至于占用什麼記憶體。例如以下圖所看到的:

Android 性能優化探究

17)Re-using Bitmaps

我們知道bitmap會占用大量的記憶體空間,這節會解說什麼是inBitmap屬性,怎樣利用這個屬性來提升bitmap的循環效率。前面我們介紹過使用對象池的技術來解決對象頻繁建立再回收的效率問題,使用這樣的方法,bitmap占用的記憶體空間會差點兒相同是恒定的數值,每次新建立出來的bitmap都會須要占用一塊單獨的記憶體區域。例如以下圖所看到的:

Android 性能優化探究

為了解決上圖所看到的的效率問題,Android在解碼圖檔的時候引進了inBitmap屬性,使用這個屬性能夠得到下圖所看到的的效果:

Android 性能優化探究

使用inBitmap屬性能夠告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所占領的pixel data記憶體區域,而不是去問記憶體又一次申請一塊區域來存放bitmap。利用這樣的特性。即使是上千張的圖檔,也僅僅會僅僅僅僅須要占用螢幕所能夠顯示的圖檔數量的記憶體大小。以下是怎樣使用inBitmap的代碼示範樣例:

Android 性能優化探究

使用inBitmap須要注意幾個限制條件:

在SDK 11 -> 18之間,重用的bitmap大小必須是一緻的。比如給inBitmap指派的圖檔大小為100-100,那麼新申請的bitmap必須也為100-100才幹夠被重用。從SDK 19開始,新申請的bitmap大小必須小于或者等于已經指派過的bitmap大小。

新申請的bitmap與舊的bitmap必須有同樣的解碼格式,比如大家都是8888的,假設前面的bitmap是8888。那麼就不能支援4444與565格式的bitmap了。

我們能夠建立一個包括多種典型可重用bitmap的對象池,這樣興許的bitmap建立都能夠找到合适的“模闆”去進行重用。例如以下圖所看到的:

Android 性能優化探究

Google介紹了一個開源的載入bitmap的庫:Glide,這裡面包括了各種對bitmap的優化技巧。

18)The Performance Lifecycle

大多數開發人員在沒有發現嚴重性能問題之前是不會特别花精力去關注性能優化的。通常大家關注的都是功能是否實作。當性能問題真的出現的時候,請不要慌亂。

我們通常採用以下三個步驟來解決性能問題。

Gather:收集資料

我們能夠通過Android SDK裡面提供的諸多工具來收集CPU,GPU。記憶體。電量等等性能資料,

Insight:分析資料

通過上面的步驟,我們擷取到了大量的資料,下一步就是分析這些資料。工具幫我們生成了非常多可讀性強的表格。我們須要事先了解怎樣檢視表格的資料。每一項代表的含義。這樣才幹夠高速定位問題。假設分析資料之後還是沒有找到問題。那麼就僅僅能不停的又一次收集資料,再進行分析,如此循環。

Action:解決這個問題

定位到問題之後,我們須要採取行動來解決這個問題。

解決這個問題之前一定要先有個計劃,評估這個解決方式是否可行。能否夠及時的解決這個問題。

19)Tools not Rules

盡管前面介紹了非常多調試的方法。處理技巧,規範建議等等。可是這并不意味着全部的情況都适用,我們還是須要依據當時的情景做特定靈活的處理。

20)Memory Profiling 101

環繞Android生态系統。不僅僅有Phone,還有Wear,TV,Auto等等。對這些不同形态的程式進行性能優化,都離不開記憶體調試這個步驟。

這節中介紹的内容大部分和Android性能優化典範與Android性能優化之記憶體篇重合,不展開了。

Android性能優化之記憶體篇

Android 性能優化探究

Google近期在Udacity上公布了Android性能優化的線上課程,分别從渲染,運算與記憶體,電量幾個方面介紹了怎樣去優化性能,這些課程是Google之前在Youtube上公布的Android性能優化典範專題課程的細化與補充。

以下是記憶體篇章的學習筆記,部分内容與前面的性能優化典範有重合,歡迎大家一起學習交流!

1)Memory, GC, and Performance

衆所周知,與C/C++須要通過手動編碼來申請以及釋放記憶體有所不同。Java擁有GC的機制。Android系統裡面有一個Generational Heap Memory的模型。系統會依據記憶體中不同的記憶體資料類型分别運作不同的GC操作。

Android 性能優化探究

除了速度差異之外,運作GC操作的時候,全部線程的不論什麼操作都會須要暫停。等待GC操作完畢之後,其它操作才幹夠繼續運作。

Android 性能優化探究

通常來說。單個的GC并不會占用太多時間,可是大量不停的GC操作則會顯著占用幀間隔時間(16ms)。假設在幀間隔時間裡面做了過多的GC操作,那麼自然其它相似計算,渲染等操作的可用時間就變得少了。

2)Memory Monitor Walkthrough

Android 性能優化探究
Android 性能優化探究
Android 性能優化探究

3)Memory Leaks

記憶體洩漏表示的是不再用到的對象由于被錯誤引用而無法進行回收。

Android 性能優化探究

發生記憶體洩漏會導緻Memory Generation中的剩餘可用Heap Size越來越小。這樣會導緻頻繁觸發GC。更進一步引起性能問題。

舉例記憶體洩漏,以下init()方法來自某個自己定義View:

private void init() {
ListenerCollector collector = new ListenerCollector();
collector.setListener(this, mListener);
}           

上面的樣例easy存在記憶體洩漏。假設activity由于裝置翻轉而又一次建立,自己定義的View會自己主動又一次把新建立出來的mListener給綁定到ListenerCollector中,可是當activity被銷毀的時候,mListener卻無法被回收了。

4)Heap Viewer Walkthrough

下圖示範了Android Tools裡面的Heap Viewer的功能。我們能夠看到目前程序中的Heap Size的情況,分别有哪些類型的資料。占比是多少。

Android 性能優化探究

5)Understanding Memory Churn

Memory Churn記憶體抖動,記憶體抖動是由于在短時間内大量的對象被建立又立即被釋放。瞬間産生大量的對象會嚴重占用Young Generation的記憶體區域,當達到閥值,剩餘空間不夠的時候,會觸發GC進而導緻剛産生的對象又非常快被回收。

即使每次配置設定的對象占用了非常少的記憶體,可是他們疊加在一起會添加Heap的壓力。進而觸發很多其它其它類型的GC。

這個操作有可能會影響到幀率,并使得使用者感覺到性能問題。

Android 性能優化探究

解決上面的問題有簡潔直覺方法,假設你在Memory Monitor裡面檢視到短時間發生了多次記憶體的漲跌,這意味着非常有可能發生了記憶體抖動。

Android 性能優化探究

同一時候我們還能夠通過Allocation Tracker來檢視在短時間内,同一個棧中不斷進出的同樣對象。

這是記憶體抖動的典型信号之中的一個。

當你大緻定位問題之後,接下去的問題修複也就顯得相對直接簡單了。比如,你須要避免在for循環裡面配置設定對象占用記憶體,須要嘗試把對象的建立移到循環體之外,自己定義View中的onDraw方法也須要引起注意。每次螢幕發生繪制以及動畫運作過程中,onDraw方法都會被調用到,避免在onDraw方法裡面運作複雜的操作。避免建立對象。對于那些無法避免須要建立對象的情況,我們能夠考慮對象池模型,通過對象池來解決頻繁建立與銷毀的問題,可是這裡須要注意結束使用之後,須要手動釋放對象池中的對象。

6)Allocation Tracker

關于Allocation Tracker工具的使用,不展開了。參考以下的連結:

http://developer.android.com/tools/debugging/ddms.html#alloc

http://android-developers.blogspot.com/2009/02/track-memory-allocations.html

7)Improve Your Code To Reduce Churn

以下示範一個樣例,怎樣通過改動代碼來避免記憶體抖動。優化之前的記憶體檢測圖:

Android 性能優化探究

8)Recap

  • Memory Monitor:跟蹤整個app的記憶體變化情況。
  • Heap Viewer:檢視目前記憶體快照,便于對照分析哪些對象有可能發生了洩漏。
  • Allocation Tracker:追蹤記憶體對象的來源。

    Notes:關于很多其它記憶體優化。這裡另一篇文章,請點選這裡