Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android 內存和性能優化匯總

android 內存和性能優化匯總

編輯:關於Android編程

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

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

5、 將成員緩存到本地

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

for(int i =0; i 

最好改成這樣:

int count = this.mCount;
Item[] items = this.mItems;
for(int i =0; i < count; i++)  {
       dumpItems(items);
}

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

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

這裡有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”只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)。


7、

使用改進的For循環語法

改進for循環(有時被稱為for-each循環)能夠用於實現了iterable接口的集合類及數組中。在集合類中,迭代器讓接口調用 hasNext()和next()方法。在ArrayList中,手寫的計數循環迭代要快3倍(無論有沒有JIT),但其他集合類中,改進的for循環語 法和迭代器具有相同的效率。下面展示集中訪問數組的方法:

static class Foo {
        int mSplat;
    }
    Foo[] mArray = ...
    public void zero() {
        int sum = 0;
        for (int i = 0; i < mArray.length; ++i) {
            sum += mArray[i].mSplat;
        }
    }
    public void one() {
        int sum = 0;
        Foo[] localArray = mArray;
        int len = localArray.length;
  
        for (int i = 0; i < len; ++i) {
            sum += localArray[i].mSplat;
        }
    }
    public void two() {
        int sum = 0;
        for (Foo a : mArray) {
            sum += a.mSplat;
        }
}
}  

在zero()中,每次循環都會訪問兩次靜態成員變量,取得一次數組的長度。

在one()中,將所有成員變量存儲到本地變量。

two()使用了在java1.5中引入的foreach語法。編譯器會將對數組的引用和數組的長度保存到本地變量中,這對訪問數組元素非常好。 但是編譯器還會在每次循環中產生一個額外的對本地變量的存儲操作(對變量a的存取)這樣會比one()多出4個字節,速度要稍微慢一些。

8、

避免使用浮點數

通常的經驗是,在Android設備中,浮點數會比整型慢兩倍,在缺少FPU和JIT的G1上對比有FPU和JIT的Nexus One中確實如此(兩種設備間算術運算的絕對速度差大約是10倍)從速度方面說,在現代硬件上,float和double之間沒有任何不同。更廣泛的講,double大2倍。在台式機上,由於不存在空間問題,double的優先級高於float。但即使是整型,有的芯片擁有硬件乘法,卻缺少除法。這種情況下,整型除法和求模運算是通過軟件實現的,就像當你設計Hash表,或是做大量的算術那樣,例如a/2可以換成a*0.5。

9、

減少不必要的全局變量

盡量避免static成員變量引用資源耗費過多的實例,比如Context。Android提供了很健全的消息傳遞機制(Intent)和任務模型(Handler),可以通過傳遞或事件的方式,防止一些不必要的全局變量

10、

了解Java四種引用方式

JDK 1.2版本開始,把對象的引用分為4種級別,從而使程序能更加靈活地控制對象的生命周期。這4種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。

i. 強引用(StrongReference)

強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。

ii. 軟引用(SoftReference)

如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

iii. 弱引用(WeakReference)

在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。

iv. 虛引用(PhantomReference)

顧名思義,就是形同虛設。與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。了解並熟練掌握這4中引用方式,選擇合適的對象應用方式,對內存的回收是很有幫助的。

詳細請參考 http://blog.csdn.net/feng88724/article/details/6590064

Android中常使用緩存:
a. 線程池
b. Android圖片緩存,Android圖片Sdcard緩存,數據預取緩存
c. 消息緩存
通過handler.obtainMessage復用之前的message,如下:

1 handler.sendMessage(handler.obtainMessage(0,object));

d. ListView緩存

e. 網絡緩存
數據庫緩存http response,根據http頭信息中的Cache-Control域確定緩存過期時間。
f. 文件IO緩存
使用具有緩存策略的輸入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.對文件、網絡IO皆適用。
g. layout緩存
h. 其他需要頻繁訪問或訪問一次消耗較大的數據緩存

(2). 數據存儲優化
包括數據類型、數據結構的選擇。
a. 數據類型選擇
字符串拼接用StringBuilder代替String,在非並發情況下用StringBuilder代替StringBuffer。如果你對字符串的長度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,減少空間不夠時的再次分配。
64位類型如long double的處理比32位如int慢
使用SoftReference、WeakReference相對正常的強應用來說更有利於系統垃圾回收
final類型存儲在常量區中讀取效率更高
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高

b. 數據結構選擇
常見的數據結構選擇如:
ArrayList和LinkedList的選擇,ArrayList根據index取值更快,LinkedList更占內存、隨機插入刪除更快速、擴容效率更高。一般推薦ArrayList。
ArrayList、HashMap、LinkedHashMap、HashSet的選擇,hash系列數據結構查詢速度更優,ArrayList存儲有序元素,HashMap為鍵值對數據結構,LinkedHashMap可以記住加入次序的hashMap,HashSet不允許重復元素。
HashMap、WeakHashMap選擇,WeakHashMap中元素可在適當時候被系統垃圾回收器自動回收,所以適合在內存緊張型中使用。
Collections.synchronizedMap和ConcurrentHashMap的選擇,ConcurrentHashMap為細分鎖,鎖粒度更小,並發性能更優。Collections.synchronizedMap為對象鎖,自己添加函數進行鎖控制更方便。

Android也提供了一些性能更優的數據類型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的數據結構是為key為int情況的特殊處理,采用二分查找及簡單的數組存儲,加上不需要泛型轉換的開銷,相對Map來說性能更優。不過我不太明白為啥默認的容量大小是10,是做過數據統計嗎,還是說現在的內存優化不需要考慮這些東西,寫16會死嗎,還是建議大家根據自己可能的容量設置初始值。

(3). 算法優化
這個主題比較大,需要具體問題具體分析,盡量不用O(n*n)時間復雜度以上的算法,必要時候可用空間換時間。
查詢考慮hash和二分,盡量不用遞歸。可以從結構之法 算法之道或微軟、Google等面試題學習。

(4). JNI
Android應用程序大都通過Java開發,需要Dalvik的JIT編譯器將Java字節碼轉換成本地代碼運行,而本地代碼可以直接由設備管理器直接執行,節省了中間步驟,所以執行速度更快。不過需要注意從Java空間切換到本地空間需要開銷,同時JIT編譯器也能生成優化的本地代碼,所以糟糕的本地代碼不一定性能更優。
這個優化點會在後面單獨用一片博客介紹。

(5). 邏輯優化
這個不同於算法,主要是理清程序邏輯,減少不必要的操作。

(6). 需求優化
這個就不說了,對於sb的需求可能帶來的性能問題,只能說做為一個合格的程序員不能只是執行者,要學會說NO。不過不能拿這種接口敷衍產品經理哦。

2、異步,利用多線程提高TPS
充分利用多核Cpu優勢,利用線程解決密集型計算、IO、網絡等操作。
關於多線程可參考:
在Android應用程序中由於系統ANR的限制,將可能造成主線程超時操作放入另外的工作線程中。在工作線程中可以通過handler和主線程交互。

3、提前或延遲操作,錯開時間段提高TPS
(1) 延遲操作
不在Activity、Service、BroadcastReceiver的生命周期等對響應時間敏感函數中執行耗時操作,可適當delay。
Java中延遲操作可使用ScheduledExecutorService,不推薦使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,還有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定時等。

(2) 提前操作
對於第一次調用較耗時操作,可統一放到初始化中,將耗時提前。如得到壁紙wallpaperManager.getDrawable();

4、網絡優化
以下是網絡優化中一些客戶端和服務器端需要盡量遵守的准則:
a. 圖片必須緩存,最好根據機型做圖片做圖片適配
b. 所有http請求必須添加httptimeout
c. api接口數據以json格式返回,而不是xml或html
d. 根據http頭信息中的Cache-Control域確定是否緩存請求結果。
e. 減少網絡請求次數,服務器端適當做請求合並。
f. 減少重定向次數
g. api接口服務器端響應時間不超過100ms
google正在做將移動端網頁速度降至1秒的項目,關注中https://developers.google.com/speed/docs/insights/mobile



Android 性能優化方法


http://mobile.51cto.com/abased-410785.htm

對於一些Android項目,影響性能瓶頸的主要是Android自己內存管理機制問題,目前手機廠商對RAM都比較吝啬,對於軟件的流暢性來說RAM對性能的影響十分敏感,除了 優化Dalvik虛擬機的堆內存分配外,我們還可以強制定義自己軟件的對內存大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設置最小堆內存為例:

  1. private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;



  1. VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

//設置最小heap內存為6MB大小。當然對於內存吃緊來說還可以通過手動干涉GC去處理


bitmap 設置圖片尺寸,避免 內存溢出 OutOfMemoryError的優化方法
★android 中用bitmap 時很容易內存溢出,報如下錯誤:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget

● 主要是加上這段:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inSampleSize = 2;



● eg1:(通過Uri取圖片)

  1. private ImageView preview;
  2. BitmapFactory.Options options = new BitmapFactory.Options();
  3. options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
  4. Bitmap bitmap = BitmapFactory.decodeStream(cr
  5. .openInputStream(uri), null, options);
  6. preview.setImageBitmap(bitmap);


以上代碼可以優化內存溢出,但它只是改變圖片大小,並不能徹底解決內存溢出。
● eg2:(通過路徑去圖片)

  1. private ImageView preview;
  2. private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg";
  3. BitmapFactory.Options options = new BitmapFactory.Options();
  4. options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
  5. Bitmap b = BitmapFactory.decodeFile(fileName, options);
  6. preview.setImageBitmap(b);
  7. filePath.setText(fileName);



★Android 還有一些性能優化的方法:
● 首先內存方面,可以參考 Android堆內存也可自己定義大小 和 優化Dalvik虛擬機的堆內存分配

● 基礎類型上,因為Java沒有實際的指針,在敏感運算方面還是要借助NDK來完成。這點比較有意思的是Google 推出NDK可能是幫助游戲開發人員,比如OpenGL ES的支持有明顯的改觀,本地代碼操作圖形界面是很必要的。

● 圖形對象優化,這裡要說的是Android上的Bitmap對象銷毀,可以借助recycle()方法顯示讓GC回收一個Bitmap對象,通常對一個不用的Bitmap可以使用下面的方式,如

  1. if(bitmapObject.isRecycled()==false) //如果沒有回收
  2. bitmapObject.recycle();



● 目前系統對動畫支持比較弱智對於常規應用的補間過渡效果可以,但是對於游戲而言一般的美工可能習慣了GIF方式的統一處理,目前Android系統僅能預覽GIF的第一幀,可以借助J2ME中通過線程和自己寫解析器的方式來讀取GIF89格式的資源。

● 對於大多數Android手機沒有過多的物理按鍵可能我們需要想象下了做好手勢識別 GestureDetector 和重力感應來實現操控。通常我們還要考慮誤操作問題的降噪處理。

Android堆內存也可自己定義大小

對於一些大型Android項目或游戲來說在算法處理上沒有問題外,影響性能瓶頸的主要是Android自己內存管理機制問題,目前手機廠商對RAM都比較吝啬,對於軟件的流暢性來說RAM對性能的影響十分敏感,除了上次Android開發網提到的優化Dalvik虛擬機的堆內存分配外,我們還可以強制定義自己軟件的對內存大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設置最小堆內存為例:

  1. private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;



  1. VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

//設置最小heap內存為6MB大小。當然對於內存吃緊來說還可以通過手動干涉GC去處理,我們將在下次提到具體應用。

優化Dalvik虛擬機的堆內存分配

對於Android平台來說,其托管層使用的Dalvik JavaVM從目前的表現來看還有很多地方可以優化處理,比如我們在開發一些大型游戲或耗資源的應用中可能考慮手動干涉GC處理,使用 dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強程序堆內存的處理效率。當然具體原理我們可以參考開源工程,這裡我們僅說下使用方法: private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate時就可以調用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。

【編輯推薦】


Android 性能優化的一些方法
http://blog.csdn.net/yugui865/article/details/10211595

1.采用硬件加速,在androidmanifest.xml中application添加 android:hardwareAccelerated="true"。不過這個需要在android 3.0才可以使用。

2. View 中設置緩存屬性. setDrawingCache為true.

3. 優化你的布局。通過Android sdk中tools目錄下的layoutopt 命令查看你的布局是否需要優化。

4. 動態加載View. 采用ViewStub 避免一些不經常的視圖長期握住引用.

5.將Acitivity 中的Window 的背景圖設置為空。getWindow().setBackgroundDrawable(null); android的默認背景是不是為空。

6. 采用 優化布局層數。 采用來共享布局。

7. 查看Heap 的大小

8. 利用TraceView查看跟蹤函數調用。有的放矢的優化。

9. cursor 的使用。不過要注意管理好cursor,不要每次打開關閉cursor.因為打開關閉Cursor非常耗時。Cursor.require用於刷新cursor.

10.采用環形Buffer(可以采用鏈表數據結構實現)。可以設置一個鏈表長度的上限,根據手勢的變化來不斷地更新環形Buffer的內容。

11.采用SurfaceView在子線程刷新UI, 避免手勢的處理和繪制在同一UI線程(普通View都這樣做)。

12.采用JNI,將耗時間的處理放到c/c++層來處理。

13.有些能用文件操作的,盡量采用文件操作,文件操作的速度比數據庫的操作要快10倍左右。

14. 懶加載和緩存機制。訪問網絡的耗時操作啟動一個新線程來做,而不要再UI線程來做。


集合中對象沒清理造成的內存洩露
  我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。


.static關鍵字
當類的成員變量聲明成static後,它是屬於類的而不是屬於對象的,如果我們將很大的資源對象(Bitmap,context等)聲明成static,那麼這些資源不會隨著對象的回收而回收,
會一直存在,所以在使用static關鍵字定義成員變量的時候要慎重。

使用保守的Service

如果你的應用需要使用 service 在後台執行業務功能, 除非是一直在進行活動的工作(比如每隔幾秒向服務器端請求數據之類)否則不要讓它一直保持在後台運行. 並且, 當你的 service 執行完成但是停止失敗時要小心 service 導致的內存洩露問題.

當你啟動 service 時, 系統總是優先保持服務的運行. 這會導致內存應用效率非常低, 因為被該服務使用的內存不能做其它事情. 也會減少系統一直保持的LRU緩存處理數目, 使不同的app切換效率降低. 當前所有 service 的運行會導致內存不夠不能維持正常系統的運行時, 系統會發生卡頓的現象嚴重時能導致系統不斷重啟.

最好的方式是使用 IntentSevice 控制 service 的生命周期, 當使用 intent 開始任務後, 該 service 執行完所有的工作時會自動停止.

在android應用中當不需要使用常駐 service 執行業務功能而去使用一個常駐 service 是最糟糕的內存管理方式之一. 所以不要貪婪的使用 service 使你的應用一直運行狀態. 這樣不僅使你因為內存的限制提高了應用運行的風險, 也會導致用戶發現這些異常行為後而卸載應用.

當視圖變為隱藏狀態後釋放內存

當用戶跳轉到不同的應用並且你的視圖不再顯示時, 你應該釋放應用視圖所占的資源. 這時釋放所占用的資源能顯著的提高系統的緩存處理容量, 並且對用戶的體驗質量有直接的影響.

當實現當前 Activity 類的 onTrimMemory() 回調方法後, 用戶離開視圖時會得到通知. 使用該方法可以監聽 TRIM_MEMORY_UI_HIDDEN 級別, 當你的視圖元素從父視圖中處於隱藏狀態時釋放視圖所占用的資源.

注意只有當你應用的所有視圖元素變為隱藏狀態時你的應用才能收到 onTrimMemory() 回調方法的 TRIM_MEMORY_UI_HIDDEN . 這個和 onStop() 回調方法不同, 該方法只有當 Activity 的實例變為隱藏狀態, 或者有用戶移動到應用中的另外的 activity 才會引發. 所以說你雖然實現了 onStop() 去釋放 activity 的資源例如網絡連接或者未注冊的廣播接收者, 但是應該直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去釋放視圖資源否則不應該釋放視圖所占用的資源. 這裡可以確定的是如果用戶通過後退鍵從另外的 activity 進入到你的應用中, 視圖資源會一直處於可用的狀態可以用來快速的恢復 activity.

內存資源緊張時釋放內存

在應用生命周期的任何階段 onTrimMemory() 回調方法都可以告訴你設備的內存越來越低的情況, 你可以根據該方法推送的內存緊張級別來釋放資源.

  • TRIM_MEMORY_RUNNING_CRITICAL

    應用處於運行狀態並且不會被殺掉, 設備使用的內存比較低, 系統級會殺掉一些其它的緩存應用.

    • TRIM_MEMORY_RUNNING_LOW

      應用處於運行狀態並且不會被殺掉, 設備可以使用的內存非常低, 可以把不用的資源釋放一些提高性能(會直接影響程序的性能)

      • TRIM_MEMORY_RUNNING_CRITICAL
        應用處於運行狀態但是系統已經把大多數緩存應用殺掉了, 你必須釋放掉不是非常關鍵的資源, 如果系統不能回收足夠的運行內存, 系統會清除所有緩存應用並且會把正在活動的應用殺掉.

        還有, 當你的應用被系統正緩存時, 通過 onTrimMemory() 回調方法可以收到以下幾個內存級別:

        • TRIM_MEMORY_BACKGROUND

          系統處於低內存的運行狀態中並且你的應用處於緩存應用列表的初級階段. 雖然你的應用不會處於被殺的高風險中, 但是系統已經開始清除緩存列表中的其它應用, 所以你必須釋放資源使你的應用繼續存留在列表中以便用戶再次回到你的應用時能快速恢復進行使用.

          • TRIM_MEMORY_MODERATE

            系統處於低內存的運行狀態中並且你的應用處於緩存應用列表的中級階段. 如果系運行內存收到限制, 你的應用有被殺掉的風險.

            • TRIM_MEMORY_COMPLETE

              系統處於低內存的運行狀態中如果系統現在沒有內存回收你的應用將會第一個被殺掉. 你必須釋放掉所有非關鍵的資源從而恢復應用的狀態.

              因為 onTrimMemory() 是在級別14的android api中加入的, 所以低版本的要使用 onLowMemory() 方法, 該方法大致相當於 TRIM_MEMORY_COMPLETE 事件.

              注意: 當系統開始清除緩存應用列表中的應用時, 雖然系統的主要工作機制是自下而上, 但是也會通過殺掉消費大內存的應用從而使系統獲得更多的內存, 所以在緩存應用列表中消耗更少的內存將會有更大的機會留存下來以便用戶再次使用時進行快速恢復.

              檢查可以使用多大的內存

              前面提到, 不同的android設備系統擁有的運行內存各自都不同, 從而不同的應用堆內存的限制大小也不一樣. 你可以通過調用 ActivityManager 中的 getMemoryClass() 函數可以通過以兆為單位獲取當前應用可用的內存大小, 如果你想獲取超過最大限度的內存則會發生 OutOfMemoryError .

              有一個特別的情況, 可以在 manifest 文件中的 標簽中設置 largeHeap 屬性的值為 "true"時, 當前應用就可以獲取到系統分配的最大堆內存. 如果你設置了該值, 可以通過ActivityManager 的 getLargeMemoryClass() 函數獲取最大的堆內存.

              然後, 只有一小部分應用需要消耗大量堆內存(比如大照片編輯應用). 從來不需要使用大量內存僅僅是因為你已經消耗了大量的內存並且必須快速修復它, 你必須使用它是因為你恰好知道所有的內存已經被分配完了而你必須要保留當前應用不會被清除掉. 甚至當你的應用需要消耗大量內存時, 你應該盡可能的避免這種需求. 使用大量內存後, 當你切換不同的應用或者執行其它類似的操作時, 因為長時間的內存回收會導致系統的性能下降從而漸漸的會損害整個系統的用戶體驗.

              另外, 大內存不是所有的設備都相同. 當跑在有運行內存限制的設備上時, 大內存和正常的堆內存是一樣的. 所以如果你需要大內存, 你就要調用 getMemoryClass() 函數查看正常的堆內存的大小並且盡可能使內存使用情況維護在正常堆內存之下.

              避免在 bitmaps 中浪費內存

              當你加載 bitmap 時, 需要根據分辨率來保持它的內存時最大為當前設備的分辨率, 如果下載下來的原圖為高分辨率則要拉伸它. 要小心bitmap的分辨率增加後所占用的內存也要進行相應的增加, 因為它是根據x和y的大小來增加內存占用的.

              注意: 在 Android 2.3.x(api level 10)以下, 無論圖片的分辨率多大 bitmap 對象在內存中始終顯示相同大小, 實際的像素數據被存儲在底層 native 的內存中(c++內存). 因為內存分析工具無法跟蹤 native 的內存狀態所有調試 bitmap 內存分配變得非常困難. 然而, 從 Android 3.0(api level 11)開始, bitmap 對象的內存數據開始在應用程序所在Dalvik虛擬機堆內存中進行分配, 提高了回收機率和調試的可能性. 如果你在老版本中發現 bitmap 對象占用的內存大小始終一樣時, 切換設備到系統3.0或以上來進行調試.

              使用優化後的數據容器

              利用 Android 框架優化後的數據容器, 比如 SparseArray, SparseBooleanArray 和 LongSparseArray. 傳統的 HashMap 在內存上的實現十分的低效因為它需要為 map 中每一項在內存中建立映射關系. 另外, SparseArray類非常高效因為它避免系統中需要自動封箱(autobox)的key和有些值.

              知道內存的開銷

              在你設計應用各個階段都要很謹慎的考慮所使用的語言和庫帶來的內存上的成本和開銷. 通常情況下, 表面上看起來無害的會帶來巨大的開銷, 下面在例子說明:

              • 當枚舉(enum)成為靜態常量時超過正常兩倍以上的內存開銷, 在 android 中你需要嚴格避免使用枚舉
              • java 中的每個類(包含匿名內部類)大約使用500個字節
              • 每個類實例在運行內存(RAM)中占用12到16個字節
              • 在 hashmap 中放入單項數據時, 需要為額外的其它項分配內存, 總共占用32個字節

                使用很多的不必要類和對象時, 增加了分析堆內存問題的復雜度.

                當心抽象代碼

                通常來說, 使用簡單的抽象是一種好的編程習慣, 因為一定程度上的抽象可以提供代碼的伸縮性和可維護性. 然而抽象會帶來非常顯著的開銷: 需要執行更多的代碼, 需要更長時間和更多的運行內存把代碼映射到內存中, 所以如果抽象沒有帶來顯著的效果就盡量避免.

                使用納米 Protocol buffers 作為序列化數據

                Protocol Buffers 是 Google 公司開發的一種數據描述語言,類似於XML能夠將結構化數據序列化. 但是它更小, 更快, 更簡單. 如果你決定使用它作為你的數據, 你必須在你的客戶端代碼中一直使用納米 protocol buffer, 因為正常的 protocol buffer 會產生極其冗余的代碼, 在你的應用生會引起很多問題: 增加了使用的內存, 增加了apk文件的大小, 執行速度較慢以及會快速的把一些限定符號打入 dex 包中.

                盡量避免使用依賴注入框架

                使用像 Guice 和 RoboGuice 依賴注入框架會有很大的吸引力, 因為它使我們可以寫一些更簡單的代碼和提供自適應的環境用來進行有用的測試和進行其它配置的更改. 然而這些框架通過注解的方式掃描你的代碼來執行一系列的初始化, 但是這些也會把一些我們不需要的大量的代碼映射到內存中. 被映射後的數據會被分配到干淨的內存中, 放入到內存中後很長一段時間都不會使用, 這樣造成了內存大量的浪費.

                謹慎使用外部依賴庫

                許多的外部依賴庫往往不是在移動環境下寫出來的, 這樣當在移動使用中使用這些庫時就會非常低效. 所以當你決定使用一個外部庫時, 你就要承擔為優化為移動應用外部庫帶來的移植問題和維護負擔. 在項目計劃前期就要分析該類庫的授權條件, 代碼量, 內存的占用再來決定是否使用該庫.

                甚至據說專門設計用於 android 的庫也有潛在的風險, 因為每個庫做的事情都不一樣. 例如, 一個庫可能使用的是 nano protobuf 另外一個庫使用的是 micro protobuf, 現在在你的應用中有兩個不同 protobuf 的實現. 這將會有不同的日志, 分析, 圖片加載框架, 緩存, 等所有你不可預知的事情的發生. Proguard 不會保存你的這些, 因為所有低級別的 api 依賴需要你依賴的庫裡所包含的特征. 當你使用從外部庫繼承的 activity 時尤其會成為一個問題(因為這往往產生大量的依賴). 庫要使用反射(這是常見的因為你要花許多時間去調整ProGuard使它工作)等.

                也要小心不要陷入使用幾十個依賴庫去實現一兩個特性的陷阱; 不要引入大量不需要使用的代碼. 一天結束時, 當你沒有發現符合你要求的實現時, 最好的方式是創建一個屬於自己的實現.

                優化整體性能

                除了上述情況外, 還可以優化CPU的性能和用戶界面, 也會帶動內存的優化

                使用代碼混淆去掉不需要的代碼

                代碼混淆工具 ProGuard 通過去除沒有用的代碼和通過語義模糊來重命名類, 字段和方法來縮小, 優化和混淆你的代碼. 使用它能使你的代碼更簡潔, 更少量的RAM映射頁.

                使用簽名工具簽名apk文件

                如果構建apk後你沒有做後續的任何處理(包括根據你的證書進行簽名), 你必須運行 zipalign 工具為你的apk重新簽名, 如果不這樣做會導致你的應用使用更多的內存, 因為像資源這樣的東西不會再從apk中進行映射(mmap).

                注意:goole play store 不接受沒有簽名的apk

                分析你的內存使用情況

                使用adb shell dumpsys meminfo +包名 等工具來分析你的應用在各個生命周期的內存使用情況, 這個後續博文會有所體現.

                使用多進程

                一種更高級的技術能管理應用中的內存, 分離組件技術能把單進程內存劃分為多進程內存. 該技術一定要謹慎的使用並且大多數的應用都不會跑多進程, 因為如果你操作不當反而會浪費更多的內存而不是減少內存. 它主要用於後台和前台能各自負責不同業務的應用程序

                當你構建一個音樂播放器應用並且長時間從一個 service 中播放音樂時使用多進程處理對你的應用來說更恰當. 如果整個應用只有一個進程, 當前用戶卻在另外一個應用或服務中控制播放時, 卻為了播放音樂而運行著許多不相關的用戶界面會造成許多的內存浪費. 像這樣的應用可以分隔為兩個進程:一個進程負責 UI 工作, 另外一個則在後台服務中運行其它的工作.

                在各個應用的 manifest 文件中為各個組件申明 android:process 屬性就可以分隔為不同的進程.例如你可以指定你一運行的服務從主進程中分隔成一個新的進程來並取名為"background"(當然名字可以任意取).



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