Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android系統教程 >> Android開發教程 >> android開發中的內存優化

android開發中的內存優化

編輯:Android開發教程

一、Android應用程序內存優化

在開發Android App的過程中,經常會遇到內存方面的壓力,比如OOM,或者頻繁GC。本文不打算涵蓋內存優化的所有方面,只是介紹一下我自己遇到的問題和解決方法。

1.確定頻繁分配內存的代碼路徑

一般來說,頻繁分配內存的路徑可能會是繪制(draw)相關的方法,排版(layout)相關的方法,某些回調方法(特別是傳感器回調方法)。你可能會檢查這部分代碼,然後優化它。但是,內存分配可能發生在調用鏈的更下面,檢查代碼非常困難。這裡推薦一個工具,DDMS下的Allocation Tracker。它可以顯示出程序運行中頻繁分配內存的部分,並准確定位到對應的代碼。

2.減少頻繁的內存分配

雖然Java是自動回收內存的,但是頻繁的內存分配肯定會增加GC方面的壓力,導致程序運行不流暢。減少頻繁地創建對象或者不在這些地方創建對象是一個顯而易見的方法。但是,也有一些比較隱秘的內存消耗點,下面就介紹一下for循環的內存消耗。

我們知道,在Java中,for循環有兩種寫法:

    ArrayList();


for(int i  ; i  size(); i) {
       s  get(i);
      
}


for( s : ) {
      
}

寫法二是Java推薦的方法,但是它卻需要分配一個迭代器對象。如果是放在onDraw這樣的方法中,那麼每次調用就需要分配一小塊內存。所以,我推薦在onDraw這樣的方法中,使用方法一。

二、

隨著技術的發展,智能手機硬件配置越來越高,可是它和現在的PC相比,其運算能力,續航能力,存儲空間等都還是受到很大的限制,同時用戶對手機的體驗要 求遠遠高於PC的桌面應用程序。以上理由,足以需要開發人員更加專心去實現和優化你的代碼了。選擇合適的算法和數據結構永遠是開發人員最先應該考慮的事 情。同時,我們應該時刻牢記,寫出高效代碼的兩條基本的原則:(1)不要做不必要的事;(2)不要分配不必要的內存。

我從去年開始接觸Android開發,以下結合自己的一點項目經驗,同時參考了Google的優化文檔和網上的諸多技術大牛給出的意見,整理出這份文檔。

1.內存優化

Android系統對每個軟件所能使用的RAM空間進行了限制(如:Nexus one 對每個軟件的內存限制是24M),同時Java語言本身比較消耗內存,dalvik虛擬機也要占用一定的內存空間,所以合理使用內存,彰顯出一個程序員的素質和技能。

1)了解JIT

即時編譯(Just-in-time Compilation,JIT),又稱動態轉譯(Dynamic Translation),是一種通過在運行時將字節碼翻譯為機器碼,從而改善字節碼編譯語言性能的技術。即時編譯前期的兩個運行時理論是字節碼編譯和動態編譯。Android原來Dalvik虛擬機是作為一種解釋器實現,新版(Android2.2+)將換成JIT編譯器實現。性能測試顯示,在多項測試中新版本比舊版本提升了大約6倍。

詳細請參考:http://hi.baidu.com/cool_parkour/blog/item/2802b01586e22cd8a6ef3f6b.html

2)避免創建不必要的對象

就像世界上沒有免費的午餐,世界上也沒有免費的對象。雖然gc為每個線程都建立了臨時對象池,可以使創建對象的代價變得小一些,但是分配內存永遠都比不分配內存的代價大。如果你在用戶界面循環中分配對象內存,就會引發周期性的垃圾回收,用戶就會覺得界面像打嗝一樣一頓一頓的。所以,除非必要,應盡量避免盡力對象的實例。下面的例子將幫助你理解這條原則:當你從用戶輸入的數據中截取一段字符串時,盡量使用substring函數取得原始數據的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一 個新的String對象,它與原始數據共享一個char數組。 如果你有一個函數返回一個String對象,而你確切的知道這個字符串會被附加到一個StringBuffer,那麼,請改變這個函數的參數和實現方式, 直接把結果附加到StringBuffer中,而不要再建立一個短命的臨時對象。

一個更極端的例子是,把多維數組分成多個一維數組:

int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比 (int,int)對象數組性能要好很多。同理,這試用於所有基本類型的組合。如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個單獨的 Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個API,讓別人調用它的時候。這時候你要注重對API接口的設計而犧牲一點兒速度。當然在API的內部,你仍要盡可能的提高代碼的效率)

總體來說,就是避免創建短命的臨時對象。減少對象的創建就能減少垃圾收集,進而減少對用戶體驗的影響。

3) 靜態方法代替虛擬方法

如果不需要訪問某對象的字段,將方法設置為靜態,調用會加速15%到20%。這也是一種好的做法,因為你可以從方法聲明中看出調用該方法不需要更新此對象的狀態。

4)避免內部Getters/Setters

在源生語言像C++中,通常做法是用Getters(i=getCount())代替直接字段訪問(i=mCount)。這是C++中一個好的習慣,因為編譯器會內聯這些訪問,並且如果需要約束或者調試這些域的訪問,你可以在任何時間添加代碼。而在Android中,這不是一個好的做法。虛方法調用的代價比直接字段訪問高昂許多。通常根據面向對象語言的實踐,在公共接口中使用Getters和Setters是有道理的,但在一個字段經常被訪問的類中宜采用直接訪問。無JIT時,直接字段訪問大約比調用getter訪問快3倍。有JIT時(直接訪問字段開銷等同於局部變量訪問),要快7倍。

5)將成員緩存到本地

訪問成員變量比訪問本地變量慢得多,下面一段代碼:

( i =; i <.mCount; i++)  {
dumpItem(.mItems);
}
最好改成這樣:
  = .mCount;
Item[] items = .mItems;
( i =; i < ; i++)  {
       dumpItems(items);
}

另一個相似的原則是:永遠不要在for的第二個條件中調用任何方法。如下面方法所示,在每次循環的時候都會調用getCount()方法,這樣做比你在一個int先把結果保存起來開銷大很多。

同樣如果你要多次訪問一個變量,也最好先為它建立一個本地變量,例如:

  (Canvas canvas,  width,  height) {
   (isHorizontalScrollBarEnabled()) {
      intsize = mScrollBar.getSize();
        (size <=) {
          size = mScrollBarSize;
         }
    mScrollBar.setBounds(, height - size, width, height);
    mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),);
    mScrollBar.draw(canvas);
   }
} 

這裡有4次訪問成員變量mScrollBar,如果將它緩存到本地,4次成員變量訪問就會變成4次效率更高的棧變量訪問。

另外就是方法的參數與本地變量的效率相同。

6)對常量使用static final修飾符

讓我們來看看這兩段在類前面的聲明:

static int intVal = 42;

static String strVal = “Hello, world!”

必以其會生成一個叫做clinit的初始化類的方法,當類第一次被使用的時候這個方法會被執行。方法會將42賦給intVal,然後把一個指向類中常量表 的引用賦給strVal。當以後要用到這些值的時候,會在成員變量表中查找到他們。 下面我們做些改進,使用“final”關鍵字:

static final int intVal = 42;

static final String strVal = “Hello, world!”;

現在,類不再需要clinit方法,因為在成員變量初始化的時候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字符串常量,而不是使用成員變量。將一個方法或類聲明為final不會帶來性能的提升,但是會幫助編譯器優化代碼。舉例說,如果編譯器知道一個getter方法不會被重載,那麼編譯器會對其采用內聯調用。

你也可以將本地變量聲明為final,同樣,這也不會帶來性能的提升。使用“final”只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved