標籤:
記憶體最佳化
Android系統對每個軟體所能使用的RAM空間進行了限制(如:Nexus one 對每個軟體的記憶體限制是24M),同時Java語言本身比較消耗記憶體,dalvik虛擬機器也要佔用一定的記憶體空間,所以合理使用記憶體,彰顯出一個程式員的素質和技能。
1) 瞭解JIT
即時編譯(Just-in-time Compilation,JIT),又稱動態轉譯(Dynamic Translation),是一種通過在運行時將位元組碼翻譯為機器碼,從而改善位元組碼編譯語言效能的技術。即時編譯前期的兩個運行時理論是位元組碼編譯和動態編譯。Android原來Dalvik虛擬機器是作為一種解譯器實現,新版(Android2.2+)將換成JIT編譯器實現。效能測試顯示,在多項測試中新版本比舊版本提升了大約6倍。
避免建立不必要的對象
就像世界上沒有免費的午餐,世界上也沒有免費的對象。雖然gc為每個線程都建立了臨時對象池,可以使建立對象的代價變得小一些,但是分配記憶體永遠都比不分配記憶體的代價大。如果你在使用者介面迴圈中指派至記憶體,就會引發周期性的記憶體回收,使用者就會覺得介面像打嗝一樣一頓一頓的。所以,除非必要,應盡量避免儘力對象的執行個體。下面的例子將協助你理解這條原則:當你從使用者輸入的資料中截取一段字串時,盡量使用substring函數取得未經處理資料的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一 個新的String對象,它與未經處理資料共用一個char數組。 如果你有一個函數返回一個String對象,而你確切的知道這個字串會被附加到一個StringBuffer,那麼,請改變這個函數的參數和實現方式, 直接把結果附加到StringBuffer中,而不要再建立一個短命的臨時對象。
一個更極端的例子是,把多維陣列分成多個一維數組:
int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比 (int,int)對象數組效能要好很多。同理,這試用於所有基本類型的組合。如果你想用一種容器儲存(Foo,Bar)元組,嘗試使用兩個單獨的 Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個API,讓別人調用它的時候。這時候你要注重對API介面的設計而犧牲一點兒速度。當然在API的內部,你仍要儘可能的提高代碼的效率)
總體來說,就是避免建立短命的臨時對象。減少對象的建立就能減少垃圾收集,進而減少對使用者體驗的影響。
盡量避免在經常調用的方法,迴圈中new對象,由於系統不僅要花費時間來建立對象,而且還要花時間對這些對象進行記憶體回收和處理,在我們可以控制的範圍內,最大限度的重用對象,最好能用基本的資料類型或數組來替代對象。
總體來說,就是避免建立短命的臨時對象。減少對象的建立就能減少垃圾收集,進而減少對使用者體驗的影響。
減少不必要的全域變數
盡量避免static成員變數引用資源耗費過多的執行個體,比如Context。Android提供了很健全的訊息傳遞機制(Intent)和任務模型(Handler),可以通過傳遞或事件的方式,防止一些不必要的全域變數。
不要過多指望gc
Java的gc使用的是一個有向圖,判斷一個對象是否有效看的是其他的對象能到達這個對象的頂點,有向圖的相對於鏈表、二叉樹來說開銷是可想而知。所以不要過多指望gc。將不用的對象可以把它指向NULL,並注意代碼品質。同時,顯示讓系統gc回收,例片處理時,
if(bitmap.isRecycled()==false) { //如果沒有回收 bitmap.recycle();}
盡量避免隨意使用靜態變數
要知道,當某個對象被定義為stataic變數所引用,那麼gc通常是不會回收這個對象所佔有的記憶體,如
public class A{ static B b = new B();}
將成員緩衝到本地
訪問成員變數比訪問本地變數慢得多,下面一段代碼:
for(int i =0; i <this.mCount; i++) {dumpItem(this.mItems);}
最好改成這樣:
int count = this.mCount;Item[] items = this.mItems;for(int i =0; i < count; i++) { dumpItems(items);}
另一個相似的原則是:永遠不要在for的第二個條件中調用任何方法。如下面方法所示,在每次迴圈的時候都會調用getCount()方法,這樣做比你在一個int先把結果儲存起來開銷大很多。
for(int i =0; i < this.getCount(); i++) {dumpItems(this.getItem(i));}
同樣如果你要多次訪問一個變數,也最好先為它建立一個本地變數,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) { if(isHorizontalScrollBarEnabled()) { intsize = mScrollBar.getSize(false); if(size <=0) { size = mScrollBarSize; } mScrollBar.setBounds(0, height - size, width, height); mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false); mScrollBar.draw(canvas); }}
這裡有4次訪問成員變數mScrollBar,如果將它緩衝到本地,4次成員變數訪問就會變成4次效率更高的棧變數訪問。
如果mScrollBar 只是 一個 基礎資料型別 (Elementary Data Type),直接存取是可以接受的; 但如果mScrollBar 是一個 複雜的對象,需要 複製過來比較好, 因為如果訪問的話,
另外就是方法的參數與本地變數的效率相同。
盡量早釋放無用對象的引用
大部分時,方法局部引用變數所引用的對象 會隨著方法結束而變成垃圾,因此,大部分時候程式無需將局部,引用變數顯式設為null。
Public void test(){ Object obj = new Object(); …… Obj=null;}
上面這個就沒必要了,隨著方法test()的執行完成,程式中obj引用變數的範圍就結束了。但是如果是改成下面:
Public void test(){ Object obj = new Object(); …… Obj=null; //執行耗時,耗記憶體操作;或調用耗時,耗記憶體的方法 ……}
這時候就有必要將obj賦值為null,可以儘早的釋放對Object對象的引用。
盡量緩衝經常使用的對象
儘可能將經常使用的對象進行緩衝,可以使用數組,或HashMap的容器來進行緩衝,但這種方式可能導致系統佔用過多的緩衝,效能下降,推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行緩衝,他們基本都實現了FIFO/FLU等緩衝演算法。
盡量避免非常大的記憶體配置
有時候問題不是由當時的堆狀態造成的,而是因為分配失敗造成的。分配的記憶體塊都必須是連續的,而隨著堆越來越滿,找到較大的連續塊越來越困難。
緩衝
適量使用緩衝,不要過量使用,因為記憶體有限,能儲存路徑地址的就不要存放圖片資料,不經常使用的盡量不要緩衝,不用時就清空。
Android最佳化-與Java有關-記憶體