1. 程式人生 > >android app效能優化大彙總

android app效能優化大彙總

效能指標:

(1)使用流暢度:

   圖片處理器每秒重新整理的幀數(FPS),可用來指示頁面是否平滑的渲染。高的幀率可以得到更流暢,更逼真的動畫,不過幀率達到60fps以上,人眼主觀感受到的差別就不大了。所以以60fps作為衡量標準,即要求每一幀重新整理的時間小於16ms,這樣才能保證滑動中平滑的流暢度。

(2)記憶體使用情況: 

  在android系統中,每個APP程序除了同其他程序共享(shared dirty)外,還獨用私有記憶體(private dirty),通常我們使用PSS(=私有記憶體+比例分配共享記憶體)來衡量一個APP的記憶體開銷。移動裝置的記憶體資源是非常有限,為每個APP程序分配的私有記憶體也是有限制。一方面我們要合理的申請記憶體使用,以免導致頻繁的GC影響效能和大物件申請發生記憶體溢位;另一方面,我們要及時釋放記憶體,以免發生記憶體洩漏。

(3)電量使用情況:

  相對於PC來說,移動裝置的電池電量是非常有限的,保持持久的續航能力尤為重要。另外,android的很多特性都比較耗電(如螢幕,GPS,sensor感測器,喚醒機制,CPU,連網等的使用),我們必須要慎重檢查APP的電量使用,以免導致使用者手機耗電發熱,帶來不良體驗。

(4)流量使用情況:

  目前的網路型別包含2G\3G\4G\wifi,其中還有不同運營商的區分,我們在APP的使用中經常遇到大資源,重複請求,呼叫響應慢,呼叫失敗等各種情況。在不同的網路型別之下,我們不僅要控制流量使用,還需要加快請求的響應。

原理以及優化技術要點

原理及相關知識介紹請參考google官方的教程:

具體優化方案:

 程式碼編寫風格細節對效能的影響:

1使用優化後的資料容器:

請使用 Andorid 框架中優化過的資料容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray。類似於 HashMap 這一類的容器的效率不是很高,因為在每個 Map 中對於每一次的存放資料,他都需要獨立一個單獨的 Entry 物件進行傳芳。而 SparseArray 由於禁止系統自動封裝鍵值對,因此他更加有效率。並且你不需要擔心丟失掉原有資訊。

2儘量避免使用列舉型別:

列舉與靜態常量相比,通常會消耗兩倍的記憶體資源。

3、避免建立不必要的物件:(特別是在onDraw類似的函式裡)

建立太多的物件會造成效能低下,這誰都知道,可是為什麼呢?首先分配記憶體本身需要時間,其次虛擬機器執行時堆記憶體使用量是有上限的,當使用量到達一定程度時會觸發垃圾回收,垃圾回收會使得執行緒甚至是整個程序暫停執行。可想而知,如果有物件頻繁的建立和銷燬,或者記憶體使用率很高,就會造成應用程式嚴重卡頓。(記憶體抖動)

4、合理使用static成員:

主要有三點需要掌握:
(1)如果一個方法不需要操作執行時的動態變數和方法,那麼可以將方法設定為static的。
(2)常量欄位要宣告為“static final”,因為這樣常量會被存放在dex檔案的靜態欄位初始化器中被直接訪問,否則在執行時需要通過編譯時自動生成的一些函式來初始化。此規則只對基本型別和String型別有效。
(3)不要將檢視控制元件宣告為static,因為View物件會引用Activity物件,當Activity退出時其物件本身無法被銷燬,會造成記憶體溢位。

5、 使用for-each迴圈:

增強的For迴圈(也被稱為 for-each 迴圈)可以被用在實現了 Iterable 介面的 collections 以及陣列上。使用collection的時候,Iterator (迭代器,譯者注) 會被分配,用於for-each呼叫 hasNext() 和 next() 方法。使用ArrayList時,手寫的計數式for迴圈會快3倍(不管有沒有JIT),但是對於其他collection,增強的for-each迴圈寫法會和迭代器寫法的效率一樣。

請比較下面三種迴圈的方法:

複製程式碼
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()是最慢的,因為JIT沒有辦法對它進行優化。

  • one()稍微快些。

  • two() 在沒有做JIT時是最快的,可是如果經過JIT之後,與方法one()是差不多一樣快的。它使用了增強的迴圈方法for-each。

所以請儘量使用for-each的方法,但是對於ArrayList,請使用方法one()。

你還可以參考 Josh Bloch 的 《Effective Java》這本書的第46條

6、避免使用浮點型別:

經驗之談,所在Android裝置中浮點型大概比整型資料處理速度慢兩倍,以如果整型可以解決的問題就不要用浮點型。另外,一些處理器有硬體乘法但是沒有除法,這種情況下除法和取模運算是用軟體實現的。為了提高效率,在寫運算式時可以考慮將一些除法操作直接改寫為乘法實現,例如將“x / 2”改寫為“x * 0.5”。

7、瞭解並使用庫函式:

Java標準庫和Android Framework中包含了大量高效且健壯的庫函式,很多函式還採用了native實現,通常情況下比我們用Java實現同樣功能的程式碼的效率要高很多。所以善於使用系統庫函式可以節省開發時間,並且也不容易出錯。

 8、避免依賴注入框架:

使用類似於 Guice 和 RoboGuice 的依賴注射框架,或許會使你的程式碼變得更加漂亮,因為他們能夠減少你需要寫的程式碼,並且為測試或者在其他條件改變的情況下,提供一種自適應的環境。但是,這些框架在初始化的時候會因為註釋而消耗大量的工作在掃描你的程式碼上,這會讓你的程式碼在進行記憶體對映的時候花費更多的資源。雖然這些記憶體能夠被 Android 進行回收,但是等待整個分頁被釋放需要很長一段時間。

9、小心使用外部依賴包:

很多依賴包都不是專門為了移動環境或者移動客戶端寫的。如果你決定使用一個外部依賴包,你應該提前明白你需要為了將它移植到移動端而消耗花費大量的時間和工作量。請在使用外部依賴包得時候提前分析他的程式碼和記憶體佔用

即使依賴包是為了 Android 而設計的,但是這也有潛伏的危險,因為每一個包都做著不同的工作。例如,有一個依賴包使用納米級的 protobufs 但是別的包使用微米級的 protobufs.那麼現在在你的應用中就有兩套 protobuf 的標準了。這會在你記錄資料,分析資料,載入影象,快取,或者其他任何可能的情況下發生你不希望發生的事情。ProGuard 無法在這裡幫助你,因為他們都是你所依賴包的底層實現,。當你使用從別的依賴包(他可能繼承了很多的依賴包)裡繼承的 Activity 時,這個問題變得尤其嚴重,當你使用反射以及幹別的事情的時候

請注意不要落入一個依賴包的陷阱,你不希望引入一大片你根本不會使用到的程式碼。如果你無法找到一種已經實現的邏輯來完全滿足你的需求,那麼你儘量建立一個自己的實現方式。

10、避免內部的Getters/Setters:

像C++等native language,通常使用getters(i = getCount())而不是直接訪問變數(i =mCount)。這是編寫C++的一種優秀習慣,而且通常也被其他面向物件的語言所採用,例如C#與Java,因為編譯器通常會做inline訪問,而且 你需要限制或者除錯變數,你可以在任何時候在getter/setter裡面新增程式碼。然而,在Android上,這是一個糟糕的寫法。虛擬函式的呼叫比起直接訪問變數要耗費更多。在面向物件程式設計中,將getter和setting暴露給公用介面是合理的,但在類內部應該僅僅使用域直接訪問。在沒有JIT(Just In Time Compiler)時,直接訪問變數的速度是呼叫getter的3倍。有JIT時,直接訪問變數的速度是通過getter訪問的7倍。請注意,如果你使用 ProGuard , 你可以獲得同樣的效果,因為ProGuard可以為你inline accessors.