Android 性能優化小技巧
說明:本文翻譯來源谷歌官網
非完全翻譯,按理解來的,水平有限,勿噴!
本文主要包括能夠細微改善所有APP性能,通過這些改進不能造成性能的翻天覆地變化。選擇正確的算法和數據結構纔是的優先考慮的事,但是這已經超過了本文檔的範圍。你應該使用這些小技巧培養你的編碼習慣,來創造高效的代碼。
這裏有兩條基本的規則去寫高效的代碼:
不要做不需要做的事;
不要分配你可以避免分配的內存;
最複雜的問題之一是當進行細微優化的你需要確保你的APP能夠運行在多種硬件上。運行在不同處理器上不同版本的虛擬機運行的速度不一樣。這不是一個簡單的問題你說“設備X比設備Y運行的快或慢”,需要測量你的結果在不同的設備。特殊的情況,在模擬器上的測量告訴你很少的性能差異在任何設備上。是否使用JIT即時有巨大的區別:有JIT的設備最好的代碼不一定是沒有JIT的設備上最好的代碼。
確保你的APP在不多種設備上運行良好,確保你的代碼是高效在各種層級,積極的優化你的APP性能。
避免創建不需要的對象
對象的創建從來都不是免費。垃圾回收和爲分配臨時對象的每個線程池使得分配代價更小,但是分配內存總是會比不分配內存代價更加的高。
當你分配對象在你的APP,你將強制進行週期性的垃圾回收,會造成卡頓的用戶體驗,併發的垃圾回收器在Android 2.3引入,但是不需要的工作還是應該被避免。
因此,你應該避避免創建對象你不需要的。下面有一些有用的例子:
假如你有個返回String,你知道這個結果總是被附加到一個StringBuffer上面,改變你的函數簽名和實現,讓函數直接附加而不是創建一個短生命週期的臨時對象。
當獲得額外的Strings從一個輸入數據集,努力的返回一個原始數據的子字符串,而不是產生一個複製。 你將創建一個新的字符串對象,但是它共享char[]字符(博主注:這裏是說明是字符保存在字符數組中)。
有一些更加徹底的想法就是拆分多維數組到對應的一維數組。
一個int類型的數組要比一個Integer對象的數組要更好,進一步的事實是兩個平行的數組更加的高效對比一個擁有(int,int)的數組。其它的基本數據類型也是類似的道理。
假如你需要實現一個容器用來存儲(Foo,Bar)元組對象,嘗試去記住平行的Foo[]和Bar[]數組是更好的。
通常來說,只要可以就應該避免創建短生命週期的臨時對象。更好的創建對象就意味着更少的頻次的垃圾回收,這對用戶體驗有直接的影響。
選擇Static而不是Virtual
加入你不要或者對象的成員域,使你的方法爲Static.調用將快15%-20%。這也是一個好的習慣,因爲你能通過方法簽名來告訴這個方法不能修改對象狀態。
使用Static Final 給常量 (博主注:很有用)
思考下面的聲明在頂級類。
static int intVal = 42;
static String strVal = "hello world!";
編譯器產生一個類的初始化方法叫做,這個方法將被執行,當類第一次被使用。這個方法存儲數值42到intVal,以及提取一個引用從類的字符串常量表給strVal。當這些數值之後被引用,它們能夠通過域查找來獲取。
我們能夠改善這個問題通過”final”關鍵字
static final int intVal = 42;
static final String strVal = "Hello, world!";
這個類不再需要方法,因爲這個常量進入在Dex文件中的靜態域初始化器。代碼引用intVal將直接使用整數42,並且獲取strVal使用相對消耗較低的“字符串常量”結果而不是域查找。
避免內部的Getters/Setters
在native語言像C++,使用(i==getCount())而不是(i = mCount)是一個好習慣。它是一個優秀的習慣對C++來說,也是慣例對C#和java來說。因爲編譯器能偶內聯這種獲取,假如你需要重構或者Debug域的獲取,您能夠在任何時候添加代碼。
但是,這是一個壞主意在Android上。虛方法的調用是昂貴的,比實例域的查找更昂貴。公共接口擁有getters和setters是合理的,但是在類的內部應該直接進行調用。
沒有JIT,直接域的獲取比調用一個不重要的getter快三倍。如果是擁有JIT,則快了7倍。
使用增強過的循環語法
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() 是最快的的
針對私有內部類,使用包可見權限而不是私用權限
考慮下面的一種定義:
public class Foo {
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}
private int mValue;
public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}
private void doStuff(int value) {
System.out.println("Value is " + value);
}
}
這種寫法是合理的,結果是預期的。
問題是虛擬機認爲直接獲取從Foo
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
不管什麼時候,只要內部類獲取外部類mValue或者調用方法外部類的doStuff(),內部方法將調用以上靜態方法。這意味着就本質而言,上述方式中你獲取成員變量是通過getter方法。更早之前我們談論過,爲什麼getter要直接獲取域要慢。
避免使用浮點類型
根據經驗來說,在安卓設備上,浮點類型要比整數類型慢兩倍。
在速度方面,float和double沒有區別在大多數的現代硬件。控件方面,double要大兩倍。對桌面機器而言,空間不是問題,你應該選擇double而不是float.
瞭解並使用Libraries
使用更多的Library代碼,像 String.indexOf(),System.arraycopy().
謹慎的使用natvie 方法
使用已有的natvie代碼來對接Android,native是有用的,不要爲了速度優化而是用native
性能謬見
沒有JIT的設備(Android2.2版本提供了JIT機制提升性能),具體的類型比接口類型高效。
但是JIT的設備上兩者沒有太大的差距
擁有JIT的設備上會緩存域的獲取。擁有JIT的設備對域變量和本地變量獲取擁有相同的耗費,所以這不值得優化,除非是爲了代碼的可讀性。
持續的測量
在你開始優化之前,確保你有需要解決的問題。確保你能夠精確的測量你已經存在的程序的性能,否則你沒法測量使用常識方案後性能改善。
使用TraceView工具是有用的。根據TraceView的數據建議改善代碼後,不使用TraceView ,確認代碼的性能有所改善。