Android開(kāi)發(fā)性能優(yōu)化簡(jiǎn)介這篇文章來(lái)自北京大學(xué)Android研究實(shí)驗室,不得不說(shuō)寫(xiě)的真是好,沒(méi)給PKU丟臉~
==============================================================
作者:賀小令
隨著(zhù)技術(shù)的發(fā)展,智能手機硬件配置越來(lái)越高,可是它和現在的PC相比,其運算能力,續航能力,存儲空間等都還是受到很大的限制,同時(shí)用戶(hù)對手機的體驗要求遠遠高于PC的桌面應用程序。以上理由,足以需要開(kāi)發(fā)人員更加專(zhuān)心去實(shí)現和優(yōu)化你的代碼了。選擇合適的算法和數據結構永遠是開(kāi)發(fā)人員最先應該考慮的事情。同時(shí),我們應該時(shí)刻牢記,寫(xiě)出高效代碼的兩條基本的原則:(1)不要做不必要的事;(2)不要分配不必要的內存。
我從去年開(kāi)始接觸Android開(kāi)發(fā),以下結合自己的一點(diǎn)項目經(jīng)驗,同時(shí)參考了Google的優(yōu)化文檔和網(wǎng)上的諸多技術(shù)大牛給出的意見(jiàn),整理出這份文檔。
1.內存優(yōu)化
Android系統對每個(gè)軟件所能使用的RAM空間進(jìn)行了限制(如:Nexus one 對每個(gè)軟件的內存限制是24M),同時(shí)Java語(yǔ)言本身比較消耗內存,dalvik虛擬機也要占用一定的內存空間,所以合理使用內存,彰顯出一個(gè)程序員的素質(zhì)和技能。
1)了解JIT
即時(shí)編譯(Just-in-time Compilation,JIT),又稱(chēng)動(dòng)態(tài)轉譯(Dynamic Translation),是一種通過(guò)在運行時(shí)將字節碼翻譯為機器碼,從而改善字節碼編譯語(yǔ)言性能的技術(shù)。即時(shí)編譯前期的兩個(gè)運行時(shí)理論是字節碼編譯和動(dòng)態(tài)編譯。Android原來(lái)Dalvik虛擬機是作為一種解釋器實(shí)現,新版(Android2.2+)將換成JIT編譯器實(shí)現。性能測試顯示,在多項測試中新版本比舊版本提升了大約6倍。
詳細請參考
http://hi.baidu.com/cool_parkour/blog/item/2802b01586e22cd8a6ef3f6b.html2)避免創(chuàng )建不必要的對象
就像世界上沒(méi)有免費的午餐,世界上也沒(méi)有免費的對象。雖然gc為每個(gè)線(xiàn)程都建立了臨時(shí)對象池,可以使創(chuàng )建對象的代價(jià)變得小一些,但是分配內存永遠都比不分配內存的代價(jià)大。如果你在用戶(hù)界面循環(huán)中分配對象內存,就會(huì )引發(fā)周期性的垃圾回收,用戶(hù)就會(huì )覺(jué)得界面像打嗝一樣一頓一頓的。所以,除非必要,應盡量避免盡力對象的實(shí)例。下面的例子將幫助你理解這條原則:
當你從用戶(hù)輸入的數據中截取一段字符串時(shí),盡量使用substring函數取得原始數據的一個(gè)子串,而不是為子串另外建立一份拷貝。這樣你就有一個(gè)新的String對象,它與原始數據共享一個(gè)char數組。 如果你有一個(gè)函數返回一個(gè)String對象,而你確切的知道這個(gè)字符串會(huì )被附加到一個(gè)StringBuffer,那么,請改變這個(gè)函數的參數和實(shí)現方式,直接把結果附加到StringBuffer中,而不要再建立一個(gè)短命的臨時(shí)對象。
一個(gè)更極端的例子是,把多維數組分成多個(gè)一維數組:
int數組比Integer數組好,這也概括了一個(gè)基本事實(shí),兩個(gè)平行的int數組比 (int,int)對象數組性能要好很多。同理,這試用于所有基本類(lèi)型的組合。如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個(gè)單獨的Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個(gè)API,讓別人調用它的時(shí)候。這時(shí)候你要注重對API接口的設計而犧牲一點(diǎn)兒速度。當然在A(yíng)PI的內部,你仍要盡可能的提高代碼的效率)
總體來(lái)說(shuō),就是避免創(chuàng )建短命的臨時(shí)對象。減少對象的創(chuàng )建就能減少垃圾收集,進(jìn)而減少對用戶(hù)體驗的影響。
3)靜態(tài)方法代替虛擬方法
如果不需要訪(fǎng)問(wèn)某對象的字段,將方法設置為靜態(tài),調用會(huì )加速15%到20%。這也是一種好的做法,因為你可以從方法聲明中看出調用該方法不需要更新此對象的狀態(tài)。
4)避免內部Getters/Setters
在源生語(yǔ)言像C++中,通常做法是用Getters(i=getCount())代替直接字段訪(fǎng)問(wèn)(i=mCount)。這是C++中一個(gè)好的習慣,因為編譯器會(huì )內聯(lián)這些訪(fǎng)問(wèn),并且如果需要約束或者調試這些域的訪(fǎng)問(wèn),你可以在任何時(shí)間添加代碼。
而在A(yíng)ndroid中,這不是一個(gè)好的做法。虛方法調用的代價(jià)比直接字段訪(fǎng)問(wèn)高昂許多。通常根據面向對象語(yǔ)言的實(shí)踐,在公共接口中使用Getters和Setters是有道理的,但在一個(gè)字段經(jīng)常被訪(fǎng)問(wèn)的類(lèi)中宜采用直接訪(fǎng)問(wèn)。
無(wú)JIT時(shí),直接字段訪(fǎng)問(wèn)大約比調用getter訪(fǎng)問(wèn)快3倍。有JIT時(shí)(直接訪(fǎng)問(wèn)字段開(kāi)銷(xiāo)等同于局部變量訪(fǎng)問(wèn)),要快7倍。
5)將成員緩存到本地
訪(fǎng)問(wèn)成員變量比訪(fǎng)問(wèn)本地變量慢得多,下面一段代碼:
[java]
view plaincopyfor(inti=0;i<this.mCount;i++){
dumpItem(this.mItems);
}
最好改成這樣:
[java]
view plaincopyintcount=this.mCount;
Item[]items=this.mItems;
for(inti=0;i<count;i++){
dumpItems(items);
}
另一個(gè)相似的原則是:永遠不要在for的第二個(gè)條件中調用任何方法。如下面方法所示,在每次循環(huán)的時(shí)候都會(huì )調用getCount()方法,這樣做比你在一個(gè)int先把結果保存起來(lái)開(kāi)銷(xiāo)大很多。
[java]
view plaincopyfor(inti=0;i<this.getCount();i++){
dumpItems(this.getItem(i));
}
同樣如果你要多次訪(fǎng)問(wèn)一個(gè)變量,也最好先為它建立一個(gè)本地變量,例如:
[java]
view plaincopyprotectedvoiddrawHorizontalScrollBar(Canvascanvas,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次訪(fǎng)問(wèn)成員變量mScrollBar,如果將它緩存到本地,4次成員變量訪(fǎng)問(wèn)就會(huì )變成4次效率更高的棧變量訪(fǎng)問(wèn)。 另外就是方法的參數與本地變量的效率相同。
1)對常量使用static final修飾符
讓我們來(lái)看看這兩段在類(lèi)前面的聲明:
[java]
view plaincopystaticintintVal=42;
staticStringstrVal="Hello,world!";
必以其會(huì )生成一個(gè)叫做clinit的初始化類(lèi)的方法,當類(lèi)第一次被使用的時(shí)候這個(gè)方法會(huì )被執行。方法會(huì )將42賦給intVal,然后把一個(gè)指向類(lèi)中常量表的引用賦給strVal。當以后要用到這些值的時(shí)候,會(huì )在成員變量表中查找到他們。 下面我們做些改進(jìn),使用“final”關(guān)鍵字:
[java]
view plaincopystaticfinalintintVal=42;
staticfinalStringstrVal="Hello,world!";
現在,類(lèi)不再需要clinit方法,因為在成員變量初始化的時(shí)候,會(huì )將常量直接保存到類(lèi)文件中。用到intVal的代碼被直接替換成42,而使用strVal的會(huì )指向一個(gè)字符串常量,而不是使用成員變量。 將一個(gè)方法或類(lèi)聲明為final不會(huì )帶來(lái)性能的提升,但是會(huì )幫助編譯器優(yōu)化代碼。舉例說(shuō),如果編譯器知道一個(gè)getter方法不會(huì )被重載,那么編譯器會(huì )對其采用內聯(lián)調用。
你也可以將本地變量聲明為final,同樣,這也不會(huì )帶來(lái)性能的提升。使用“final”只能使本地變量看起來(lái)更清晰些(但是也有些時(shí)候這是必須的,比如在使用匿名內部類(lèi)的時(shí)候)。
2)使用改進(jìn)的For循環(huán)語(yǔ)法
改進(jìn)for循環(huán)(有時(shí)被稱(chēng)為for-each循環(huán))能夠用于實(shí)現了iterable接口的集合類(lèi)及數組中。在集合類(lèi)中,迭代器讓接口調用hasNext()和next()方法。在A(yíng)rrayList中,手寫(xiě)的計數循環(huán)迭代要快3倍(無(wú)論有沒(méi)有JIT),但其他集合類(lèi)中,改進(jìn)的for循環(huán)語(yǔ)法和迭代器具有相同的效率。下面展示集中訪(fǎng)問(wèn)數組的方法:
[java]
view plaincopystaticclassFoo{
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(Fooa:mArray){
sum+=a.mSplat;
}
}
}
在zero()中,每次循環(huán)都會(huì )訪(fǎng)問(wèn)兩次靜態(tài)成員變量,取得一次數組的長(cháng)度。 在one()中,將所有成員變量存儲到本地變量。
two()使用了在java1.5中引入的foreach語(yǔ)法。編譯器會(huì )將對數組的引用和數組的長(cháng)度保存到本地變量中,這對訪(fǎng)問(wèn)數組元素非常好。但是編譯器還會(huì )在每次循環(huán)中產(chǎn)生一個(gè)額外的對本地變量的存儲操作(對變量a的存?。┻@樣會(huì )比one()多出4個(gè)字節,速度要稍微慢一些。
3)避免使用浮點(diǎn)數
通常的經(jīng)驗是,在A(yíng)ndroid設備中,浮點(diǎn)數會(huì )比整型慢兩倍,在缺少FPU和JIT的G1上對比有FPU和JIT的Nexus One中確實(shí)如此(兩種設備間算術(shù)運算的絕對速度差大約是10倍)
從速度方面說(shuō),在現代硬件上,float和double之間沒(méi)有任何不同。更廣泛的講,double大2倍。在臺式機上,由于不存在空間問(wèn)題,double的優(yōu)先級高于float。
但即使是整型,有的芯片擁有硬件乘法,卻缺少除法。這種情況下,整型除法和求模運算是通過(guò)軟件實(shí)現的,就像當你設計Hash表,或是做大量的算術(shù)那樣,例如a/2可以換成a*0.5。
4)了解并使用類(lèi)庫
選擇Library中的代碼而非自己重寫(xiě),除了通常的那些原因外,考慮到系統空閑時(shí)會(huì )用匯編代碼調用來(lái)替代library方法,這可能比JIT中生成的等價(jià)的最好的Java代碼還要好。
i. 當你在處理字串的時(shí)候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊實(shí)現的方法。這些方法都是使用C/C++實(shí)現的,比起Java循環(huán)快10到100倍。
ii. System.arraycopy方法在有JIT的Nexus One上,自行編碼的循環(huán)快9倍。
iii. android.text.format包下的Formatter類(lèi),提供了IP地址轉換、文件大小轉換等方法;DateFormat類(lèi),提供了各種時(shí)間轉換,都是非常高效的方法。
詳細請參考
http://developer.android.com/reference/android/text/format/package-summary.htmliv. TextUtils類(lèi)
對于字符串處理Android為我們提供了一個(gè)簡(jiǎn)單實(shí)用的TextUtils類(lèi),如果處理比較簡(jiǎn)單的內容不用去思考正則表達式不妨試試這個(gè)在android.text.TextUtils的類(lèi),詳細請參考
http://developer.android.com/reference/android/text/TextUtils.htmlv. 高性能MemoryFile類(lèi)。
很多人抱怨Android處理底層I/O性能不是很理想,如果不想使用NDK則可以通過(guò)MemoryFile類(lèi)實(shí)現高性能的文件讀寫(xiě)操作。
MemoryFile適用于哪些地方呢?對于I/O需要頻繁操作的,主要是和外部存儲相關(guān)的I/O操作,MemoryFile通過(guò)將 NAND或SD卡上的文件,分段映射到內存中進(jìn)行修改處理,這樣就用高速的RAM代替了ROM或SD卡,性能自然提高不少,對于A(yíng)ndroid手機而言同時(shí)還減少了電量消耗。該類(lèi)實(shí)現的功能不是很多,直接從Object上繼承,通過(guò)JNI的方式直接在C底層執行。
詳細請參考
http://developer.android.com/reference/android/os/MemoryFile.html在此,只簡(jiǎn)單列舉幾個(gè)常用的類(lèi)和方法,更多的是要靠平時(shí)的積累和發(fā)現。多閱讀Google給的幫助文檔時(shí)很有益的。
5)合理利用本地方法
本地方法并不是一定比Java高效。最起碼,Java和native之間過(guò)渡的關(guān)聯(lián)是有消耗的,而JIT并不能對此進(jìn)行優(yōu)化。當你分配本地資源時(shí)(本地堆上的內存,文件說(shuō)明符等),往往很難實(shí)時(shí)的回收這些資源。同時(shí)你也需要在各種結構中編譯你的代碼(而非依賴(lài)JIT)。甚至可能需要針對相同的架構來(lái)編譯出不同的版本:針對ARM處理器的GI編譯的本地代碼,并不能充分利用Nexus One上的ARM,而針對Nexus One上ARM編譯的本地代碼不能在G1的ARM上運行。
當你想部署程序到存在本地代碼庫的Android平臺上時(shí),本地代碼才顯得尤為有用,而并非為了Java應用程序的提速。
6)復雜算法盡量用C完成
復雜算法盡量用C或者C++完成,然后用JNI調用。但是如果是算法比較單間,不必這么麻煩,畢竟JNI調用也會(huì )花一定的時(shí)間。請權衡。
7)減少不必要的全局變量
盡量避免static成員變量引用資源耗費過(guò)多的實(shí)例,比如Context。Android提供了很健全的消息傳遞機制(Intent)和任務(wù)模型(Handler),可以通過(guò)傳遞或事件的方式,防止一些不必要的全局變量。
8)不要過(guò)多指望gc
Java的gc使用的是一個(gè)有向圖,判斷一個(gè)對象是否有效看的是其他的對象能到達這個(gè)對象的頂點(diǎn),有向圖的相對于鏈表、二叉樹(shù)來(lái)說(shuō)開(kāi)銷(xiāo)是可想而知。所以不要過(guò)多指望gc。將不用的對象可以把它指向NULL,并注意代碼質(zhì)量。同時(shí),顯示讓系統gc回收,例如圖片處理時(shí),
[java]
view plaincopyif(bitmap.isRecycled()==false){//如果沒(méi)有回收
bitmap.recycle();
}
9)了解Java四種引用方式
JDK 1.2版本開(kāi)始,把對象的引用分為4種級別,從而使程序能更加靈活地控制對象的生命周期。這4種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
i. 強引用(StrongReference)
強引用是使用最普遍的引用。如果一個(gè)對象具有強引用,那垃圾回收器絕不會(huì )回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會(huì )靠隨意回收具有強引用的對象來(lái)解決內存不足的問(wèn)題。
ii. 軟引用(SoftReference)
如果一個(gè)對象只具有軟引用,則內存空間足夠,垃圾回收器就不會(huì )回收它;如果內存空間不足了,就會(huì )回收這些對象的內存。只要垃圾回收器沒(méi)有回收它,該對象就可以被程序使用。軟引用可用來(lái)實(shí)現內存敏感的高速緩存。
iii. 弱引用(WeakReference)
在垃圾回收器線(xiàn)程掃描它所管轄的內存區域的過(guò)程中,一旦發(fā)現了只具有弱引用的對象,不管當前內存空間足夠與否,都會(huì )回收它的內存。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級很低的線(xiàn)程,因此不一定會(huì )很快發(fā)現那些只具有弱引用的對象。
iv. 虛引用(PhantomReference)
顧名思義,就是形同虛設。與其他幾種引用都不同,虛引用并不會(huì )決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。
了解并熟練掌握這4中引用方式,選擇合適的對象應用方式,對內存的回收是很有幫助的。
詳細請參考
http://blog.csdn.net/feng88724/article/details/659006410) 使用實(shí)體類(lèi)比接口好
假設你有一個(gè)HashMap對象,你可以將它聲明為HashMap或者M(jìn)ap:
[java]
view plaincopyMapmap1=newHashMap();
HashMapmap2=newHashMap();
哪個(gè)更好呢? 按照傳統的觀(guān)點(diǎn)Map會(huì )更好些,因為這樣你可以改變他的具體實(shí)現類(lèi),只要這個(gè)類(lèi)繼承自Map接口。傳統的觀(guān)點(diǎn)對于傳統的程序是正確的,但是它并不適合嵌入式系統。調用一個(gè)接口的引用會(huì )比調用實(shí)體類(lèi)的引用多花費一倍的時(shí)間。如果HashMap完全適合你的程序,那么使用Map就沒(méi)有什么價(jià)值。如果有些地方你不能確定,先避免使用 Map,剩下的交給IDE提供的重構功能好了。(當然公共API是一個(gè)例外:一個(gè)好的API常常會(huì )犧牲一些性能)
11) 避免使用枚舉
枚舉變量非常方便,但不幸的是它會(huì )犧牲執行的速度和并大幅增加文件體積。例如:
[java]
view plaincopypublicclassFoo{
publicenumShrubbery{GROUND,CRAWLING,HANGING}
}
會(huì )產(chǎn)生一個(gè)900字節的.class文件(Foo$Shubbery.class)。在它被首次調用時(shí),這個(gè)類(lèi)會(huì )調用初始化方法來(lái)準備每個(gè)枚舉變量。每個(gè)枚舉項都會(huì )被聲明成一個(gè)靜態(tài)變量,并被賦值。然后將這些靜態(tài)變量放在一個(gè)名為”$VALUES”的靜態(tài)數組變量中。而這么一大堆代碼,僅僅是為了使用三個(gè)整數。 這樣:Shrubbery shrub =Shrubbery.GROUND;會(huì )引起一個(gè)對靜態(tài)變量的引用,如果這個(gè)靜態(tài)變量是final int,那么編譯器會(huì )直接內聯(lián)這個(gè)常數。
一方面說(shuō),使用枚舉變量可以讓你的API更出色,并能提供編譯時(shí)的檢查。所以在通常的時(shí)候你毫無(wú)疑問(wèn)應該為公共API選擇枚舉變量。但是當性能方面有所限制的時(shí)候,你就應該避免這種做法了。
有些情況下,使用ordinal()方法獲取枚舉變量的整數值會(huì )更好一些,舉例來(lái)說(shuō):
[java]
view plaincopyfor(intn=0;n<list.size();n++){
if(list.items[n].e==MyEnum.VAL_X){
//dosomething
}elseif(list.items[n].e==MyEnum.VAL_Y){
//dosomething
}
}
替換為:
[java]
view plaincopyintvalX=MyEnum.VAL_X.ordinal();
intvalY=MyEnum.VAL_Y.ordinal();
intcount=list.size();
MyItemitems=list.items();
for(intn=0;n<count;n++){
intvalItem=items[n].e.ordinal();
if(valItem==valX){
//dosomething
}elseif(valItem==valY){
//dosomething
}
}
會(huì )使性能得到一些改善,但這并不是最終的解決之道。
12) 在私有內部?jì)戎?,考慮用包訪(fǎng)問(wèn)權限替代私有訪(fǎng)問(wèn)權限
[java]
view plaincopypublicclassFoo{
publicclassInner{
publicvoidstuff(){
Foo.this.doStuff(Foo.this.mValue);
}
}
privateintmValue;
publicvoidrun(){
Innerin=newInner();
mValue=27;
in.stuff();
}
privatevoiddoStuff(intvalue){
System.out.println("value:"+value);
}
}
需要注意的關(guān)鍵是:我們定義的一個(gè)私有內部類(lèi)(Foo$Inner),直接訪(fǎng)問(wèn)外部類(lèi)中的一個(gè)私有方法和私有變量。這是合法的,代碼也會(huì )打印出預期的“Value is 27”。 但問(wèn)題是,虛擬機認為從Foo$Inner中直接訪(fǎng)問(wèn)Foo的私有成員是非法的,因為他們是兩個(gè)不同的類(lèi),盡管Java語(yǔ)言允許內部類(lèi)訪(fǎng)問(wèn)外部類(lèi)的私有成員,但是通過(guò)編譯器生成幾個(gè)綜合方法來(lái)橋接這些間隙的。
[java]
view plaincopy/*package*/staticintFoo.access$100(Foofoo){
returnfoo.mValue;
}
/*package*/staticvoidFoo.access%200(Foofoo,intvalue){
foo.duStuff(value);
}
內部類(lèi)會(huì )在外部類(lèi)中任何需要訪(fǎng)問(wèn)mValue字段或調用doStuff方法的地方調用這些靜態(tài)方法。這意味著(zhù)這些代碼將直接存取成員變量表現為通過(guò)存取器方法訪(fǎng)問(wèn)。之前提到過(guò)存取器訪(fǎng)問(wèn)如何比直接訪(fǎng)問(wèn)慢,這例子說(shuō)明,某些語(yǔ)言約會(huì )定導致不可見(jiàn)的性能問(wèn)題。 如果你在高性能的Hotspot中使用這些代碼,可以通過(guò)聲明被內部類(lèi)訪(fǎng)問(wèn)的字段和成員為包訪(fǎng)問(wèn)權限,而非私有。但這也意味著(zhù)這些字段會(huì )被其他處于同一個(gè)包中的類(lèi)訪(fǎng)問(wèn),因此在公共API中不宜采用。
13) 將與內部類(lèi)一同使用的變量聲明在包范圍內
請看下面的類(lèi)定義:
[java]
view plaincopypublicclassFoo{
privateclassInner{
voidstuff(){
Foo.this.doStuff(Foo.this.mValue);
}
}
privateintmValue;
publicvoidrun(){
Innerin=newInner();
mValue=27;
in.stuff();
}
privatevoiddoStuff(intvalue){
System.out.println("Valueis"+value);
}
}
這其中的關(guān)鍵是,我們定義了一個(gè)內部類(lèi)(Foo$Inner),它需要訪(fǎng)問(wèn)外部類(lèi)的私有域變量和函數。這是合法的,并且會(huì )打印出我們希望的結果Value is 27。 問(wèn)題是在技術(shù)上來(lái)講(在幕后)Foo$Inner是一個(gè)完全獨立的類(lèi),它要直接訪(fǎng)問(wèn)Foo的私有成員是非法的。要跨越這個(gè)鴻溝,編譯器需要生成一組方法:
[java]
view plaincopy/*package*/staticintFoo.access$100(Foofoo){
returnfoo.mValue;
}
/*package*/staticvoidFoo.access$200(Foofoo,intvalue){
foo.doStuff(value);
}
內部類(lèi)在每次訪(fǎng)問(wèn)mValueg和gdoStuffg方法時(shí),都會(huì )調用這些靜態(tài)方法。就是說(shuō),上面的代碼說(shuō)明了一個(gè)問(wèn)題,你是在通過(guò)接口方法訪(fǎng)問(wèn)這些成員變量和函數而不是直接調用它們。在前面我們已經(jīng)說(shuō)過(guò),使用接口方法(getter、setter)比直接訪(fǎng)問(wèn)速度要慢。所以這個(gè)例子就是在特定語(yǔ)法下面產(chǎn)生的一個(gè)“隱性的”性能障礙。
通過(guò)將內部類(lèi)訪(fǎng)問(wèn)的變量和函數聲明由私有范圍改為包范圍,我們可以避免這個(gè)問(wèn)題。這樣做可以讓代碼運行更快,并且避免產(chǎn)生額外的靜態(tài)方法。(遺憾的是,這些域和方法可以被同一個(gè)包內的其他類(lèi)直接訪(fǎng)問(wèn),這與經(jīng)典的OO原則相違背。因此當你設計公共API的時(shí)候應該謹慎使用這條優(yōu)化原則)。
14) 緩存
適量使用緩存,不要過(guò)量使用,因為內存有限,能保存路徑地址的就不要存放圖片數據,不經(jīng)常使用的盡量不要緩存,不用時(shí)就清空。
15) 關(guān)閉資源對象
對SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O操作等都應該記得顯示關(guān)閉。
2.視圖優(yōu)化
1)View優(yōu)化
i. 減少不必要的View以及View的嵌套層次。
比如實(shí)現一個(gè)listview中常用的layout,可以使用RelativeLayout減少嵌套,要知道每個(gè)View的對象會(huì )耗費1~2k內存,嵌套層次過(guò)多會(huì )引起頻繁的gc,造成ANR。
ii. 通過(guò)HierarchyViewer查看布局結構
利用HierarchyViewer來(lái)查看View的結構:~/tools/hierarchyviewer,能很清楚地看到RelativeLayout下面的扁平結構,這樣能加快dom的渲染速度。
詳細請參考
http://developer.android.com/guide/developing/tools/hierarchy-viewer.htmliii. 通過(guò)Layoutopt優(yōu)化布局
通過(guò)Android sdk中tools目錄下的layoutopt 命令查看你的布局是否需要優(yōu)化。詳細請參考
http://apps.hi.baidu.com/share/detail/342479422)多線(xiàn)程解決復雜計算
占用CPU較多的數據操作盡可能放在一個(gè)單獨的線(xiàn)程中進(jìn)行,通過(guò)handler等方式把執行的結果交于UI線(xiàn)程顯示。特別是針對的網(wǎng)絡(luò )訪(fǎng)問(wèn),數據庫查詢(xún),和復雜的算法。目前Android提供了AsyncTask,Hanlder、Message和Thread的組合。
對于多線(xiàn)程的處理,如果并發(fā)的線(xiàn)程很多,同時(shí)有頻繁的創(chuàng )建和釋放,可以通過(guò)concurrent類(lèi)的線(xiàn)程池解決線(xiàn)程創(chuàng )建的效率瓶頸。
另外值得注意的是,應用開(kāi)發(fā)中自定義View的時(shí)候,交互部分,千萬(wàn)不要寫(xiě)成線(xiàn)程不斷刷新界面顯示,而是根據TouchListener事件主動(dòng)觸發(fā)界面的更新。
3)布局用Java完成比XML快
一般情況下對于A(yíng)ndroid程序布局往往使用XML文件來(lái)編寫(xiě),這樣可以提高開(kāi)發(fā)效率,但是考慮到代碼的安全性以及執行效率,可以通過(guò)Java代碼執行創(chuàng )建,雖然Android編譯過(guò)的XML是二進(jìn)制的,但是加載XML解析器的效率對于資源占用還是比較大的,Java處理效率比XML快得多,但是對于一個(gè)復雜界面的編寫(xiě),可能需要一些套嵌考慮,如果你思維靈活的話(huà),使用Java代碼來(lái)布局你的Android應用程序是一個(gè)更好的方法。
4)對大型圖片進(jìn)行縮放
圖片讀取是OOM(Out of Memory)的???,當在A(yíng)ndroid手機上直接讀取4M的圖片時(shí),死神一般都會(huì )降臨,所以導致往往自己手機拍攝的照片都不能直接讀取。對大型圖片進(jìn)行縮放有,處理圖片時(shí)我們經(jīng)常會(huì )用到BitmapFactory類(lèi),android系統中讀取位圖Bitmap時(shí)分給虛擬機中圖片的堆棧大小只有8M。用BitmapFactory解碼一張圖片時(shí),有時(shí)也會(huì )遇到該錯誤。這往往是由于圖片過(guò)大造成的。這時(shí)我們需要分配更少的內存空間來(lái)存儲。BitmapFactory.Options.inSampleSize設置恰當的inSampleSize可以使BitmapFactory分配更少的空間以消除該錯誤。Android提供了一種動(dòng)態(tài)計算的,如下:
讀取圖片之前先查看其大?。?div style="height:15px;">
[java]
view plaincopyBitmapFactory.Optionsopts=newBitmapFactory.Options();
opts.inJustDecodeBounds=true;
Bitmapbitmap=BitmapFactory.decodeFile(imageFile,opts);
使用得到的圖片原始寬高計算適合自己的smaplesize
[java]
view plaincopyBitmapFactory.Optionsopts=newBitmapFactory.Options();
opts.inSampleSize=4;//4就代表容量變?yōu)橐郧叭萘康?/4
Bitmapbitmap=BitmapFactory.decodeFile(imageFile,opts);
對于過(guò)時(shí)的Bitmap對象一定要及時(shí)recycle,并且把此對象賦值為null。
[java]
view plaincopybitmap.recycle();
bitmap=null;
5)合理使用ViewStub
ViewStub 是一個(gè)隱藏的,不占用內存空間的視圖對象,它可以在運行時(shí)延遲加載布局資源文件。當ViewStub可見(jiàn),或者調用 inflate()函數時(shí),才會(huì )加載這個(gè)布局資源文件。 該ViewStub在加載視圖時(shí)在父容器中替換它本身。因此,ViewStub會(huì )一直存在于視圖中,直到調用setVisibility(int) 或者inflate()為止。ViewStub的布局參數會(huì )隨著(zhù)加載的視圖數一同被添加到ViewStub父容器。同樣,你也可以通過(guò)使用inflatedId屬性來(lái)定義或重命名要加載的視圖對象的Id值。所以我們可以使用ViewStub延遲加載某些比較復雜的layout,動(dòng)態(tài)加載View,采用ViewStub 避免一些不經(jīng)常的視圖長(cháng)期握住引用。
詳細請參考http://developer.android.com/reference/android/view/ViewStub.html
6)針對ListView的性能優(yōu)化
i. 復用convertView。
ii. 在getItemView中,判斷convertView是否為空,如果不為空,可復用。如果couvertview中的view需要添加listerner,代碼一定要在if(convertView==null){}之外。
iii. 異步加載圖片,item中如果包含有web image,那么最好異步加載。
iv. 快速滑動(dòng)時(shí)不顯示圖片
當快速滑動(dòng)列表時(shí)(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view,可以不顯示出來(lái);而處于其他兩種狀態(tài)(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來(lái)。
v. item盡可能的減少使用的控件和布局的層次;背景色與cacheColorHint設置相同顏色;ListView中item的布局至關(guān)重要,必須盡可能的減少使用的控件,布局。RelativeLayout是絕對的利器,通過(guò)它可以減少布局的層次。同時(shí)要盡可能的復用控件,這樣可以減少ListView的內存使用,減少滑動(dòng)時(shí)gc次數。ListView的背景色與cacheColorHint設置相同顏色,可以提高滑動(dòng)時(shí)的渲染性能。
vi. getView優(yōu)化
ListView中g(shù)etView是性能是關(guān)鍵,這里要盡可能的優(yōu)化。getView方法中要重用view;getView方法中不能做復雜的邏輯計算,特別是數據庫和網(wǎng)絡(luò )訪(fǎng)問(wèn)操作,否則會(huì )嚴重影響滑動(dòng)時(shí)的性能。優(yōu)化如下所示:
[java]
view plaincopy@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
Log.d("MyAdapter","Position:"+position+"---"+String.valueOf(System.currentTimeMillis()));
finalLayoutInflaterinflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Viewv=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;
}
建議改為:
[java]
view plaincopy@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
Log.d("Adapter","Position:"+position+":"+String.valueOf(System.currentTimeMillis()));
ViewHolderholder;
if(convertView==null){
finalLayoutInflaterinflater=(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{
ImageViewicon;
TextViewtext;
}
}
以上是Google IO大會(huì )上給出的優(yōu)化建議,經(jīng)過(guò)嘗試ListView確實(shí)流暢了許多。使用1000條記錄,經(jīng)測試第一種方式耗時(shí):25211ms,第二種方式耗時(shí):16528ms。
7)其他
i. 分辨率適配
-ldpi,-mdpi, -hdpi配置不同精度資源,系統會(huì )根據設備自適應,包括drawable, layout,style等不同資源。
ii. 盡量使用dp(density independent pixel)開(kāi)發(fā),不用px(pixel)。
iii. 多用wrap_content, fill_parent
iv. 拋棄AbsoluteLayout
v. 使用9patch(通過(guò)~/tools/draw9patch.bat啟動(dòng)應用程序),png格式
vi. 采用<merge> 優(yōu)化布局層數;采用<include >來(lái)共享布局。
vii. 將Acitivity中的Window的背景圖設置為空。getWindow().setBackgroundDrawable(null);android的默認背景是不是為空。
viii. View中設置緩存屬性.setDrawingCache為true。
3.網(wǎng)絡(luò )優(yōu)化
1)避免頻繁網(wǎng)絡(luò )請求
訪(fǎng)問(wèn)server端時(shí),建立連接本身比傳輸需要跟多的時(shí)間,如非必要,不要將一交互可以做的事情分成多次交互(這需要與Server端協(xié)調好)。有效管理Service 后臺服務(wù)就相當于一個(gè)持續運行的Acitivity,如果開(kāi)發(fā)的程序后臺都會(huì )一個(gè)service不停的去服務(wù)器上更新數據,在不更新數據的時(shí)候就讓它sleep,這種方式是非常耗電的,通常情況下,可以使用AlarmManager來(lái)定時(shí)啟動(dòng)服務(wù)。如下所示,第30分鐘執行一次。
[java]
view plaincopyAlarmManageralarmManager=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intentintent=newIntent(context,MyService.class);
PendingIntentpendingIntent=PendingIntent.getService(context,0,intent,0);
longinterval=DateUtils.MINUTE_IN_MILLIS*30;
longfirstWake=System.currentTimeMillis()+interval;
am.setRepeating(AlarmManager.RTC,firstWake,interval,pendingIntent);
2)數據壓縮
傳輸數據經(jīng)過(guò)壓縮 目前大部門(mén)網(wǎng)站都支持GZIP壓縮,所以在進(jìn)行大數據量下載時(shí),盡量使用GZIP方式下載,可以減少網(wǎng)絡(luò )流量,一般是壓縮前數據大小的30%左右。
[java]
view plaincopyHttpGetrequest=newHttpGet("http://example.com/gzipcontent");
HttpResponseresp=newDefaultHttpClient().execute(request);
HttpEntityentity=response.getEntity();
InputStreamcompressed=entity.getContent();
InputStreamrawData=newGZIPInputStream(compressed);
3)使用線(xiàn)程池
線(xiàn)程池,分為核心線(xiàn)程池和普通線(xiàn)程池,下載圖片等耗時(shí)任務(wù)放置在普通線(xiàn)程池,避免耗時(shí)任務(wù)阻塞線(xiàn)程池后,導致所有異步任務(wù)都必須等待。
4)選擇合適的數據格式傳輸形式
其中Tree Parse 是DOM解析 Event/Stream是SAX方式解析。
很明顯,使用流的方式解析效率要高一些,因為DOM解析是在對整個(gè)文檔讀取完后,再根據節點(diǎn)層次等再組織起來(lái)。而流的方式是邊讀取數據邊解析,數據讀取完后,解析也就完畢了。在數據格式方面,JSON和Protobuf效率明顯比XML好很多,XML和JSON大家都很熟悉。
從上面的圖中可以得出結論就是盡量使用SAX等邊讀取邊解析的方式來(lái)解析數據,針對移動(dòng)設備,最好能使用JSON之類(lèi)的輕量級數據格式為佳。
1)其他
設置連接超時(shí)時(shí)間和響應超時(shí)時(shí)間。Http請求按照業(yè)務(wù)需求,分為是否可以緩存和不可緩存,那么在無(wú)網(wǎng)絡(luò )的環(huán)境中,仍然通過(guò)緩存的HttpResponse瀏覽部分數據,實(shí)現離線(xiàn)閱讀。
2.數據庫相關(guān)
1)相對于封裝過(guò)的ContentProvider而言,使用原始SQL語(yǔ)句執行效率高,比如使用方法rawQuery、execSQL的執行效率比較高。
2)對于需要一次性修改多個(gè)數據時(shí),可以考慮使用SQLite的事務(wù)方式批量處理。
3)批量插入多行數據使用InsertHelper或者bulkInsert方法
4)有些能用文件操作的,盡量采用文件操作,文件操作的速度比數據庫的操作要快10倍左右。
3.性能測試
對于A(yíng)ndroid平臺上軟件的性能測試可以通過(guò)以下幾種方法來(lái)分析效率瓶頸,目前Google在A(yíng)ndroid軟件開(kāi)發(fā)過(guò)程中已經(jīng)引入了多種測試工具包,比如Unit測試工程,調試類(lèi),還有模擬器的Dev Tools都可以直接反應執行性能。
1)在模擬器上的Dev Tools可以激活屏幕顯示當前的FPS,CPU使用率,可以幫助我們測試一些3D圖形界面的性能。
2)一般涉及到網(wǎng)絡(luò )應用的程序,在效率上和網(wǎng)速有很多關(guān)系,這里需要多次的調試才能實(shí)際了解。
3)對于邏輯算法的效率執行,我們使用Android上最普遍的,計算執行時(shí)間來(lái)查看:
[java]
view plaincopylongstart=System.currentTimeMillis();
//dosomething
longduration=System.currentTimeMillis()-start;
最終duration保存著(zhù)實(shí)際處理該方法需要的毫秒數。
4)gc效率跟蹤,如果你執行的應用比較簡(jiǎn)單,可以在DDMS中查看下Logcat的VM釋放內存情況,大概模擬下那些地方可以緩存數據或改進(jìn)算法的。
5)線(xiàn)程的使用和同步,Android平臺上給我們提供了豐富的多任務(wù)同步方法,但在深層上并沒(méi)有過(guò)多的比如自旋鎖等高級應用,不過(guò)對于Service和 appWidget而言,他們實(shí)際的產(chǎn)品中都應該以多線(xiàn)程的方式處理,以釋放CPU時(shí)間,對于線(xiàn)程和堆內存的查看這些都可以在DDMS中看到。
6)利用traceview和monkey等工具測試應用。
7)利用layoutopt和ninepatch等工具優(yōu)化視圖。
4.結束語(yǔ)
本文給出了一些Android 移動(dòng)開(kāi)發(fā)中常見(jiàn)的優(yōu)化方法,多數情況下利用這些優(yōu)化方法優(yōu)化后的代碼,執行效率有明顯的提高,內存溢出情況也有所改善。在實(shí)際應用中對程序的優(yōu)化一定要權衡是否是必須的,因為優(yōu)化可能會(huì )帶來(lái)諸如增加BUG,降低代碼的可讀性,降低代碼的移植性等不良效果。
希望不要盲目?jì)?yōu)化,請先確定存在問(wèn)題,再進(jìn)行優(yōu)化。并且你知道當前系統的性能,否則無(wú)法衡量你進(jìn)行嘗試所得到的性能提升。
希望本文能給大家切實(shí)帶來(lái)幫助。歡迎就上述問(wèn)題進(jìn)一步交流。如有發(fā)現錯誤或不足,請斧正。
參考:
[1].
http://developer.android.com/guide/practices/design/performance.html[2].
http://blog.csdn.net/wanglong0537/article/details/6412601[3].
http://apps.hi.baidu.com/share/detail/34247942[4].
http://blog.csdn.net/feng88724/article/details/6590064[5]. 陳彧堃,《Android應用開(kāi)發(fā)中的優(yōu)化和設計》。