編輯:關於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
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
2015新年伊始,Google發布了關於Android性能優化典范的專題,一共16個短視頻,每個3-5分鐘,幫助開發者創建更快更優秀的Android App。課程專題不僅僅介紹了Android系統中有關性能問題的底層工作原理,同時也介紹了如何通過工具來找出性能問題以及提升性能的建議。主要從三個方面展開,Android的渲染機制,內存與GC,電量優化。下面是對這些問題和建議的總結梳理。
大多數用戶感知到的卡頓等性能問題的最主要根源都是因為渲染性能。從設計師的角度,他們希望App能夠有更多的動畫,圖片等時尚元素來實現流暢的用戶體驗。但是Android系統很有可能無法及時完成那些復雜的界面渲染操作。Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著程序的大多數操作都必須在16ms內完成。
如果你的某個操作花費時間是24ms,系統在得到VSYNC信號的時候就無法進行正常渲染,這樣就發生了丟幀現象。那麼用戶在32ms內看到的會是同一幀畫面。
用戶容易在UI執行動畫或者滑動ListView的時候感知到卡頓不流暢,是因為這裡的操作相對復雜,容易發生丟幀的現象,從而感覺卡頓。有很多原因可以導致丟幀,也許是因為你的layout太過復雜,無法在16ms內完成渲染,有可能是因為你的UI上有層疊太多的繪制單元,還有可能是因為動畫執行的次數過多。這些都會導致CPU或者GPU負載過重。
我們可以通過一些工具來定位問題,比如可以使用HierarchyViewer來查找Activity中的布局是否過於復雜,也可以使用手機設置裡面的開發者選項,打開Show GPU Overdraw等選項進行觀察。你還可以使用TraceView來觀察CPU的執行情況,更加快捷的找到性能瓶頸。
Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次的UI結構裡面,如果不可見的UI也在做繪制的操作,這就會導致某些像素區域被繪制了多次。這就浪費大量的CPU以及GPU資源。
vcC01L224LXEsuO1/tfpvP7AtMq1z9bV4tbWytO+9dCnufu1xLnWyKaho9XiutzI3dLXtbzWwrTzwb+1xNDUxNzOyszio6zOqsHLu/G1w9fuvNG1xNDUxNyjrM7Sw8ex2NDrvqHBv7z1ydlPdmVyZHJhd7XEx+m/9reiyfqhozwvcD4NCjxwPtDS1Mu1xMrHo6zO0sPHv8nS1M2ouf3K1rv6yejWw8Dvw+a1xL+qt6LV39Ghz+6jrLTyv6pTaG93IEdQVSBPdmVyZHJhd7XE0aHP7qOsv8nS1LnbsuxVScnPtcRPdmVyZHJhd8fpv/ahozxiciAvPg0KPGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20150724/2015072408335151.png" title="\" />
藍色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標就是盡量減少紅色Overdraw,看到更多的藍色區域。
Overdraw有時候是因為你的UI布局存在大量重疊的部分,還有的時候是因為非必須的重疊背景。例如某個Activity有一個背景,然後裡面的Layout又有自己的背景,同時子View又分別有自己的背景。僅僅是通過移除非必須的背景圖片,這就能夠減少大量的紅色Overdraw區域,增加藍色區域的占比。這一措施能夠顯著提升程序性能。
為了理解App是如何進行渲染的,我們必須了解手機硬件是如何工作,那麼就必須理解什麼是VSYNC。
在講解VSYNC之前,我們需要了解兩個相關的概念:
Refresh Rate:代表了屏幕在一秒內刷新屏幕的次數,這取決於硬件的固定參數,例如60Hz。 Frame Rate:代表了GPU在一秒內繪制操作的幀數,例如30fps,60fps。不幸的是,刷新頻率和幀率並不是總能夠保持相同的節奏。如果發生幀率與刷新頻率不一致的情況,就會容易出現Tearing的現象(畫面上下兩部分顯示內容發生斷裂,來自不同的兩幀數據發生重疊)。
理解圖像渲染裡面的雙重與三重緩存機制,這個概念比較復雜,請移步查看這裡:
http://source.android.com/devices/graphics/index.html
http://article.yeeyan.org/view/37503/304664
通常來說,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下,GPU所產生的幀數據會因為等待VSYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實際的新的數據可以顯示。但是我們遇到更多的情況是幀率小於刷新頻率。
在這種情況下,某些幀顯示的畫面內容就會與上一幀的畫面相同。糟糕的事情是,幀率從超過60fps突然掉到60fps以下,這樣就會發生LAG,JANK,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在。
性能問題如此的麻煩,幸好我們可以有工具來進行調試。打開手機裡面的開發者選項,選擇Profile GPU Rendering,選中On screen as bars的選項。
選擇了這樣以後,我們可以在手機畫面上看到豐富的GPU繪制圖形信息,分別關於StatusBar,NavBar,激活的程序Activity區域的GPU Rending信息。
隨著界面的刷新,界面上會滾動顯示垂直的柱狀圖來表示每幀畫面所需要渲染的時間,柱狀圖越高表示花費的渲染時間越長。
中間有一根綠色的橫線,代表16ms,我們需要確保每一幀花費的總時間都低於這條橫線,這樣才能夠避免出現卡頓的問題。
每一條柱狀線都包含三部分,藍色代表測量繪制Display List的時間,紅色代表OpenGL渲染Display List所需要的時間,黃色代表CPU等待GPU處理的時間。
我們通常都會提到60fps與16ms,可是知道為何會是以程序是否達到60fps來作為App性能的衡量標准嗎?這是因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。
12fps大概類似手動快速翻動書籍的幀率,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續線性的運動,這其實是歸功於運動模糊的效果。24fps是電影膠圈通常使用的幀率,因為這個幀率已經足夠支撐大部分電影畫面需要表達的內容,同時能夠最大的減少費用支出。但是低於30fps是無法順暢表現絢麗的畫面內容的,此時就需要用到60fps來達到想要的效果,當然超過60fps是沒有必要的。
開發app的性能目標就是保持60fps,這意味著每一幀你只有16ms=1000/60的時間來處理所有的任務。
了解Android是如何利用GPU進行畫面渲染有助於我們更好的理解性能問題。那麼一個最實際的問題是:activity的畫面是如何繪制到屏幕上的?那些復雜的XML布局文件又是如何能夠被識別並繪制出來的?
Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎的操作。它把那些組件拆分到不同的像素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作。
CPU負責把UI組件計算成Polygons,Texture紋理,然後交給GPU進行柵格化渲染。
然而每次從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計算,繪制,渲染等等操作。
順滑精妙的動畫是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的位置。如果布局很復雜,這就會很容易導致嚴重的性能問題。我們需要盡量減少Overdraw。
我們可以通過前面介紹的Monitor GPU Rendering來查看渲染的表現性能如何,另外也可以通過開發者選項裡面的Show GPU view updates來查看視圖更新的操作,最後我們還可以通過HierarchyViewer這個工具來查看布局,使得布局盡量扁平化,移除非必需的UI組件,這些操作能夠減少Measure,Layout的計算時間。
引起性能問題的一個很重要的方面是因為過多復雜的繪制操作。我們可以通過工具來檢測並修復標准UI組件的Overdraw問題,但是針對高度自定義的UI組件則顯得有些力不從心。
有一個竅門是我們可以通過執行幾個APIs方法來顯著提升繪制操作的性能。前面有提到過,非可見的UI組件進行繪制更新會導致Overdraw。例如Nav Drawer從前置可見的Activity滑出之後,如果還繼續繪制那些在Nav Drawer裡面不可見的UI組件,這就導致了Overdraw。為了解決這個問題,Android系統會通過避免繪制那些完全不可見的組件來盡量減少Overdraw。那些Nav Drawer裡面不可見的View就不會被執行浪費資源。
但是不幸的是,對於那些過於復雜的自定義的View(重寫了onDraw方法),Android系統無法檢測具體在onDraw裡面會執行什麼操作,系統無法監控並自動優化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪制,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪制指令都不會被執行,那些部分內容在矩形區域內的組件,仍然會得到繪制。
除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區域內的繪制操作。做了那些優化之後,我們可以通過上面介紹的Show GPU Overdraw來查看效果。
雖然Android有自動管理內存的機制,但是對內存的不恰當使用仍然容易引起嚴重的性能問題。在同一幀裡面創建過多的對象是件需要特別引起注意的事情。
Android系統裡面有一個Generational Heap Memory的模型,系統會根據內存中不同的內存數據類型分別執行不同的GC操作。例如,最近剛分配的對象會放在Young Generation區域,這個區域的對象通常都是會快速被創建並且很快被銷毀回收的,同時這個區域的GC操作速度也是比Old Generation區域的GC操作速度更快的。
除了速度差異之外,執行GC操作的時候,所有線程的任何操作都會需要暫停,等待GC操作完成之後,其他操作才能夠繼續運行。
通常來說,單個的GC並不會占用太多時間,但是大量不停的GC操作則會顯著占用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。
導致GC頻繁執行有兩個原因:
Memory Churn內存抖動,內存抖動是因為大量的對象被創建又在短時間內馬上被釋放。
瞬間產生大量的對象會嚴重占用Young Generation的內存區域,當達到閥值,剩余空間不夠的時候,也會觸發GC。即使每次分配的對象占用了很少的內存,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他類型的GC。這個操作有可能會影響到幀率,並使得用戶感知到性能問題。
解決上面的問題有簡潔直觀方法,如果你在Memory Monitor裡面查看到短時間發生了多次內存的漲跌,這意味著很有可能發生了內存抖動。
同時我們還可以通過Allocation Tracker來查看在短時間內,同一個棧中不斷進出的相同對象。這是內存抖動的典型信號之一。
當你大致定位問題之後,接下去的問題修復也就顯得相對直接簡單了。例如,你需要避免在for循環裡面分配對象占用內存,需要嘗試把對象的創建移到循環體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發生繪制以及動畫執行過程中,onDraw方法都會被調用到,避免在onDraw方法裡面執行復雜的操作,避免創建對象。對於那些無法避免需要創建對象的情況,我們可以考慮對象池模型,通過對象池來解決頻繁創建與銷毀的問題,但是這裡需要注意結束使用之後,需要手動釋放對象池中的對象。
JVM的回收機制給開發人員帶來很大的好處,不用時刻處理對象的分配與回收,可以更加專注於更加高級的代碼實現。相比起Java,C與C++等語言具備更高的執行效率,他們需要開發人員自己關注對象的分配與回收,但是在一個龐大的系統當中,還是免不了經常發生部分對象忘記回收的情況,這就是內存洩漏。
原始JVM中的GC機制在Android中得到了很大程度上的優化。Android裡面是一個三級Generation的內存模型,最近分配的對象會存放在Young Generation區域,當這個對象在這個區域停留的時間達到一定程度,它會被移動到Old Generation,最後到Permanent Generation區域。
每一個級別的內存區域都有固定的大小,此後不斷有新的對象被分配到此區域,當這些對象總的大小快達到這一級別內存區域的閥值時,會觸發GC的操作,以便騰出空間來存放其他新的對象。
前面提到過每次GC發生的時候,所有的線程都是暫停狀態的。GC所占用的時間和它是哪一個Generation也有關系,Young Generation的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。執行時間的長短也和當前Generation中的對象數量有關,遍歷查找20000個對象比起遍歷50個對象自然是要慢很多的。
雖然Google的工程師在盡量縮短每次GC所花費的時間,但是特別注意GC引起的性能問題還是很有必要。如果不小心在最小的for循環單元裡面執行了創建對象的操作,這將很容易引起GC並導致性能問題。通過Memory Monitor我們可以查看到內存的占用情況,每一次瞬間的內存降低都是因為此時發生了GC操作,如果在短時間內發生大量的內存上漲與降低的事件,這說明很有可能這裡有性能問題。我們還可以通過Heap and Allocation Tracker工具來查看此時內存中分配的到底有哪些對象。
雖然Java有自動回收的機制,可是這不意味著Java中不存在內存洩漏的問題,而內存洩漏會很容易導致嚴重的性能問題。
內存洩漏指的是那些程序不再使用的對象無法被GC識別,這樣就導致這個對象一直留在內存當中,占用了寶貴的內存空間。顯然,這還使得每級Generation的內存區域可用空間變小,GC就會更容易被觸發,從而引起性能問題。
尋找內存洩漏並修復這個漏洞是件很棘手的事情,你需要對執行的代碼很熟悉,清楚的知道在特定環境下是如何運行的,然後仔細排查。例如,你想知道程序中的某個activity退出的時候,它之前所占用的內存是否有完整的釋放干淨了?首先你需要在activity處於前台的時候使用Heap Tool獲取一份當前狀態的內存快照,然後你需要創建一個幾乎不這麼占用內存的空白activity用來給前一個Activity進行跳轉,其次在跳轉到這個空白的activity的時候主動調用System.gc()方法來確保觸發一個GC操作。最後,如果前面這個activity的內存都有全部正確釋放,那麼在空白activity被啟動之後的內存快照中應該不會有前面那個activity中的任何對象了。
如果你發現在空白activity的內存快照中有一些可疑的沒有被釋放的對象存在,那麼接下去就應該使用Alocation Track Tool來仔細查找具體的可疑對象。我們可以從空白activity開始監聽,啟動到觀察activity,然後再回到空白activity結束監聽。這樣操作以後,我們可以仔細觀察那些對象,找出內存洩漏的真凶。
通常來說,Android對GC做了大量的優化操作,雖然執行GC操作的時候會暫停其他任務,可是大多數情況下,GC操作還是相對很安靜並且高效的。但是如果我們對內存的使用不恰當,導致GC頻繁執行,這樣就會引起不小的性能問題。
為了尋找內存的性能問題,Android Studio提供了工具來幫助開發者。
Memory Monitor:查看整個app所占用的內存,以及發生GC的時刻,短時間內發生大量的GC操作是一個危險的信號。
Allocation Tracker:使用此工具來追蹤內存的分配,前面有提到過。
Heap Tool:查看當前內存快照,便於對比分析哪些對象有可能是洩漏了的,請參考前面的Case。
Android Studio中的Memory Monitor可以很好的幫助我們查看程序的內存使用情況。
電量其實是目前手持設備最寶貴的資源之一,大多數設備都需要不斷的充電來維持繼續使用。不幸的是,對於開發者來說,電量優化是他們最後才會考慮的的事情。但是可以確定的是,千萬不能讓你的應用成為消耗電量的大戶。
Purdue University研究了最受歡迎的一些應用的電量消耗,平均只有30%左右的電量是被程序最核心的方法例如繪制圖片,擺放布局等等所使用掉的,剩下的70%左右的電量是被上報數據,檢查位置信息,定時檢索後台廣告信息所使用掉的。如何平衡這兩者的電量消耗,就顯得非常重要了。
有下面一些措施能夠顯著減少電量的消耗:
我們應該盡量減少喚醒屏幕的次數與持續的時間,使用WakeLock來處理喚醒的問題,能夠正確執行喚醒操作並根據設定及時關閉操作進入睡眠狀態。
某些非必須馬上執行的操作,例如上傳歌曲,圖片處理等,可以等到設備處於充電狀態或者電量充足的時候才進行。
觸發網絡請求的操作,每次都會保持無線信號持續一段時間,我們可以把零散的網絡請求打包進行一次操作,避免過多的無線信號引起的電量消耗。關於網絡請求引起無線信號的電量消耗,還可以參考這裡:
http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
我們可以通過手機設置選項找到對應App的電量消耗統計數據。我們還可以通過Battery Historian Tool來查看詳細的電量消耗。
如果發現我們的App有電量消耗過多的問題,我們可以使用JobScheduler API來對一些任務進行定時處理,例如我們可以把那些任務重的操作等到手機處於充電狀態,或者是連接到WiFi的時候來處理。 關於JobScheduler的更多知識可以參考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html
電量消耗的計算與統計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情。唯一可行的方案是使用第三方監測電量的設備,這樣才能夠獲取到真實的電量消耗。
當設備處於待機狀態時消耗的電量是極少的,以N5為例,打開飛行模式,可以待機接近1個月。可是點亮屏幕,硬件各個模塊就需要開始工作,這會需要消耗很多電量。
使用WakeLock或者JobScheduler喚醒設備處理定時的任務之後,一定要及時讓設備回到初始狀態。每次喚醒無線信號進行數據傳遞,都會消耗很多電量,它比WiFi等操作更加的耗電,詳情請關注
http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
修復電量的消耗是另外一個很大的課題,這裡就不展開繼續了。
高效的保留更多的電量與不斷促使用戶使用你的App會消耗電量,這是矛盾的選擇題。不過我們可以使用一些更好的辦法來平衡兩者。
假設你的手機裡面裝了大量的社交類應用,即使手機處於待機狀態,也會經常被這些應用喚醒用來檢查同步新的數據信息。Android會不斷關閉各種硬件來延長手機的待機時間,首先屏幕會逐漸變暗直至關閉,然後CPU進入睡眠,這一切操作都是為了節約寶貴的電量資源。但是即使在這種睡眠狀態下,大多數應用還是會嘗試進行工作,他們將不斷的喚醒手機。一個最簡單的喚醒手機的方法是使用PowerManager.WakeLock的API來保持CPU工作並防止屏幕變暗關閉。這使得手機可以被喚醒,執行工作,然後回到睡眠狀態。知道如何獲取WakeLock是簡單的,可是及時釋放WakeLock也是非常重要的,不恰當的使用WakeLock會導致嚴重錯誤。例如網絡請求的數據返回時間不確定,導致本來只需要10s的事情一直等待了1個小時,這樣會使得電量白白浪費了。這也是為何使用帶超時參數的wakelock.acquice()方法是很關鍵的。但是僅僅設置超時並不足夠解決問題,例如設置多長的超時比較合適?什麼時候進行重試等等?
解決上面的問題,正確的方式可能是使用非精准定時器。通常情況下,我們會設定一個時間進行某個操作,但是動態修改這個時間也許會更好。例如,如果有另外一個程序需要比你設定的時間晚5分鐘喚醒,最好能夠等到那個時候,兩個任務捆綁一起同時進行,這就是非精確定時器的核心工作原理。我們可以定制計劃的任務,可是系統如果檢測到一個更好的時間,它可以推遲你的任務,以節省電量消耗。
這正是JobScheduler API所做的事情。它會根據當前的情況與任務,組合出理想的喚醒時間,例如等到正在充電或者連接到WiFi的時候,或者集中任務一起執行。我們可以通過這個API實現很多免費的調度算法。
從Android 5.0開始發布了Battery History Tool,它可以查看程序被喚醒的頻率,又誰喚醒的,持續了多長的時間,這些信息都可以獲取到。
請關注程序的電量消耗,用戶可以通過手機的設置選項觀察到那些耗電量大戶,並可能決定卸載他們。所以盡量減少程序的電量消耗是非常有必要的。
Google前幾天剛發布了Android性能優化典范第2季的課程,一共20個短視頻,包括的內容大致有:電量優化,網絡優化,Wear上如何做優化,使用對象池來提高效率,LRU Cache,Bitmap的縮放,緩存,重用,PNG壓縮,自定義View的性能,提升設置alpha之後View的渲染性能,以及Lint,StictMode等等工具的使用技巧。 下面是對這些課程的總結摘要,認知有限,理解偏差的地方請多多指教!
對於手機程序,網絡操作相對來說是比較耗電的行為。優化網絡操作能夠顯著節約電量的消耗。在性能優化第1季裡面有提到過,手機硬件的各個模塊的耗電量是不一樣的,其中移動蜂窩模塊對電量消耗是比較大的,另外蜂窩模塊在不同工作強度下,對電量的消耗也是有差異的。當程序想要執行某個網絡請求之前,需要先喚醒設備,然後發送數據請求,之後等待返回數據,最後才慢慢進入休眠狀態。這個流程如下圖所示:
在上面那個流程中,蜂窩模塊的電量消耗差異如下圖所示:
從圖示中可以看到,激活瞬間,發送數據的瞬間,接收數據的瞬間都有很大的電量消耗,所以,我們應該從如何傳遞網絡數據以及何時發起網絡請求這兩個方面來著手優化。
首先我們需要區分哪些網絡請求是需要及時返回結果的,哪些是可以延遲執行的。例如,用戶主動下拉刷新列表,這種行為需要立即觸發網絡請求,並等待數據返回。但是對於上傳用戶操作的數據,同步程序設置等等行為則屬於可以延遲的行為。我們可以通過Battery Historian這個工具來查看關於移動蜂窩模塊的電量消耗(關於這部分的細節,請點擊Android性能優化之電量篇)。在Mobile Radio那一行會顯示蜂窩模塊的電量消耗情況,紅色的部分代表模塊正在工作,中間的間隔部分代表模塊正在休眠狀態,如果看到有一段區間,紅色與間隔頻繁的出現,那就說明這裡有可以優化的行為。如下圖所示:
對於上面可以優化的部分,我們可以有針對性的把請求行為捆綁起來,延遲到某個時刻統一發起請求。如下圖所示:
經過上面的優化之後,我們再回頭使用Battery Historian導出電量消耗圖,可以看到喚醒狀態與休眠狀態是連續大塊間隔的,這樣的話,總體電量的消耗就會變得更少。
當然,我們甚至可以把請求的任務延遲到手機網絡切換到WiFi,手機處於充電狀態下再執行。在前面的描述過程中,我們會遇到的一個難題是如何把網絡請求延遲,並批量進行執行。還好,Android提供了JobScheduler來幫助我們達成這個目標。
關於這部分主要會涉及到Prefetch(預取)與Compressed(壓縮)這兩個技術。對於Prefetch的使用,我們需要預先判斷用戶在此次操作之後,後續零散的請求是否很有可能會馬上被觸發,可以把後面5分鐘有可能會使用到的零散請求都一次集中執行完畢。對於Compressed的使用,在上傳與下載數據之前,使用CPU對數據進行壓縮與解壓,可以很大程度上減少網絡傳輸的時間。
想要知道我們的應用程序中網絡請求發生的時間,每次請求的數據量等等信息,可以通過Android Studio中的Networking Traffic Tool來查看詳細的數據,如下圖所示:
在Android Wear上會大量的使用Sensors來實現某些特殊功能,如何在盡量節約電量的前提下利用好Sensor會是我們需要特別注意的問題。下面會介紹一些在Android Wear上的最佳實踐典范。
盡量減少刷新請求,例如我們可以在不需要某些數據的時候盡快注銷監聽,減小刷新頻率,對Sensor的數據做批量處理等等。那麼如何做到這些優化呢?
首先我們需要盡量使用Android平台提供的既有運動數據,而不是自己去實現監聽采集數據,因為大多數Android Watch自身記錄Sensor數據的行為是有經過做電量優化的。
其次在Activity不需要監聽某些Sensor數據的時候需要盡快釋放監聽注冊。
還有我們需要盡量控制更新的頻率,僅僅在需要刷新顯示數據的時候才觸發獲取最新數據的操作。
另外我們可以針對Sensor的數據做批量處理,待數據累積一定次數或者某個程度的時候才更新到UI上。
最後當Watch與Phone連接起來的時候,可以把某些復雜操作的事情交給Phone來執行,Watch只需要等待返回的結果。
更對關於Sensors的知識,可以點擊這裡
Android Material Design風格的應用采用了大量的動畫來進行UI切換,優化動畫的性能不僅能夠提升用戶體驗還可以減少電量的消耗,下面會介紹一些簡單易行的方法。
在Android裡面一個相對操作比較繁重的事情是對Bitmap進行旋轉,縮放,裁剪等等。例如在一個圓形的鐘表圖上,我們把時鐘的指針摳出來當做單獨的圖片進行旋轉會比旋轉一張完整的圓形圖的所形成的幀率要高56%。
另外盡量減少每次重繪的元素可以極大的提升性能,假如某個鐘表界面上有很多需要顯示的復雜組件,我們可以把這些組件做拆分處理,例如把背景圖片單獨拎出來設置為一個獨立的View,通過setLayerType()方法使得這個View強制用Hardware來進行渲染。至於界面上哪些元素需要做拆分,他們各自的更新頻率是多少,需要有針對性的單獨討論。
如何使用Systrace等工具來查看某些View的渲染性能,在前面的章節裡面有提到過,感興趣的可以點擊這裡
對於大多數應用中的動畫,我們會使用PropertyAnimation或者ViewAnimation來操作實現,Android系統會自動對這些Animation做一定的優化處理,在Android上面學習到的大多數性能優化的知識同樣也適用於Android Wear。
想要獲取更多關於Android Wear中動畫效果的優化,請點擊WatchFace這個范例。
在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 把零碎的數據請求捆綁一起再進行操作在程序裡面經常會遇到的一個問題是短時間內創建大量的對象,導致內存緊張,從而觸發GC導致性能問題。對於這個問題,我們可以使用對象池技術來解決它。通常對象池中的對象可能是bitmaps,views,paints等等。關於對象池的操作原理,不展開述說了,請看下面的圖示:
使用對象池技術有很多好處,它可以避免內存抖動,提升性能,但是在使用的時候有一些內容是需要特別注意的。通常情況下,初始化的對象池裡面都是空白的,當使用某個對象的時候先去對象池查詢是否存在,如果不存在則創建這個對象然後加入對象池,但是我們也可以在程序剛啟動的時候就事先為對象池填充一些即將要使用到的數據,這樣可以在需要使用到這些對象的時候提供更快的首次加載速度,這種行為就叫做預分配。使用對象池也有不好的一面,程序員需要手動管理這些對象的分配與釋放,所以我們需要慎重地使用這項技術,避免發生對象的內存洩漏。為了確保所有的對象能夠正確被釋放,我們需要保證加入對象池的對象和其他外部對象沒有互相引用的關系。
遍歷容器是編程裡面一個經常遇到的場景。在Java語言中,使用Iterate是一個比較常見的方法。可是在Android開發團隊中,大家卻盡量避免使用Iterator來執行遍歷操作。下面我們看下在Android上可能用到的三種不同的遍歷方法:
使用上面三種方式在同一台手機上,使用相同的數據集做測試,他們的表現性能如下所示:
從上面可以看到for index的方式有更好的效率,但是因為不同平台編譯器優化各有差異,我們最好還是針對實際的方法做一下簡單的測量比較好,拿到數據之後,再選擇效率最高的那個方式。
這小節我們要討論的是緩存算法,在Android上面最常用的一個緩存算法是LRU(Least Recently Use),關於LRU算法,不展開述說,用下面一張圖演示下含義:
LRU Cache的基礎構建用法如下:
為了給LRU Cache設置一個比較合理的緩存大小值,我們通常是用下面的方法來做界定的:
使用LRU Cache時為了能夠讓Cache知道每個加入的Item的具體大小,我們需要Override下面的方法:
使用LRU Cache能夠顯著提升應用的性能,可是也需要注意LRU Cache中被淘汰對象的回收,否者會引起嚴重的內存洩露。
Lint是Android提供的一個靜態掃描應用源碼並找出其中的潛在問題的一個強大的工具。
例如,如果我們在onDraw方法裡面執行了new對象的操作,Lint就會提示我們這裡有性能問題,並提出對應的建議方案。Lint已經集成到Android Studio中了,我們可以手動去觸發這個工具,點擊工具欄的Analysis -> Inspect Code,觸發之後,Lint會開始工作,並把結果輸出到底部的工具欄,我們可以逐個查看原因並根據指示做相應的優化修改。
Lint的功能非常強大,他能夠掃描各種問題。當然我們可以通過Android Studio設置找到Lint,對Lint做一些定制化掃描的設置,可以選擇忽略掉那些不想Lint去掃描的選項,我們還可以針對部分掃描內容修改它的提示優先級。
建議把與內存有關的選項中的嚴重程度標記為紅色的Error,對於Layout的性能問題標記為黃色Warning。
這小節會介紹如何減少透明區域對性能的影響。通常來說,對於不透明的View,顯示它只需要渲染一次即可,可是如果這個View設置了alpha值,會至少需要渲染兩次。原因是包含alpha的view需要事先知道混合View的下一層元素是什麼,然後再結合上層的View進行Blend混色處理。
在某些情況下,一個包含alpha的View有可能會觸發改View在HierarchyView上的父View都被額外重繪一次。下面我們看一個例子,下圖演示的ListView中的圖片與二級標題都有設置透明度。
大多數情況下,屏幕上的元素都是由後向前進行渲染的。在上面的圖示中,會先渲染背景圖(藍,綠,紅),然後渲染人物頭像圖。如果後渲染的元素有設置alpha值,那麼這個元素就會和屏幕上已經渲染好的元素做blend處理。很多時候,我們會給整個View設置alpha的來達到fading的動畫效果,如果我們圖示中的ListView做alpha逐漸減小的處理,我們可以看到ListView上的TextView等等組件會逐漸融合到背景色上。但是在這個過程中,我們無法觀察到它其實已經觸發了額外的繪制任務,我們的目標是讓整個View逐漸透明,可是期間ListView在不停的做Blending的操作,這樣會導致不少性能問題。
如何渲染才能夠得到我們想要的效果呢?我們可以先按照通常的方式把View上的元素按照從後到前的方式繪制出來,但是不直接顯示到屏幕上,而是使用GPU預處理之後,再又GPU渲染到屏幕上,GPU可以對界面上的原始數據直接做旋轉,設置透明度等等操作。使用GPU進行渲染,雖然第一次操作相比起直接繪制到屏幕上更加耗時,可是一旦原始紋理數據生成之後,接下去的操作就比較省時省力。
如何才能夠讓GPU來渲染某個View呢?我們可以通過setLayerType的方法來指定View應該如何進行渲染,從SDK 16開始,我們還可以使用ViewPropertyAnimator.alpha().withLayer()來指定。如下圖所示:
另外一個例子是包含陰影區域的View,這種類型的View並不會出現我們前面提到的問題,因為他們並不存在層疊的關系。
為了能夠讓渲染器知道這種情況,避免為這種View占用額外的GPU內存空間,我們可以做下面的設置。
通過上面的設置以後,性能可以得到顯著的提升,如下圖所示:
我們都知道應該避免在onDraw()方法裡面執行導致內存分配的操作,下面講解下為何需要這樣做。
首先onDraw()方法是執行在UI線程的,在UI線程盡量避免做任何可能影響到性能的操作。雖然分配內存的操作並不需要花費太多系統資源,但是這並不意味著是免費無代價的。設備有一定的刷新頻率,導致View的onDraw方法會被頻繁的調用,如果onDraw方法效率低下,在頻繁刷新累積的效應下,效率低的問題會被擴大,然後會對性能有嚴重的影響。
如果在onDraw裡面執行內存分配的操作,會容易導致內存抖動,GC頻繁被觸發,雖然GC後來被改進為執行在另外一個後台線程(GC操作在2.3以前是同步的,之後是並發),可是頻繁的GC的操作還是會影響到CPU,影響到電量的消耗。
那麼簡單解決頻繁分配內存的方法就是把分配操作移動到onDraw()方法外面,通常情況下,我們會把onDraw()裡面new Paint的操作移動到外面,如下面所示:
UI線程被阻塞超過5秒,就會出現ANR,這太糟糕了。防止程序出現ANR是很重要的事情,那麼如何找出程序裡面潛在的坑,預防ANR呢?很多大部分情況下執行很快的方法,但是他們有可能存在巨大的隱患,這些隱患的爆發就很容易導致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();
}
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的渲染,這樣能夠極大的提高程序的整體表現性能。優化性能時大多數時候討論的都是如何減少不必要的操作,但是選擇何時去執行某些操作同樣也很重要。在第1季以及上一期的性能優化之電量篇裡面,我們有提到過移動蜂窩模塊的電量消耗模型。為了避免我們的應用程序過多的頻繁消耗電量,我們需要學習如何把後台任務打包批量,並選擇一個合適的時機進行觸發執行。下圖是每個應用程序各自執行後台任務導致的電量消耗示意圖:
因為像上面那樣做會導致浪費很多電量,我們需要做的是把部分應用的任務延遲處理,等到一定時機,這些任務一並進行處理。結果如下面的示意圖:
執行延遲任務,通常有下面三種方式:
1)AlarmManager
使用AlarmManager設置定時任務,可以選擇精確的間隔時間,也可以選擇非精確時間作為參數。除非程序有很強烈的需要使用精確的定時喚醒,否者一定要避免使用他,我們應該盡量使用非精確的方式。
2)SyncAdapter
我們可以使用SyncAdapter為應用添加設置賬戶,這樣在手機設置的賬戶列表裡面可以找到我們的應用。這種方式功能更多,但是實現起來比較復雜。我們可以從這裡看到官方的培訓課程:http://developer.android.com/training/sync-adapters/index.html
3)JobSchedulor
這是最簡單高效的方法,我們可以設置任務延遲的間隔,執行條件,還可以增加重試機制。
常見的png,jpeg,webp等格式的圖片在設置到UI上之前需要經過解碼的過程,而解壓時可以選擇不同的解碼率,不同的解碼率對內存的占用是有很大差別的。在不影響到畫質的前提下盡量減少內存的占用,這能夠顯著提升應用程序的性能。
Android的Heap空間是不會自動做兼容壓縮的,意思就是如果Heap空間中的圖片被收回之後,這塊區域並不會和其他已經回收過的區域做重新排序合並處理,那麼當一個更大的圖片需要放到heap之前,很可能找不到那麼大的連續空閒區域,那麼就會觸發GC,使得heap騰出一塊足以放下這張圖片的空閒區域,如果無法騰出,就會發生OOM。如下圖所示:
所以為了避免加載一張超大的圖片,需要盡量減少這張圖片所占用的內存大小,Android為圖片提供了4種解碼格式,他們分別占用的內存大小如下圖所示:
隨著解碼占用內存大小的降低,清晰度也會有損失。我們需要針對不同的應用場景做不同的處理,大圖和小圖可以采用不同的解碼率。在Android裡面可以通過下面的代碼來設置解碼率:
盡量減少PNG圖片的大小是Android裡面很重要的一條規范。相比起JPEG,PNG能夠提供更加清晰無損的圖片,但是PNG格式的圖片會更大,占用更多的磁盤空間。到底是使用PNG還是JPEG,需要設計師仔細衡量,對於那些使用JPEG就可以達到視覺效果的,可以考慮采用JPEG即可。我們可以通過Google搜索到很多關於PNG壓縮的工具,如下圖所示:
這裡要介紹一種新的圖片格式:Webp,它是由Google推出的一種既保留png格式的優點,又能夠減少圖片大小的一種新型圖片格式。關於Webp的更多細節,請點擊這裡
對bitmap做縮放,這也是Android裡面最遇到的問題。對bitmap做縮放的意義很明顯,提示顯示性能,避免分配不必要的內存。Android提供了現成的bitmap縮放的API,叫做createScaledBitmap(),使用這個方法可以獲取到一張經過縮放的圖片。
上面的方法能夠快速的得到一張經過縮放的圖片,可是這個方法能夠執行的前提是,原圖片需要事先加載到內存中,如果原圖片過大,很可能導致OOM。下面介紹其他幾種縮放圖片的方式。
inSampleSize能夠等比的縮放顯示圖片,同時還避免了需要先把原圖加載進內存的缺點。我們會使用類似像下面一樣的方法來縮放bitmap:
另外,我們還可以使用inScaled,inDensity,inTargetDensity的屬性來對解碼圖片做處理,源碼如下圖所示:
還有一個經常使用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖片,可以事先獲取到圖片的大小而不至於占用什麼內存。如下圖所示:
我們知道bitmap會占用大量的內存空間,這節會講解什麼是inBitmap屬性,如何利用這個屬性來提升bitmap的循環效率。前面我們介紹過使用對象池的技術來解決對象頻繁創建再回收的效率問題,使用這種方法,bitmap占用的內存空間會差不多是恆定的數值,每次新創建出來的bitmap都會需要占用一塊單獨的內存區域,如下圖所示:
為了解決上圖所示的效率問題,Android在解碼圖片的時候引進了inBitmap屬性,使用這個屬性可以得到下圖所示的效果:
使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的內存區域,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所占據的pixel data內存區域,而不是去問內存重新申請一塊區域來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要占用屏幕所能夠顯示的圖片數量的內存大小。下面是如何使用inBitmap的代碼示例:
使用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創建都能夠找到合適的“模板”去進行重用。如下圖所示:
Google介紹了一個開源的加載bitmap的庫:Glide,這裡面包含了各種對bitmap的優化技巧。
大多數開發者在沒有發現嚴重性能問題之前是不會特別花精力去關注性能優化的,通常大家關注的都是功能是否實現。當性能問題真的出現的時候,請不要慌亂。我們通常采用下面三個步驟來解決性能問題。
Gather:收集數據
我們可以通過Android SDK裡面提供的諸多工具來收集CPU,GPU,內存,電量等等性能數據,
Insight:分析數據
通過上面的步驟,我們獲取到了大量的數據,下一步就是分析這些數據。工具幫我們生成了很多可讀性強的表格,我們需要事先了解如何查看表格的數據,每一項代表的含義,這樣才能夠快速定位問題。如果分析數據之後還是沒有找到問題,那麼就只能不停的重新收集數據,再進行分析,如此循環。
Action:解決問題
定位到問題之後,我們需要采取行動來解決問題。解決問題之前一定要先有個計劃,評估這個解決方案是否可行,是否能夠及時的解決問題。
雖然前面介紹了很多調試的方法,處理技巧,規范建議等等,可是這並不意味著所有的情況都適用,我們還是需要根據當時的情景做特定靈活的處理。
圍繞Android生態系統,不僅僅有Phone,還有Wear,TV,Auto等等。對這些不同形態的程序進行性能優化,都離不開內存調試這個步驟。這節中介紹的內容大部分和Android性能優化典范與Android性能優化之內存篇重合,不展開了。
Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Youtube上發布的Android性能優化典范專題課程的細化與補充。
下面是內存篇章的學習筆記,部分內容與前面的性能優化典范有重合,歡迎大家一起學習交流!
眾所周知,與C/C++需要通過手動編碼來申請以及釋放內存有所不同,Java擁有GC的機制。Android系統裡面有一個Generational Heap Memory的模型,系統會根據內存中不同的內存數據類型分別執行不同的GC操作。例如,最近剛分配的對象會放在Young Generation區域,這個區域的對象通常都是會快速被創建並且很快被銷毀回收的,同時這個區域的GC操作速度也是比Old Generation區域的GC操作速度更快的。
除了速度差異之外,執行GC操作的時候,所有線程的任何操作都會需要暫停,等待GC操作完成之後,其他操作才能夠繼續運行。
通常來說,單個的GC並不會占用太多時間,但是大量不停的GC操作則會顯著占用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。
Android Studio中的Memory Monitor可以很好的幫助我們查看程序的內存使用情況。
內存洩漏表示的是不再用到的對象因為被錯誤引用而無法進行回收。
發生內存洩漏會導致Memory Generation中的剩余可用Heap Size越來越小,這樣會導致頻繁觸發GC,更進一步引起性能問題。
舉例內存洩漏,下面init()方法來自某個自定義View:
private void init() {
ListenerCollector collector = new ListenerCollector();
collector.setListener(this, mListener);
}
上面的例子容易存在內存洩漏,如果activity因為設備翻轉而重新創建,自定義的View會自動重新把新創建出來的mListener給綁定到ListenerCollector中,但是當activity被銷毀的時候,mListener卻無法被回收了。
下圖演示了Android Tools裡面的Heap Viewer的功能,我們可以看到當前進程中的Heap Size的情況,分別有哪些類型的數據,占比是多少。
Memory Churn內存抖動,內存抖動是因為在短時間內大量的對象被創建又馬上被釋放。瞬間產生大量的對象會嚴重占用Young Generation的內存區域,當達到閥值,剩余空間不夠的時候,會觸發GC從而導致剛產生的對象又很快被回收。即使每次分配的對象占用了很少的內存,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他類型的GC。這個操作有可能會影響到幀率,並使得用戶感知到性能問題。
解決上面的問題有簡潔直觀方法,如果你在Memory Monitor裡面查看到短時間發生了多次內存的漲跌,這意味著很有可能發生了內存抖動。
同時我們還可以通過Allocation Tracker來查看在短時間內,同一個棧中不斷進出的相同對象。這是內存抖動的典型信號之一。
當你大致定位問題之後,接下去的問題修復也就顯得相對直接簡單了。例如,你需要避免在for循環裡面分配對象占用內存,需要嘗試把對象的創建移到循環體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發生繪制以及動畫執行過程中,onDraw方法都會被調用到,避免在onDraw方法裡面執行復雜的操作,避免創建對象。對於那些無法避免需要創建對象的情況,我們可以考慮對象池模型,通過對象池來解決頻繁創建與銷毀的問題,但是這裡需要注意結束使用之後,需要手動釋放對象池中的對象。
關於Allocation Tracker工具的使用,不展開了,參考下面的鏈接:
http://developer.android.com/tools/debugging/ddms.html#alloc
http://android-developers.blogspot.com/2009/02/track-memory-allocations.html
下面演示一個例子,如何通過修改代碼來避免內存抖動。優化之前的內存檢測圖:
定位代碼之後,修復了String拼接的問題:
優化之後的內存監測圖:
上面提到了三種測量內存的工具,下面再簡要概括一下他們各自的特點:
Memory Monitor:跟蹤整個app的內存變化情況。 Heap Viewer:查看當前內存快照,便於對比分析哪些對象有可能發生了洩漏。 Allocation Tracker:追蹤內存對象的來源。
AnDroidDraw 是一個與 DroidDraw 集成的 Android 應用程序,它允許你從 DroidDraw 應用 程序下載你的 GUIs, 也允許你在一個 A
我們在平時做開發的時候,免不了會用到各種各樣的對話框,相信有過其他平台開發經驗的朋友都會知道,大部分的平台都只提供了幾個最簡單的實現,如果我們想實現自己特定需求的對話框,
To create a dynamic and multi-pane user interface on Android, you need to encapsulate
本文實例講述了Android利用jsoup解析HTML頁面的方法。分享給大家供大家參考,具體如下:這節主要是講解jsoup解析HTML頁面。由於在android開發過程中