關(guān)于android性能,內(nèi)存優(yōu)化
看了些資料整理了下,安卓的性能和內(nèi)存優(yōu)化的一些方法和注意事項(xiàng)。分享出來。
隨著技術(shù)的發(fā)展,智能手機(jī)硬件配置越來越高,可是它和現(xiàn)在的PC相比,其運(yùn)算能力,續(xù)航能力,存儲空間等都還是受到很大的限制,同時(shí)用戶對手機(jī)的體驗(yàn)要求遠(yuǎn)遠(yuǎn)高于PC的桌面應(yīng)用程序。以上理由,足以需要開發(fā)人員更加專心去實(shí)現(xiàn)和優(yōu)化你的代碼了。選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)永遠(yuǎn)是開發(fā)人員最先應(yīng)該考慮的事情。同時(shí),我們應(yīng)該時(shí)刻牢記,寫出高效代碼的兩條基本的原則:(1)不要做不必要的事;(2)不要分配不必要的內(nèi)存。
我從去年開始接觸Android開發(fā),以下結(jié)合自己的一點(diǎn)項(xiàng)目經(jīng)驗(yàn),同時(shí)參考了Google的優(yōu)化文檔和網(wǎng)上的諸多技術(shù)大牛給出的意見,整理出這份文檔。
1.內(nèi)存優(yōu)化
Android系統(tǒng)對每個(gè)軟件所能使用的RAM空間進(jìn)行了限制(如:Nexusone對每個(gè)軟件的內(nèi)存限制是24M),同時(shí)Java語言本身比較消耗內(nèi)存,dalvik虛擬機(jī)也要占用一定的內(nèi)存空間,所以合理使用內(nèi)存,彰顯出一個(gè)程序員的素質(zhì)和技能。
1)了解JIT
即時(shí)編譯(Just-in-timeCompilation,JIT),又稱動態(tài)轉(zhuǎn)譯(DynamicTranslation),是一種通過在運(yùn)行時(shí)將字節(jié)碼翻譯為機(jī)器碼,從而改善字節(jié)碼編譯語言性能的技術(shù)。即時(shí)編譯前期的兩個(gè)運(yùn)行時(shí)理論是字節(jié)碼編譯和動態(tài)編譯。Android原來Dalvik虛擬機(jī)是作為一種解釋器實(shí)現(xiàn),新版(Android2.2+)將換成JIT編譯器實(shí)現(xiàn)。性能測試顯示,在多項(xiàng)測試中新版本比舊版本提升了大約6倍。
詳細(xì)請參考http://hi.baidu.com/cool_parkour/blog/item/2802b01586e22cd8a6ef3f6b.html
2)避免創(chuàng)建不必要的對象
就像世界上沒有免費(fèi)的午餐,世界上也沒有免費(fèi)的對象。雖然gc為每個(gè)線程都建立了臨時(shí)對象池,可以使創(chuàng)建對象的代價(jià)變得小一些,但是分配內(nèi)存永遠(yuǎn)都比不分配內(nèi)存的代價(jià)大。如果你在用戶界面循環(huán)中分配對象內(nèi)存,就會引發(fā)周期性的垃圾回收,用戶就會覺得界面像打嗝一樣一頓一頓的。所以,除非必要,應(yīng)盡量避免盡力對象的實(shí)例。下面的例子將幫助你理解這條原則:當(dāng)你從用戶輸入的數(shù)據(jù)中截取一段字符串時(shí),盡量使用substring函數(shù)取得原始數(shù)據(jù)的一個(gè)子串,而不是為子串另外建立一份拷貝。這樣你就有一個(gè)新的String對象,它與原始數(shù)據(jù)共享一個(gè)char數(shù)組。如果你有一個(gè)函數(shù)返回一個(gè)String對象,而你確切的知道這個(gè)字符串會被附加到一個(gè)StringBuffer,那么,請改變這個(gè)函數(shù)的參數(shù)和實(shí)現(xiàn)方式,直接把結(jié)果附加到StringBuffer中,而不要再建立一個(gè)短命的臨時(shí)對象。
一個(gè)更極端的例子是,把多維數(shù)組分成多個(gè)一維數(shù)組:
int數(shù)組比Integer數(shù)組好,這也概括了一個(gè)基本事實(shí),兩個(gè)平行的int數(shù)組比(int,int)對象數(shù)組性能要好很多。同理,這試用于所有基本類型的組合。如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個(gè)單獨(dú)的Foo[]數(shù)組和Bar[]數(shù)組,一定比(Foo,Bar)數(shù)組效率更高。(也有例外的情況,就是當(dāng)你建立一個(gè)API,讓別人調(diào)用它的時(shí)候。這時(shí)候你要注重對API接口的設(shè)計(jì)而犧牲一點(diǎn)兒速度。當(dāng)然在API的內(nèi)部,你仍要盡可能的提高代碼的效率)
總體來說,就是避免創(chuàng)建短命的臨時(shí)對象。減少對象的創(chuàng)建就能減少垃圾收集,進(jìn)而減少對用戶體驗(yàn)的影響。
3)???????靜態(tài)方法代替虛擬方法
如果不需要訪問某對象的字段,將方法設(shè)置為靜態(tài),調(diào)用會加速15%到20%。這也是一種好的做法,因?yàn)槟憧梢詮姆椒暶髦锌闯稣{(diào)用該方法不需要更新此對象的狀態(tài)。
4)???????避免內(nèi)部Getters/Setters
在源生語言像C++中,通常做法是用Getters(i=getCount())代替直接字段訪問(i=mCount)。這是C++中一個(gè)好的習(xí)慣,因?yàn)榫幾g器會內(nèi)聯(lián)這些訪問,并且如果需要約束或者調(diào)試這些域的訪問,你可以在任何時(shí)間添加代碼。而在Android中,這不是一個(gè)好的做法。虛方法調(diào)用的代價(jià)比直接字段訪問高昂許多。通常根據(jù)面向?qū)ο笳Z言的實(shí)踐,在公共接口中使用Getters和Setters是有道理的,但在一個(gè)字段經(jīng)常被訪問的類中宜采用直接訪問。無JIT時(shí),直接字段訪問大約比調(diào)用getter訪問快3倍。有JIT時(shí)(直接訪問字段開銷等同于局部變量訪問),要快7倍。
5)???????將成員緩存到本地
訪問成員變量比訪問本地變量慢得多,下面一段代碼:
for(inti =0; i
dumpItem(this.mItems);
}
最好改成這樣:
intcount =this.mCount;
Item[] items =this.mItems;
for(inti =0; i < count; i++)? {
dumpItems(items);
}
另一個(gè)相似的原則是:永遠(yuǎn)不要在for的第二個(gè)條件中調(diào)用任何方法。如下面方法所示,在每次循環(huán)的時(shí)候都會調(diào)用getCount()方法,這樣做比你在一個(gè)int先把結(jié)果保存起來開銷大很多。
for(inti =0; i
dumpItems(this.getItem(i));
}
同樣如果你要多次訪問一個(gè)變量,也最好先為它建立一個(gè)本地變量,例如:
protectedvoiddrawHorizontalScrollBar(Canvas canvas,intwidth,intheight) {
if(isHorizontalScrollBarEnabled()) {
intsize = mScrollBar.getSize(false);
if(size <=0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false);
mScrollBar.draw(canvas);
}
}
這里有4次訪問成員變量mScrollBar,如果將它緩存到本地,4次成員變量訪問就會變成4次效率更高的棧變量訪問。
另外就是方法的參數(shù)與本地變量的效率相同。
1)???????對常量使用static final修飾符
讓我們來看看這兩段在類前面的聲明:
staticintintVal =42;
staticString strVal ="Hello, world!";
必以其會生成一個(gè)叫做clinit的初始化類的方法,當(dāng)類第一次被使用的時(shí)候這個(gè)方法會被執(zhí)行。方法會將42賦給intVal,然后把一個(gè)指向類中常量表 的引用賦給strVal。當(dāng)以后要用到這些值的時(shí)候,會在成員變量表中查找到他們。 下面我們做些改進(jìn),使用“final”關(guān)鍵字:
static final int intVal = 42;static final String strVal = "Hello, world!";
現(xiàn)在,類不再需要clinit方法,因?yàn)樵诔蓡T變量初始化的時(shí)候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個(gè)字符串常量,而不是使用成員變量。
將一個(gè)方法或類聲明為final不會帶來性能的提升,但是會幫助編譯器優(yōu)化代碼。舉例說,如果編譯器知道一個(gè)getter方法不會被重載,那么編譯器會對其采用內(nèi)聯(lián)調(diào)用。
你也可以將本地變量聲明為final,同樣,這也不會帶來性能的提升。使用“final”只能使本地變量看起來更清晰些(但是也有些時(shí)候這是必須的,比如在使用匿名內(nèi)部類的時(shí)候)。
2)???????使用改進(jìn)的For循環(huán)語法
改進(jìn)for循環(huán)(有時(shí)被稱為for-each循環(huán))能夠用于實(shí)現(xiàn)了iterable接口的集合類及數(shù)組中。在集合類中,迭代器讓接口調(diào)用 hasNext()和next()方法。在ArrayList中,手寫的計(jì)數(shù)循環(huán)迭代要快3倍(無論有沒有JIT),但其他集合類中,改進(jìn)的for循環(huán)語 法和迭代器具有相同的效率。下面展示集中訪問數(shù)組的方法:
staticclassFoo {
intmSplat;
}
Foo[] mArray = ...
publicvoidzero() {
intsum =0;
for(inti =0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}
publicvoidone() {
intsum =0;
Foo[] localArray = mArray;
intlen = localArray.length;
for(inti =0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}
publicvoidtwo() {
intsum =0;
for(Foo a : mArray) {
sum += a.mSplat;
}
}
}
在zero()中,每次循環(huán)都會訪問兩次靜態(tài)成員變量,取得一次數(shù)組的長度。
在one()中,將所有成員變量存儲到本地變量。
two()使用了在java1.5中引入的foreach語法。編譯器會將對數(shù)組的引用和數(shù)組的長度保存到本地變量中,這對訪問數(shù)組元素非常好。 但是編譯器還會在每次循環(huán)中產(chǎn)生一個(gè)額外的對本地變量的存儲操作(對變量a的存?。┻@樣會比one()多出4個(gè)字節(jié),速度要稍微慢一些。
3)???????避免使用浮點(diǎn)數(shù)
通常的經(jīng)驗(yàn)是,在Android設(shè)備中,浮點(diǎn)數(shù)會比整型慢兩倍,在缺少FPU和JIT的G1上對比有FPU和JIT的Nexus One中確實(shí)如此(兩種設(shè)備間算術(shù)運(yùn)算的絕對速度差大約是10倍)從速度方面說,在現(xiàn)代硬件上,float和double之間沒有任何不同。更廣泛的講,double大2倍。在臺式機(jī)上,由于不存在空間問題,double的優(yōu)先級高于float。但即使是整型,有的芯片擁有硬件乘法,卻缺少除法。這種情況下,整型除法和求模運(yùn)算是通過軟件實(shí)現(xiàn)的,就像當(dāng)你設(shè)計(jì)Hash表,或是做大量的算術(shù)那樣,例如a/2可以換成a*0.5。
4)???????了解并使用類庫
選擇Library中的代碼而非自己重寫,除了通常的那些原因外,考慮到系統(tǒng)空閑時(shí)會用匯編代碼調(diào)用來替代library方法,這可能比JIT中生成的等價(jià)的最好的Java代碼還要好。
i.??? 當(dāng)你在處理字串的時(shí)候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊實(shí)現(xiàn)的方法。這些方法都是使用C/C++實(shí)現(xiàn)的,比起Java循環(huán)快10到100倍。
ii.??? System.arraycopy方法在有JIT的Nexus One上,自行編碼的循環(huán)快9倍。
iii.??? android.text.format包下的Formatter類,提供了IP地址轉(zhuǎn)換、文件大小轉(zhuǎn)換等方法;DateFormat類,提供了各種時(shí)間轉(zhuǎn)換,都是非常高效的方法。
詳細(xì)請參考http://developer.android.com/reference/android/text/format/package-summary.html
iv.??? TextUtils類
對于字符串處理Android為我們提供了一個(gè)簡單實(shí)用的TextUtils類,如果處理比較簡單的內(nèi)容不用去思考正則表達(dá)式不妨試試這個(gè)在android.text.TextUtils的類,詳細(xì)請參考http://developer.android.com/reference/android/text/TextUtils.html
v.??? 高性能MemoryFile類。
很多人抱怨Android處理底層I/O性能不是很理想,如果不想使用NDK則可以通過MemoryFile類實(shí)現(xiàn)高性能的文件讀寫操作。MemoryFile適用于哪些地方呢?對于I/O需要頻繁操作的,主要是和外部存儲相關(guān)的I/O操作,MemoryFile通過將 NAND或SD卡上的文件,分段映射到內(nèi)存中進(jìn)行修改處理,這樣就用高速的RAM代替了ROM或SD卡,性能自然提高不少,對于Android手機(jī)而言同時(shí)還減少了電量消耗。該類實(shí)現(xiàn)的功能不是很多,直接從Object上繼承,通過JNI的方式直接在C底層執(zhí)行。
詳細(xì)請參考http://developer.android.com/reference/android/os/MemoryFile.html
在此,只簡單列舉幾個(gè)常用的類和方法,更多的是要靠平時(shí)的積累和發(fā)現(xiàn)。多閱讀Google給的幫助文檔時(shí)很有益的。
5)???????合理利用本地方法
本地方法并不是一定比Java高效。最起碼,Java和native之間過渡的關(guān)聯(lián)是有消耗的,而JIT并不能對此進(jìn)行優(yōu)化。當(dāng)你分配本地資源時(shí) (本地堆上的內(nèi)存,文件說明符等),往往很難實(shí)時(shí)的回收這些資源。同時(shí)你也需要在各種結(jié)構(gòu)中編譯你的代碼(而非依賴JIT)。甚至可能需要針對相同的架構(gòu) 來編譯出不同的版本:針對ARM處理器的GI編譯的本地代碼,并不能充分利用Nexus One上的ARM,而針對Nexus One上ARM編譯的本地代碼不能在G1的ARM上運(yùn)行。當(dāng)你想部署程序到存在本地代碼庫的Android平臺上時(shí),本地代碼才顯得尤為有用,而并非為了Java應(yīng)用程序的提速。
6)???????復(fù)雜算法盡量用C完成
復(fù)雜算法盡量用C或者C++完成,然后用JNI調(diào)用。但是如果是算法比較單間,不必這么麻煩,畢竟JNI調(diào)用也會花一定的時(shí)間。請權(quán)衡。
7)???????減少不必要的全局變量
盡量避免static成員變量引用資源耗費(fèi)過多的實(shí)例,比如Context。Android提供了很健全的消息傳遞機(jī)制(Intent)和任務(wù)模型(Handler),可以通過傳遞或事件的方式,防止一些不必要的全局變量。
8)???????不要過多指望gc
Java的gc使用的是一個(gè)有向圖,判斷一個(gè)對象是否有效看的是其他的對象能到達(dá)這個(gè)對象的頂點(diǎn),有向圖的相對于鏈表、二叉樹來說開銷是可想而知。所以不要過多指望gc。將不用的對象可以把它指向NULL,并注意代碼質(zhì)量。同時(shí),顯示讓系統(tǒng)gc回收,例如圖片處理時(shí),
if(bitmap.isRecycled()==false) {//如果沒有回收bitmap.recycle();
}
9)???????了解Java四種引用方式
JDK 1.2版本開始,把對象的引用分為4種級別,從而使程序能更加靈活地控制對象的生命周期。這4種級別由高到低依次為:強(qiáng)引用、軟引用、弱引用和虛引用。
i.??? 強(qiáng)引用(StrongReference)
強(qiáng)引用是使用最普遍的引用。如果一個(gè)對象具有強(qiáng)引用,那垃圾回收器絕不會回收它。當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題。
ii.??? 軟引用(SoftReference)
如果一個(gè)對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
iii.??? 弱引用(WeakReference)
在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。不過,由于垃圾回收器是一個(gè)優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。
iv.??? 虛引用(PhantomReference)
顧名思義,就是形同虛設(shè)。與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。了解并熟練掌握這4中引用方式,選擇合適的對象應(yīng)用方式,對內(nèi)存的回收是很有幫助的。
詳細(xì)請參考http://blog.csdn.net/feng88724/article/details/6590064
10)???? 使用實(shí)體類比接口好
假設(shè)你有一個(gè)HashMap對象,你可以將它聲明為HashMap或者M(jìn)ap:
Map map1 = new HashMap();
HashMap map2 = new HashMap();
哪個(gè)更好呢?
按照傳統(tǒng)的觀點(diǎn)Map會更好些,因?yàn)檫@樣你可以改變他的具體實(shí)現(xiàn)類,只要這個(gè)類繼承自Map接口。傳統(tǒng)的觀點(diǎn)對于傳統(tǒng)的程序是正確的,但是它并不適合嵌入式系統(tǒng)。調(diào)用一個(gè)接口的引用會比調(diào)用實(shí)體類的引用多花費(fèi)一倍的時(shí)間。如果HashMap完全適合你的程序,那么使用Map就沒有什么價(jià)值。如果有些地方你不能確定,先避免使用Map,剩下的交給IDE提供的重構(gòu)功能好了。(當(dāng)然公共API是一個(gè)例外:一個(gè)好的API常常會犧牲一些性能)
11)???? 避免使用枚舉
枚舉變量非常方便,但不幸的是它會犧牲執(zhí)行的速度和并大幅增加文件體積。例如:
publicclassFoo {publicenumShrubbery { GROUND, CRAWLING, HANGING }
}
會產(chǎn)生一個(gè)900字節(jié)的.class文件(FooShubbery.class)。在它被首次調(diào)用時(shí),這個(gè)類會調(diào)用初始化方法來準(zhǔn)備每個(gè)枚舉變量。每個(gè)枚舉項(xiàng)都會被聲明成一個(gè)靜態(tài)變量,并被賦值。然后將這些靜態(tài)變量放在一個(gè)名為”Shubbery.class)。在它被首次調(diào)用時(shí),這個(gè)類會調(diào)用初始化方法來準(zhǔn)備每個(gè)枚舉變量。每個(gè)枚舉項(xiàng)都會被聲明成一個(gè)靜態(tài)變量,并被賦值。然后將這些靜態(tài)變量放在一個(gè)名為”VALUES”的靜態(tài)數(shù)組變量中。而這么一大堆代碼,僅僅是為了使用三個(gè) 整數(shù)。
這樣:Shrubbery shrub =Shrubbery.GROUND;會引起一個(gè)對靜態(tài)變量的引用,如果這個(gè)靜態(tài)變量是final int,那么編譯器會直接內(nèi)聯(lián)這個(gè)常數(shù)。
一方面說,使用枚舉變量可以讓你的API更出色,并能提供編譯時(shí)的檢查。所以在通常的時(shí)候你毫無疑問應(yīng)該為公共API選擇枚舉變量。但是當(dāng)性能方面有所限制的時(shí)候,你就應(yīng)該避免這種做法了。
有些情況下,使用ordinal()方法獲取枚舉變量的整數(shù)值會更好一些,舉例來說:

for(intn =0; n < list.size(); n++) {if(list.items[n].e ==MyEnum.VAL_X) {//do something}elseif(list.items[n].e ==MyEnum.VAL_Y) {//do something}
}

替換為:

intvalX =MyEnum.VAL_X.ordinal();intvalY =MyEnum.VAL_Y.ordinal();intcount =list.size();
MyItem items=list.items();for(intn =0; n < count; n++) {
intvalItem=items[n].e.ordinal();if(valItem ==valX) {//do something}elseif(valItem ==valY) {//do something}
}

會使性能得到一些改善,但這并不是最終的解決之道。
12)???? 在私有內(nèi)部內(nèi)中,考慮用包訪問權(quán)限替代私有訪問權(quán)限

publicclassFoo {publicclassInner {publicvoidstuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}privateintmValue;publicvoidrun() {
Inner in=newInner();
mValue= 27;
in.stuff();
}privatevoiddoStuff(intvalue) {
System.out.println("value:"+value);
}
}

需要注意的關(guān)鍵是:我們定義的一個(gè)私有內(nèi)部類(FooInner),直接訪問外部類中的一個(gè)私有方法和私有變量。這是合法的,代碼也會打印出預(yù)期的“Valueis27”。但問題是,虛擬機(jī)認(rèn)為從FooInner),直接訪問外部類中的一個(gè)私有方法和私有變量。這是合法的,代碼也會打印出預(yù)期的“Valueis27”。但問題是,虛擬機(jī)認(rèn)為從FooInner中直接訪問Foo的私有成員是非法的,因?yàn)樗麄兪莾蓚€(gè)不同的類,盡管Java語言允許內(nèi)部類訪問外部類的私有成員,但是通過編譯器生成幾個(gè)綜合方法來橋接這些間隙的。
/*package*/staticintFoo.access$100(Foo foo) {returnfoo.mValue;
}/*package*/staticvoidFoo.access%200(Foo foo,intvalue) {
foo.duStuff(value);
}
內(nèi)部類會在外部類中任何需要訪問mValue字段或調(diào)用doStuff方法的地方調(diào)用這些靜態(tài)方法。這意味著這些代碼將直接存取成員變量表現(xiàn)為通過存取器方法訪問。之前提到過存取器訪問如何比直接訪問慢,這例子說明,某些語言約會定導(dǎo)致不可見的性能問題。如果你在高性能的Hotspot中使用這些代碼,可以通過聲明被內(nèi)部類訪問的字段和成員為包訪問權(quán)限,而非私有。但這也意味著這些字段會被其他處于同一個(gè)包中的類訪問,因此在公共API中不宜采用。
13)???? 將與內(nèi)部類一同使用的變量聲明在包范圍內(nèi)
請看下面的類定義:

publicclassFoo {privateclassInner {voidstuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}privateintmValue;publicvoidrun() {
Inner in=newInner();
mValue= 27;
in.stuff();
}privatevoiddoStuff(intvalue) {
System.out.println("Value is " +value);
}
}

這其中的關(guān)鍵是,我們定義了一個(gè)內(nèi)部類(FooInner),它需要訪問外部類的私有域變量和函數(shù)。這是合法的,并且會打印出我們希望的結(jié)果Valueis27。問題是在技術(shù)上來講(在幕后)FooInner),它需要訪問外部類的私有域變量和函數(shù)。這是合法的,并且會打印出我們希望的結(jié)果Valueis27。問題是在技術(shù)上來講(在幕后)FooInner是一個(gè)完全獨(dú)立的類,它要直接訪問Foo的私有成員是非法的。要跨越這個(gè)鴻溝,編譯器需要生成一組方法:
/*package*/staticintFoo.access$100(Foo foo) {returnfoo.mValue;
}/*package*/staticvoidFoo.access$200(Foo foo,intvalue) {
foo.doStuff(value);
}
內(nèi)部類在每次訪問mValueg和gdoStuffg方法時(shí),都會調(diào)用這些靜態(tài)方法。就是說,上面的代碼說明了一個(gè)問題,你是在通過接口方法訪問這些成員變量和函數(shù)而不是直接調(diào)用它們。在前面我們已經(jīng)說過,使用接口方法(getter、setter)比直接訪問速度要慢。所以這個(gè)例子就是在特定語法下面產(chǎn)生的一個(gè)“隱性的”性能障礙。通過將內(nèi)部類訪問的變量和函數(shù)聲明由私有范圍改為包范圍,我們可以避免這個(gè)問題。這樣做可以讓代碼運(yùn)行更快,并且避免產(chǎn)生額外的靜態(tài)方法。(遺憾的是,這些域和方法可以被同一個(gè)包內(nèi)的其他類直接訪問,這與經(jīng)典的OO原則相違背。因此當(dāng)你設(shè)計(jì)公共API的時(shí)候應(yīng)該謹(jǐn)慎使用這條優(yōu)化原則)。
14)???? 緩存
適量使用緩存,不要過量使用,因?yàn)閮?nèi)存有限,能保存路徑地址的就不要存放圖片數(shù)據(jù),不經(jīng)常使用的盡量不要緩存,不用時(shí)就清空。
15)???? 關(guān)閉資源對象
對SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O操作等都應(yīng)該記得顯示關(guān)閉。
2.??????視圖優(yōu)化
1)???????View優(yōu)化
i.??? 減少不必要的View以及View的嵌套層次。
比如實(shí)現(xiàn)一個(gè)listview中常用的layout,可以使用RelativeLayout減少嵌套,要知道每個(gè)View的對象會耗費(fèi)1~2k內(nèi)存,嵌套層次過多會引起頻繁的gc,造成ANR。
ii.??? 通過HierarchyViewer查看布局結(jié)構(gòu)
利用HierarchyViewer來查看View的結(jié)構(gòu):~/tools/hierarchyviewer,能很清楚地看到RelativeLayout下面的扁平結(jié)構(gòu),這樣能加快dom的渲染速度。
詳細(xì)請參考http://developer.android.com/guide/developing/tools/hierarchy-viewer.html
iii.??? 通過Layoutopt優(yōu)化布局
通過Android sdk中tools目錄下的layoutopt 命令查看你的布局是否需要優(yōu)化。詳細(xì)請參考http://apps.hi.baidu.com/share/detail/34247942
2)???????多線程解決復(fù)雜計(jì)算
占用CPU較多的數(shù)據(jù)操作盡可能放在一個(gè)單獨(dú)的線程中進(jìn)行,通過handler等方式把執(zhí)行的結(jié)果交于UI線程顯示。特別是針對的網(wǎng)絡(luò)訪問,數(shù)據(jù)庫查詢,和復(fù)雜的算法。目前Android提供了AsyncTask,Hanlder、Message和Thread的組合。對于多線程的處理,如果并發(fā)的線程很多,同時(shí)有頻繁的創(chuàng)建和釋放,可以通過concurrent類的線程池解決線程創(chuàng)建的效率瓶頸。另外值得注意的是,應(yīng)用開發(fā)中自定義View的時(shí)候,交互部分,千萬不要寫成線程不斷刷新界面顯示,而是根據(jù)TouchListener事件主動觸發(fā)界面的更新。
3)???????布局用Java完成比XML快
一般情況下對于Android程序布局往往使用XML文件來編寫,這樣可以提高開發(fā)效率,但是考慮到代碼的安全性以及執(zhí)行效率,可以通過Java代碼執(zhí)行創(chuàng)建,雖然Android編譯過的XML是二進(jìn)制的,但是加載XML解析器的效率對于資源占用還是比較大的,Java處理效率比XML快得多,但是對于一個(gè)復(fù)雜界面的編寫,可能需要一些套嵌考慮,如果你思維靈活的話,使用Java代碼來布局你的Android應(yīng)用程序是一個(gè)更好的方法。
4)???????對大型圖片進(jìn)行縮放
圖片讀取是OOM(Out of Memory)的???,當(dāng)在Android手機(jī)上直接讀取4M的圖片時(shí),死神一般都會降臨,所以導(dǎo)致往往自己手機(jī)拍攝的照片都不能直接讀取。對大型圖片進(jìn)行縮放處理圖片時(shí)我們經(jīng)常會用到BitmapFactory類,android系統(tǒng)中讀取位圖Bitmap時(shí)分給虛擬機(jī)中圖片的堆棧大小只有8M。用BitmapFactory解碼一張圖片時(shí),有時(shí)也會遇到該錯(cuò)誤。這往往是由于圖片過大造成的。這時(shí)我們需要分配更少的內(nèi)存空間來存儲。BitmapFactory.Options.inSampleSize設(shè)置恰當(dāng)?shù)膇nSampleSize可以使BitmapFactory分配更少的空間以消除該錯(cuò)誤。Android提供了一種動態(tài)計(jì)算的,如下:
讀取圖片之前先查看其大?。?/p>
BitmapFactory.Options opts =newBitmapFactory.Options();
opts.inJustDecodeBounds=true;
Bitmap bitmap= BitmapFactory.decodeFile(imageFile, opts);
使用得到的圖片原始寬高計(jì)算適合自己的smaplesize
BitmapFactory.Options opts =newBitmapFactory.Options();
opts.inSampleSize= 4 ;//4就代表容量變?yōu)橐郧叭萘康?/4Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
對于過時(shí)的Bitmap對象一定要及時(shí)recycle,并且把此對象賦值為null。
bitmap.recycle();
bitmap=null;
5)???????合理使用ViewStub
ViewStub 是一個(gè)隱藏的,不占用內(nèi)存空間的視圖對象,它可以在運(yùn)行時(shí)延遲加載布局資源文件。當(dāng)ViewStub可見,或者調(diào)用 inflate()函數(shù)時(shí),才會加載這個(gè)布局資源文件。 該ViewStub在加載視圖時(shí)在父容器中替換它本身。因此,ViewStub會一直存在于視圖中,直到調(diào)用setVisibility(int) 或者inflate()為止。ViewStub的布局參數(shù)會隨著加載的視圖數(shù)一同被添加到ViewStub父容器。同樣,你也可以通過使用 inflatedId屬性來定義或重命名要加載的視圖對象的Id值。所以我們可以使用ViewStub延遲加載某些比較復(fù)雜的layout,動態(tài)加載 View,采用ViewStub 避免一些不經(jīng)常的視圖長期握住引用。
詳細(xì)請參考http://developer.android.com/reference/android/view/ViewStub.html
6)???????針對ListView的性能優(yōu)化
i.??? 復(fù)用convertView。
ii.??? 在getItemView中,判斷convertView是否為空,如果不為空,可復(fù)用。如果couvertview中的view需要添加 listerner,代碼一定要在if(convertView==null){}之外。
iii.??? 異步加載圖片,item中如果包含有web image,那么最好異步加載。
iv.??? 快速滑動時(shí)不顯示圖片
當(dāng)快速滑動列表時(shí)(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view,可以不顯示出來;而處于其他兩種狀 態(tài)(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來。
v.??? item盡可能的減少使用的控件和布局的層次;背景色與cacheColorHint設(shè)置相同顏色;ListView中item的布局至關(guān)重要,必須盡可 能的減少使用的控件,布局?! elativeLayout是絕對的利器,通過它可以減少布局的層次。同時(shí)要盡可能的復(fù)用控件,這樣可以減少 ListView的內(nèi)存使用,減少滑動時(shí)gc次數(shù)。ListView的背景色與cacheColorHint設(shè)置相同顏色,可以提高滑動時(shí)的渲染性能。
vi.??? getView優(yōu)化
ListView中g(shù)etView是性能是關(guān)鍵,這里要盡可能的優(yōu)化。getView方法中要重用view;getView方法中不能做復(fù)雜的邏輯計(jì)算,特別是數(shù)據(jù)庫和網(wǎng)絡(luò)訪問操作,否則會嚴(yán)重影響滑動時(shí)的性能。優(yōu)化如下所示:

@OverridepublicView getView(intposition, View convertView, ViewGroup parent) {
Log.d("MyAdapter", "Position:" + position + "---" +String.valueOf(System.currentTimeMillis()));finalLayoutInflater inflater =(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v= inflater.inflate(R.layout.list_item_icon_text,null);
((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon);
((TextView) v.findViewById(R.id.text)).setText(mData[position]);returnv;
}

建議改為:

@OverridepublicView getView(intposition, View convertView, ViewGroup parent) {
Log.d("Adapter", "Position:" + position + " : " +String.valueOf(System.currentTimeMillis()));
ViewHolder holder;if(convertView ==null) {finalLayoutInflater inflater =(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView= inflater.inflate(R.layout.list_item_icon_text,null);
holder=newViewHolder();
holder.icon=(ImageView) convertView.findViewById(R.id.icon);
holder.text=(TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
}else{
holder=(ViewHolder) convertView.getTag();
}
holder.icon.setImageResource(R.drawable.icon);
holder.text.setText(mData[position]);returnconvertView;
}staticclassViewHolder {
ImageView icon;
TextView text;
}
}

以上是Google IO大會上給出的優(yōu)化建議,經(jīng)過嘗試ListView確實(shí)流暢了許多。使用1000條記錄,經(jīng)測試第一種方式耗時(shí):25211ms,第二種方式耗時(shí):16528ms。
7)???????其他
i.??? 分辨率適配
-ldpi,-mdpi, -hdpi配置不同精度資源,系統(tǒng)會根據(jù)設(shè)備自適應(yīng),包括drawable, layout,style等不同資源。
ii.??? 盡量使用dp(density independent pixel)開發(fā),不用px(pixel)。
iii.??? 多用wrap_content, fill_parent
iv.??? 拋棄AbsoluteLayout
v.??? 使用9patch(通過~/tools/draw9patch.bat啟動應(yīng)用程序),png格式
vi.??? 采用 優(yōu)化布局層數(shù);采用來共享布局。
vii.??? 將Acitivity中的Window的背景圖設(shè)置為空。getWindow().setBackgroundDrawable(null);android的默認(rèn)背景是不是為空。
viii.??? View中設(shè)置緩存屬性.setDrawingCache為true。
3.??????網(wǎng)絡(luò)優(yōu)化
1)???????避免頻繁網(wǎng)絡(luò)請求
訪問server端時(shí),建立連接本身比傳輸需要跟多的時(shí)間,如非必要,不要將一交互可以做的事情分成多次交互(這需要與Server端協(xié)調(diào)好)。有效管理Service 后臺服務(wù)就相當(dāng)于一個(gè)持續(xù)運(yùn)行的Acitivity,如果開發(fā)的程序后臺都會一個(gè)service不停的去服務(wù)器上更新數(shù)據(jù),在不更新數(shù)據(jù)的時(shí)候就讓它sleep,這種方式是非常耗電的,通常情況下,可以使用AlarmManager來定時(shí)啟動服務(wù)。如下所示,第30分鐘執(zhí)行一次。
AlarmManager alarmManager =(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent=newIntent(context, MyService.class);
PendingIntent pendingIntent= PendingIntent.getService(context, 0, intent, 0);longinterval = DateUtils.MINUTE_IN_MILLIS * 30;longfirstWake = System.currentTimeMillis() +interval;
am.setRepeating(AlarmManager.RTC,firstWake,? interval,? pendingIntent);
2)???????數(shù)據(jù)壓縮
傳輸數(shù)據(jù)經(jīng)過壓縮 目前大部門網(wǎng)站都支持GZIP壓縮,所以在進(jìn)行大數(shù)據(jù)量下載時(shí),盡量使用GZIP方式下載,可以減少網(wǎng)絡(luò)流量,一般是壓縮前數(shù)據(jù)大小的30%左右。
HttpGet request =newHttpGet("http://example.com/gzipcontent");
HttpResponse resp=newDefaultHttpClient().execute(request);
HttpEntity entity=response.getEntity();
InputStream compressed=entity.getContent();
InputStream rawData=newGZIPInputStream(compressed);
3)???????使用線程池
線程池,分為核心線程池和普通線程池,下載圖片等耗時(shí)任務(wù)放置在普通線程池,避免耗時(shí)任務(wù)阻塞線程池后,導(dǎo)致所有異步任務(wù)都必須等待。
4)???????選擇合適的數(shù)據(jù)格式傳輸形式
其中Tree Parse 是DOM解析 Event/Stream是SAX方式解析。
很明顯,使用流的方式解析效率要高一些,因?yàn)镈OM解析是在對整個(gè)文檔讀取完后,再根據(jù)節(jié)點(diǎn)層次等再組織起來。而流的方式是邊讀取數(shù)據(jù)邊解析,數(shù)據(jù)讀取完后,解析也就完畢了。在數(shù)據(jù)格式方面,JSON和Protobuf效率明顯比XML好很多,XML和JSON大家都很熟悉。從上面的圖中可以得出結(jié)論就是盡量使用SAX等邊讀取邊解析的方式來解析數(shù)據(jù),針對移動設(shè)備,最好能使用JSON之類的輕量級數(shù)據(jù)格式為佳。
1)???????其他
設(shè)置連接超時(shí)時(shí)間和響應(yīng)超時(shí)時(shí)間。Http請求按照業(yè)務(wù)需求,分為是否可以緩存和不可緩存,那么在無網(wǎng)絡(luò)的環(huán)境中,仍然通過緩存的HttpResponse瀏覽部分?jǐn)?shù)據(jù),實(shí)現(xiàn)離線閱讀。
2.??????數(shù)據(jù)庫相關(guān)
1)???????相對于封裝過的ContentProvider而言,使用原始SQL語句執(zhí)行效率高,比如使用方法rawQuery、execSQL的執(zhí)行效率比較高。
2)???????對于需要一次性修改多個(gè)數(shù)據(jù)時(shí),可以考慮使用SQLite的事務(wù)方式批量處理。
3)???????批量插入多行數(shù)據(jù)使用InsertHelper或者bulkInsert方法
4)???????有些能用文件操作的,盡量采用文件操作,文件操作的速度比數(shù)據(jù)庫的操作要快10倍左右。
3.??????性能測試
對于Android平臺上軟件的性能測試可以通過以下幾種方法來分析效率瓶頸,目前Google在Android軟件開發(fā)過程中已經(jīng)引入了多種測試工具包,比如Unit測試工程,調(diào)試類,還有模擬器的Dev Tools都可以直接反應(yīng)執(zhí)行性能。
1)???????在模擬器上的Dev Tools可以激活屏幕顯示當(dāng)前的FPS,CPU使用率,可以幫助我們測試一些3D圖形界面的性能。
2)???????一般涉及到網(wǎng)絡(luò)應(yīng)用的程序,在效率上和網(wǎng)速有很多關(guān)系,這里需要多次的調(diào)試才能實(shí)際了解。
3)???????對于邏輯算法的效率執(zhí)行,我們使用Android上最普遍的,計(jì)算執(zhí)行時(shí)間來查看:
longstart =System.currentTimeMillis();//do somethinglongduration = System.currentTimeMillis() - start;
最終duration保存著實(shí)際處理該方法需要的毫秒數(shù)。
4)???????gc效率跟蹤,如果你執(zhí)行的應(yīng)用比較簡單,可以在DDMS中查看下Logcat的VM釋放內(nèi)存情況,大概模擬下那些地方可以緩存數(shù)據(jù)或改進(jìn)算法的。
5)???????線程的使用和同步,Android平臺上給我們提供了豐富的多任務(wù)同步方法,但在深層上并沒有過多的比如自旋鎖等高級應(yīng)用,不 過對于Service和 appWidget而言,他們實(shí)際的產(chǎn)品中都應(yīng)該以多線程的方式處理,以釋放CPU時(shí)間,對于線程和堆內(nèi)存的查看這些都可以在DDMS中看到。
6)???????利用traceview和monkey等工具測試應(yīng)用。
7)???????利用layoutopt和ninepatch等工具優(yōu)化視圖。
4.??????結(jié)束語
本文給出了一些Android 移動開發(fā)中常見的優(yōu)化方法,多數(shù)情況下利用這些優(yōu)化方法優(yōu)化后的代碼,執(zhí)行效率有明顯的提高,內(nèi)存溢出情況也有所改善。在實(shí)際應(yīng)用中對程序的優(yōu)化一定要權(quán)衡是否是必須的,因?yàn)閮?yōu)化可能會帶來諸如增加BUG,降低代碼的可讀性,降低代碼的移植性等不良效果。希望不要盲目優(yōu)化,請先確定存在問題,再進(jìn)行優(yōu)化。并且你知道當(dāng)前系統(tǒng)的性能,否則無法衡量你進(jìn)行嘗試所得到的性能提升。希望本文能給大家切實(shí)帶來幫助。歡迎就上述問題進(jìn)一步交流。如有發(fā)現(xiàn)錯(cuò)誤或不足,請斧正。