本文為官網(wǎng)文檔的翻譯總結(jié)翠肘,原文如下:點(diǎn)擊此處
1.簡介
本文檔介紹了關(guān)于提高Android程序效率的一些建議幅慌。讀者應(yīng)當(dāng)將這些建議融入到編程的習(xí)慣當(dāng)中骗随。關(guān)于如何寫出高效的代碼芦倒,有以下兩條基本原則:
- 不要進(jìn)行沒有必要的工作
- 如果能夠避免艺挪,不要進(jìn)行內(nèi)存的管理分配。
當(dāng)你進(jìn)行Android app的微型優(yōu)化時(shí)兵扬,一件難題之一是讓你的app確保運(yùn)行在不同的硬件設(shè)備上麻裳。不同版本的虛擬機(jī)(VM)運(yùn)行在不同速度的處理器上。不僅僅是硬件設(shè)備的差異器钟,同一設(shè)備是否使用JIT[1]都會(huì)存在巨大的差異津坑。使用JIT設(shè)備上的高效代碼并不總是不使用JIT設(shè)備的高效代碼。
2.優(yōu)化建議
接下來將逐條介紹官方文檔中的優(yōu)化建議傲霸。
2.1避免創(chuàng)建不必要的對(duì)象(Avoid Creating Unnecessary Objects)
對(duì)象的創(chuàng)建永遠(yuǎn)都不是免費(fèi)的疆瑰。通常情況下帶線程管理池的垃圾收集器能夠使得臨時(shí)對(duì)象的管理更加廉價(jià)眉反。但是管理內(nèi)存總是比不管理內(nèi)存要昂貴。
當(dāng)你分配越來越多的對(duì)象時(shí)穆役,你將會(huì)迫于定期的垃圾收集機(jī)制寸五,使得用戶體驗(yàn)遇到一些小麻煩。在Android 2.3中介紹了并發(fā)垃圾收集器耿币,但是不必要的工作總是應(yīng)當(dāng)避免的梳杏。
因此你應(yīng)當(dāng)避免創(chuàng)建不必要的對(duì)象實(shí)例。以下的一些例子會(huì)有所幫助:
- 如果你有一個(gè)方法返回一個(gè)字符串淹接,并且你知道無論如何返回的結(jié)果總是會(huì)被追加到StringBuffer后面十性。代替創(chuàng)建一個(gè)短期的臨時(shí)對(duì)象,直接進(jìn)行追加塑悼。
- 當(dāng)從輸入數(shù)據(jù)集中取出字符串時(shí)劲适,盡量嘗試從原數(shù)據(jù)中返回子字符串,而不是創(chuàng)建一個(gè)拷貝拢肆。
一個(gè)更激進(jìn)的想法是减响,將多維數(shù)組分割成平行的單維數(shù)組。
- 一個(gè)int數(shù)組好于一個(gè)Integer數(shù)組郭怪。這也同樣可以概括為兩個(gè)平行的int數(shù)組比一個(gè)(int,int)數(shù)組更為有效支示。該結(jié)論適用于其他基本數(shù)據(jù)類型的組合。
- 如果你需要實(shí)現(xiàn)一個(gè)存儲(chǔ)元組(Foo,Bar)對(duì)象的容器鄙才,盡量使用兩個(gè)平行的數(shù)組Foo[]和Bar[]颂鸿,這樣會(huì)好于單個(gè)的自定義數(shù)組(Foo,Bar)。(例外情況是攒庵,當(dāng)你為其他代碼設(shè)計(jì)API時(shí)嘴纺,通常情況需要對(duì)速度做一些小的妥協(xié)從而獲得更好的API設(shè)計(jì)。但是在你自己的代碼中浓冒,你應(yīng)當(dāng)嘗試盡量使代碼盡可能高效栽渴。)
通常情況下,盡可能避免創(chuàng)建短期臨時(shí)的對(duì)象稳懒。創(chuàng)建的對(duì)象越少闲擦,意味著垃圾的收集頻率越少,這將直接影響到用戶體驗(yàn)场梆。
2.2考慮靜態(tài)方法
如果你不會(huì)訪問到對(duì)象的內(nèi)部域墅冷,那就把該方法變成static吧。這樣的調(diào)用將會(huì)提高15%~20%的速度或油。這也是一個(gè)好的慣例寞忿,因?yàn)槟隳軌騾^(qū)別該方法是否會(huì)修改方法內(nèi)對(duì)象的狀態(tài)。
2.3常量使用Static Final
考慮下面例子中的類頂部聲明:
static int intVal = 42;
static String strVal = "Hello, world!";
編譯器生成一個(gè)被稱作clinit的類初始化方法顶岸,這將會(huì)在該類第一次被使用時(shí)執(zhí)行腔彰。該方法為intVal存儲(chǔ)值32叫编,并在類文件字符串常量表中為strVal取出一個(gè)引用。當(dāng)這些值在稍后被提及時(shí)霹抛,它們將會(huì)通過域查找的方式被訪問宵溅。
我們可以使用final關(guān)鍵字進(jìn)行改進(jìn):
static final int intVal = 42;
static final String strVal = "Hello, world!";
這樣一來,該類不再需要clinit方法上炎,因?yàn)檫@些常量被放置在dex文件的靜態(tài)域初始化器中。涉及intVal的代碼將會(huì)直接指向整型值42雏搂,而strVal的訪問將會(huì)使用一個(gè)相對(duì)廉價(jià)的“字符串常量”指令來代替欲查找的方式藕施。
注:該優(yōu)化僅僅應(yīng)用于基本類型和String常量,而不是武斷地應(yīng)用在所有的類型中凸郑。無論如何裳食,盡可能使用static final是一個(gè)好習(xí)慣。
2.4避免內(nèi)部的get()/set()方法
在類似于C++這樣的原生語言芙沥,有一個(gè)常見的慣例是诲祸,使用get方法(i=getCount())代替直接進(jìn)入域(i=count)。對(duì)于C++來說這是一個(gè)非常好的習(xí)慣而昨,并常常在其他面向?qū)ο笳Z言中也是如此救氯,例如C#和JAVA,因?yàn)榫幾g器通常會(huì)內(nèi)聯(lián)訪問歌憨,如果你想要對(duì)成員的訪問進(jìn)行限制着憨,你可以在任何時(shí)候這樣去編寫代碼。
但是务嫡,這一點(diǎn)對(duì)于Android來說并不是很好甲抖。這樣的方法調(diào)用是很昂貴,甚至超過上文所說的域查找心铃。遵循常見的面向?qū)ο缶幊虘T例准谚,使用get/set的公共接口固然是合理的,但是在類的內(nèi)部你應(yīng)當(dāng)總是直接訪問成員變量去扣。
沒有JIT的情況下柱衔,直接訪問會(huì)比瑣碎的get方法調(diào)用快3倍左右。使用JIT的情況下厅篓,直接訪問會(huì)比調(diào)用get方法快7倍左右秀存。
請(qǐng)注意,如果你使用ProGuard羽氮,那么直接訪問成員將會(huì)更加受益或链,因?yàn)镻roGuard能夠進(jìn)行內(nèi)聯(lián)訪問。
2.5使用增強(qiáng)型For循環(huán)語法
增強(qiáng)型for循環(huán)(有時(shí)也被稱為“for-each”循環(huán))能夠用于數(shù)組档押,以及實(shí)現(xiàn)Iterable接口的數(shù)據(jù)集合中澳盐。對(duì)于ArrayList祈纯,使用手寫的for循環(huán)能比使用增強(qiáng)型for循環(huán)快3倍左右(無論是否使用JIT)。但是對(duì)其他的集合而言叼耙,增強(qiáng)型for循環(huán)是一種清晰的迭代器用法腕窥。
對(duì)于數(shù)組的迭代,有如下幾種可選項(xiàng):
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()是最慢的筛婉。因?yàn)镴IT并不能優(yōu)化每次循環(huán)中獲取數(shù)組長度的成本簇爆。
- one()會(huì)較快一些。它使用局部變量獲得每一項(xiàng)傳入?yún)?shù)爽撒,避免了查找的成本入蛆。一次性獲取數(shù)組的長度會(huì)使得性能收益。
- 不使用JIT的情況下硕勿,two()是最快的哨毁。在不使用JIT的情況下,性能則與one()難分伯仲源武。該方法中使用了增強(qiáng)型for循環(huán)扼褪。
因此,默認(rèn)情況下粱栖,你應(yīng)當(dāng)使用增強(qiáng)型for循環(huán)话浇。但是遇到ArrayList時(shí),請(qǐng)考慮手寫循環(huán)的方式闹究。
2.6考慮使用包級(jí)訪問來代替私有內(nèi)聯(lián)類的訪問
考慮如下類的定義:
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);
}
}
這里的關(guān)鍵在于凳枝,我們定義了一個(gè)私有內(nèi)聯(lián)類(Foo$Inner),該內(nèi)部類中直接訪問了外部類中的私有方法和私有成員變量跋核。這是合法的岖瑰,該代碼的運(yùn)行結(jié)果將會(huì)打印出“Value is 27”,這也是我們所期望的砂代。
問題在于蹋订,虛擬機(jī)認(rèn)為直接從Foo$Inner中訪問Foo的私有成員是非法的,因?yàn)镕oo和Foo$Inner是兩個(gè)不同的類刻伊,即使Java語言允許一個(gè)內(nèi)部類可以訪問外部類的私有成員露戒。為了解決該問題,編譯器生成了一組方法:
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
當(dāng)內(nèi)部類需要訪問外部類中的mValue或調(diào)用外部類的doStuff()時(shí)捶箱,內(nèi)部類代碼會(huì)調(diào)用這些靜態(tài)方法智什。這意味著上述代碼確實(shí)可以歸結(jié)為內(nèi)部類訪問外部私有成員的方法。前文中我們討論過get/set這樣的方法是慢于直接訪問成員的丁屎。所以這是一個(gè)導(dǎo)致隱形性能損失的確切案例荠锭。
如果你在性能要求較高的地方使用這樣的代碼,你可以通過聲明被內(nèi)部類訪問的方法和變量來進(jìn)行包級(jí)訪問晨川,而不是私有成員訪問证九。不幸的是删豺,這意味著成員能夠直接被同一包下的其他類所訪問,因此在公共API中愧怜,你不應(yīng)該這么做呀页。
2.7避免使用Float
在Android設(shè)備中,float比int慢兩倍左右拥坛。
就速度而言蓬蝶,float和double在當(dāng)今的絕大多數(shù)硬件設(shè)備中并沒有什么區(qū)別。至于空間方面猜惋,double是float的兩倍疾党。和桌面設(shè)備一樣,不考慮內(nèi)存空間惨奕,你應(yīng)當(dāng)優(yōu)先選擇double,都不是float竭钝。
2.8使用庫
除了常見的優(yōu)先使用類庫而不是自己重復(fù)造輪子的原因之外梨撞,請(qǐng)記住系統(tǒng)能夠使用匯編語言自由地替換類庫的方法調(diào)用,這可能會(huì)比使用JIT所能夠做的最佳代碼還要好香罐。一個(gè)典型的案例是String.indexOf()和相關(guān)的API卧波。Dalvik虛擬機(jī)會(huì)使用內(nèi)部的原有代碼進(jìn)行替換。類似地庇茫,在使用JIT的Nexus One上港粱,System.arraycopy()方法會(huì)比手寫的循環(huán)快9倍左右。
2.9謹(jǐn)慎使用原生方法(Native Methods)
使用原生語言的NDK開發(fā)Android應(yīng)用并不一定比使用JAVA編程更高效旦签。比如查坪,原生語言和JAVA之間的轉(zhuǎn)化需要一定的成本,并且JIT無法對(duì)這兩邊進(jìn)行優(yōu)化宁炫。如果你正在使用原生資源,這很顯然比直接使用資源集要困難的多。你需要為你想要執(zhí)行的每一個(gè)處進(jìn)行編譯恳不。你可能甚至不得不編譯多個(gè)版本吴叶,比如為G1的ARM處理器進(jìn)行編譯的版本并不能完全利用Nexus One的ARM處理器,并且為Nexus One的ARM處理器編譯的版本不能運(yùn)行在G1上面竿秆。
原生代碼主要用于启摄,當(dāng)你已經(jīng)持有原生的代碼庫,并想把它導(dǎo)入Android中幽钢,而并不是用于提高Java所編寫部分的速度歉备。
2.10性能神話
在不使用JIT的設(shè)備中,通過具體類型的變量調(diào)用方法比調(diào)用接口會(huì)更高效匪燕。(例如威创,調(diào)用HashMap map的方法會(huì)比調(diào)用Map map的方法更高效落午,即使在兩者中map都是HashMap)。這并不是指會(huì)造成2倍的速度差異肚豺,實(shí)際的差異更接近于6%溃斋。此外,JIT使得二者間性能的差異幾乎難以分辨吸申。
在不使用JIT的設(shè)備中梗劫,緩存會(huì)比重復(fù)訪問變量快20%左右。使用JIT時(shí)二者的成本幾乎是相同的截碴。所以并不值得進(jìn)行優(yōu)化梳侨,除非你想讓你的代碼更加易讀。
2.11總是量化
在開始優(yōu)化之前日丹,請(qǐng)確實(shí)你是真的有一個(gè)需要解決的問題走哺。確定你能清楚地量化現(xiàn)有的性能,否則的話你無法估量各種優(yōu)化方式對(duì)性能的影響哲虾。
3.總結(jié)
最后丙躏,再將本文檔所闡述的優(yōu)化建議概述如下:
- 避免多余對(duì)象的創(chuàng)建
- 使用靜態(tài)方法定義不會(huì)訪問到對(duì)象內(nèi)部的函數(shù)
- 使用Static Final定義常量
- 避免在類內(nèi)部使用get/set方法
- ArrayList不使用增強(qiáng)型for循環(huán)
- 使用包級(jí)訪問代替私有內(nèi)聯(lián)類的訪問
- 使用double代替float
- 優(yōu)先使用第三方類庫而不是重復(fù)造輪子
- 謹(jǐn)慎使用NDK
- 不要執(zhí)迷于性能神話
- 量化性能以及優(yōu)化方法性能的量化,從而選擇合適的優(yōu)化方式
-
JIT : Just In Time Compiler 又譯及時(shí)編譯束凑、實(shí)時(shí)編譯晒旅,動(dòng)態(tài)編譯的一種形式,是一種提高程序運(yùn)行效率的方法汪诉。 ?