提升Android應用性能的小貼士
本文主要介紹了提升Android應用性能的一些小方法,組合使用這些方法往往能夠改善我們所開發的應用的性能表現。但是,我們應優先關注應用所選用的數據結構和算法,切勿本末倒置。想寫出高效代碼,有兩個基本原則:
- 不要做本不必做的工作;
- 不要分配不必要的內存。
以上兩個基本原則很好理解,第一條是讓我們盡可能降低應用的時間復雜度,第二條是讓我們盡可能降低應用的空間復雜度。下文的各個建議實際上也是圍繞著這兩個基本原則展開的。
當我們對Android應用做優化時,我們必須面對的一個最蛋疼的問題之一,便是我們的應用將會在各種類型的設備上運行,這意味著它們往往有著不同的硬件架構。不同版本的Android虛擬機在各種硬件架構上會以不同的速率運行。而我們大多數情況下無法簡單地斷定,X設備的速度會是Y設備的多少倍。我們在模擬器上的測試結果,對我們做真機上的性能評估又往往沒什么幫助。
更令人蛋疼的是,支持即時編譯(JIT)的設備和不支持即時編譯的設備又有著巨大的差異——支持即時編譯的設備上的最優代碼,對于不支持即時編譯的設備來說,不總是最優的。
要確保你的應用在各種各樣的設備上都有著良好的性能表現,要確保你的代碼在各種情況下都是高效的,這需要我們下一番功夫來對代碼進行優化。以下是一些性能優化的建議。
避免創建不必要的對象
創建對象總是會產生代價的。一個支持線程級分配池的分代垃圾回收器可以使得一次內存分配所花的代價更少,但是再少也少不過“根本不進行內存分配”。
當你的應用中創建了足夠多的對象,便會導致垃圾回收經常發生,這可能會帶來用戶界面的“卡頓”。因此,你應該避免創建不必要的對象,比如:若你有一個返回一個String的方法,并且你確信返回結果總是會被添加到一個StringBuffer(StringBuilder)中,那么應對該方法做出修改,讓它不再返回String,而是直接把結果添加到相應的StringBuffer(StringBuilder)中,這樣一來便可以避免創建一個臨時的String對象。
還有一個更“激進”的建議是把多維數組都“展開”成一維數組:
- 多個int數組要比一個int[]對象數組更加高效,這對于其他原始數據類型(primitive data type)同樣適用;
- 若你需要一個存儲(Foo, Bar)元組的容器,要記得使用Foo數組和Bar數組要比使用(Foo, Bar)對象數組高效的多(例外情況是當你設計API時,為了一個良好的API設計,我們應該在性能上做出小小的妥協);
對于本條建議,概括起來就是 盡可能避免創建短時存活的對象 。
盡量使用static而不是virtual
若你不需要訪問對象的字段,那么請定義你的方法為靜態(static)方法而不是虛(virtual)方法,這會帶來15%到20%的性能提升。這同樣也是一個好的編程實踐,因為如此一來我們可以清楚的知道,調用該方法不會改變對象的狀態。
對于常量使用static final
考慮下面的聲明:
static int intVal = 42;
static String strVal = "Hello, world!";
當聲明了以上語句的類被初次使用時,編譯器會為之創建一個名為“<clinit>“的類初始化器方法。這個方法會將42存儲在intVal中,并將字符串“Hello, world!”的引用存儲在strVal中,稍后我們引用到這兩個變量時,便會通過“字段查找(field lookup)”來訪問它們。
然而通過為以上兩句變量聲明加上“final”關鍵字,那么intVal和strVal就會變為兩個常量,訪問它們時便無需通過字段查找,這樣會提升性能。
注意:這個優化建議只對String和原始數據類型有效。
使用增強版循環語法
增強版循環語法指的就是for-each寫法,它可以用于數組以及實現了Iterable接口的集合類。for-each只有對ArrayList使用時,要比常規的for循環慢,對于其他情況,for-each與常規for速度相差無幾。由于for-each能夠簡化代碼編寫,我們優先考慮使用它;只有我們使用ArrayList并且追求“極致性能”時,才應使用常規for循環。
對以下場景考慮使用包(package)而不是私有(private)
考慮下面的代碼:
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類的私有內部類Inner中訪問了Foo類的私有方法和私有字段,盡管這在Java語法中是合法的。但是對虛擬機來說,會將Foo$Inner和Foo視為兩個不同的類,所以虛擬機為了實現內部類對外圍類私有方法/字段的訪問,需要創建兩個充當“溝通紐帶”的方法,如下:
static int Foo.access$100(Foo foo) {
return foo.mValue;
}
static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
也就是說,內部類Inner要通過上面兩個方法來訪問外圍類的私有字段/方法,這顯然比直接字段訪問的開銷要大。這種情況下,我們可以考慮將mValue和doStuff()的可見性改為默認的包范圍。當然,我們不應該對公共API應用這一點。
避免使用浮點型
對于Android設備,使用浮點數要比整數大概慢兩倍;而雙精度浮點和單精度浮點在時間效率上相差無幾,只是前者會占用二倍于后者的存儲空間。還應該注意的是,有些Android設備在硬件層面上不支持除法。我們在開發中也要注意這一點。
學習并使用系統API
有時候對于某種業務邏輯,與自己實現相比,我們更應該優先使用SDK提供給我們的方法,因為系統提供給我們的實現往往更加高效。一個典型的例子是System.arrayCopy()方法要比我們手動用循環進行數組復制快9倍左右(在支持即時編譯的Nexus One設備上)。
可能無需進行的優化
我們先來考慮以下兩個方法:
void doWork(Map map);
void doWork(HashMap map);
在一個不支持即時編譯的設備上,第一個方法只比第二個方法慢一點(6%);而支持即時編譯的設備上,二者效率的差異就更小了。
我們再來考慮一下”重復訪問字段”和”把字段緩存為局部變量”所帶來的性能差異。在不支持即時編譯的設備上,緩存要比重復訪問快20%,而對于支持即時編譯的設備,兩者幾乎一樣快。
因此對于以上兩種情況,無需我們費心進行“優化”。
記得做性能測試
在你著手進行優化之前,確保你已經發現了性能問題,畢竟“過早優化是萬惡之源”。此外,還要確保你已經精確測試過應用現階段的性能表現,否則我們難以衡量優化工作的成效。我們可以使用SysTrace和TraceView來量化我們應用的性能表現。
來自:http://www.jianshu.com/p/c3b734323f01