關(guān)于android性能,內(nèi)存優(yōu)化
看了些資料整理了下纷铣,安卓的性能和內(nèi)存優(yōu)化的一些方法和注意事項(xiàng)卵史。分享出來。
隨著技術(shù)的發(fā)展搜立,智能手機(jī)硬件配置越來越高以躯,可是它和現(xiàn)在的PC相比,其運(yùn)算能力啄踊,續(xù)航能力忧设,存儲空間等都還是受到很大的限制,同時用戶對手機(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)該考慮的事情蒜哀。同時,我們應(yīng)該時刻牢記吏砂,寫出高效代碼的兩條基本的原則:(1)不要做不必要的事撵儿;(2)不要分配不必要的內(nèi)存。
我從去年開始接觸Android開發(fā)狐血,以下結(jié)合自己的一點(diǎn)項(xiàng)目經(jīng)驗(yàn)淀歇,同時參考了Google的優(yōu)化文檔和網(wǎng)上的諸多技術(shù)大牛給出的意見,整理出這份文檔匈织。
1.內(nèi)存優(yōu)化
Android系統(tǒng)對每個軟件所能使用的RAM空間進(jìn)行了限制(如:Nexusone對每個軟件的內(nèi)存限制是24M)浪默,同時Java語言本身比較消耗內(nèi)存,dalvik虛擬機(jī)也要占用一定的內(nèi)存空間缀匕,所以合理使用內(nèi)存纳决,彰顯出一個程序員的素質(zhì)和技能。
1)了解JIT
即時編譯(Just-in-timeCompilation乡小,JIT)阔加,又稱動態(tài)轉(zhuǎn)譯(DynamicTranslation),是一種通過在運(yùn)行時將字節(jié)碼翻譯為機(jī)器碼满钟,從而改善字節(jié)碼編譯語言性能的技術(shù)胜榔。即時編譯前期的兩個運(yùn)行時理論是字節(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為每個線程都建立了臨時對象池,可以使創(chuàng)建對象的代價(jià)變得小一些择浊,但是分配內(nèi)存永遠(yuǎn)都比不分配內(nèi)存的代價(jià)大戴卜。如果你在用戶界面循環(huán)中分配對象內(nèi)存,就會引發(fā)周期性的垃圾回收琢岩,用戶就會覺得界面像打嗝一樣一頓一頓的投剥。所以,除非必要担孔,應(yīng)盡量避免盡力對象的實(shí)例江锨。下面的例子將幫助你理解這條原則:當(dāng)你從用戶輸入的數(shù)據(jù)中截取一段字符串時,盡量使用substring函數(shù)取得原始數(shù)據(jù)的一個子串糕篇,而不是為子串另外建立一份拷貝啄育。這樣你就有一個新的String對象,它與原始數(shù)據(jù)共享一個char數(shù)組拌消。如果你有一個函數(shù)返回一個String對象挑豌,而你確切的知道這個字符串會被附加到一個StringBuffer,那么墩崩,請改變這個函數(shù)的參數(shù)和實(shí)現(xiàn)方式氓英,直接把結(jié)果附加到StringBuffer中,而不要再建立一個短命的臨時對象鹦筹。
一個更極端的例子是铝阐,把多維數(shù)組分成多個一維數(shù)組:
int數(shù)組比Integer數(shù)組好,這也概括了一個基本事實(shí)铐拐,兩個平行的int數(shù)組比(int,int)對象數(shù)組性能要好很多徘键。同理,這試用于所有基本類型的組合遍蟋。如果你想用一種容器存儲(Foo,Bar)元組吹害,嘗試使用兩個單獨(dú)的Foo[]數(shù)組和Bar[]數(shù)組,一定比(Foo,Bar)數(shù)組效率更高匿值。(也有例外的情況赠制,就是當(dāng)你建立一個API,讓別人調(diào)用它的時候。這時候你要注重對API接口的設(shè)計(jì)而犧牲一點(diǎn)兒速度钟些。當(dāng)然在API的內(nèi)部烟号,你仍要盡可能的提高代碼的效率)
總體來說,就是避免創(chuàng)建短命的臨時對象政恍。減少對象的創(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++中一個好的習(xí)慣辕棚,因?yàn)榫幾g器會內(nèi)聯(lián)這些訪問,并且如果需要約束或者調(diào)試這些域的訪問邓厕,你可以在任何時間添加代碼逝嚎。而在Android中,這不是一個好的做法详恼。虛方法調(diào)用的代價(jià)比直接字段訪問高昂許多补君。通常根據(jù)面向?qū)ο笳Z言的實(shí)踐,在公共接口中使用Getters和Setters是有道理的昧互,但在一個字段經(jīng)常被訪問的類中宜采用直接訪問挽铁。無JIT時,直接字段訪問大約比調(diào)用getter訪問快3倍硅堆。有JIT時(直接訪問字段開銷等同于局部變量訪問)屿储,要快7倍。
5)???????將成員緩存到本地
訪問成員變量比訪問本地變量慢得多渐逃,下面一段代碼:
for(inti =0; i
dumpItem(this.mItems);
}
最好改成這樣:
intcount =this.mCount;
Item[] items =this.mItems;
for(inti =0; i < count; i++)? {
dumpItems(items);
}
另一個相似的原則是:永遠(yuǎn)不要在for的第二個條件中調(diào)用任何方法。如下面方法所示民褂,在每次循環(huán)的時候都會調(diào)用getCount()方法茄菊,這樣做比你在一個int先把結(jié)果保存起來開銷大很多。
for(inti =0; i
dumpItems(this.getItem(i));
}
同樣如果你要多次訪問一個變量鸿摇,也最好先為它建立一個本地變量唤锉,例如:
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!";
必以其會生成一個叫做clinit的初始化類的方法,當(dāng)類第一次被使用的時候這個方法會被執(zhí)行辽幌。方法會將42賦給intVal增淹,然后把一個指向類中常量表 的引用賦給strVal。當(dāng)以后要用到這些值的時候乌企,會在成員變量表中查找到他們虑润。 下面我們做些改進(jìn),使用“final”關(guān)鍵字:
static final int intVal = 42;static final String strVal = "Hello, world!";
現(xiàn)在加酵,類不再需要clinit方法拳喻,因?yàn)樵诔蓡T變量初始化的時候,會將常量直接保存到類文件中猪腕。用到intVal的代碼被直接替換成42冗澈,而使用strVal的會指向一個字符串常量,而不是使用成員變量陋葡。
將一個方法或類聲明為final不會帶來性能的提升亚亲,但是會幫助編譯器優(yōu)化代碼。舉例說脖岛,如果編譯器知道一個getter方法不會被重載朵栖,那么編譯器會對其采用內(nèi)聯(lián)調(diào)用。
你也可以將本地變量聲明為final柴梆,同樣陨溅,這也不會帶來性能的提升。使用“final”只能使本地變量看起來更清晰些(但是也有些時候這是必須的绍在,比如在使用匿名內(nèi)部類的時候)门扇。
2)???????使用改進(jìn)的For循環(huán)語法
改進(jìn)for循環(huán)(有時被稱為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)生一個額外的對本地變量的存儲操作(對變量a的存取)這樣會比one()多出4個字節(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)空閑時會用匯編代碼調(diào)用來替代library方法没酣,這可能比JIT中生成的等價(jià)的最好的Java代碼還要好。
i.??? 當(dāng)你在處理字串的時候卵迂,不要吝惜使用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類,提供了各種時間轉(zhuǎn)換墨闲,都是非常高效的方法今妄。
詳細(xì)請參考http://developer.android.com/reference/android/text/format/package-summary.html
iv.??? TextUtils類
對于字符串處理Android為我們提供了一個簡單實(shí)用的TextUtils類,如果處理比較簡單的內(nèi)容不用去思考正則表達(dá)式不妨試試這個在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í)現(xiàn)的功能不是很多,直接從Object上繼承跌造,通過JNI的方式直接在C底層執(zhí)行杆怕。
詳細(xì)請參考http://developer.android.com/reference/android/os/MemoryFile.html
在此,只簡單列舉幾個常用的類和方法壳贪,更多的是要靠平時的積累和發(fā)現(xiàn)陵珍。多閱讀Google給的幫助文檔時很有益的。
5)???????合理利用本地方法
本地方法并不是一定比Java高效违施。最起碼互纯,Java和native之間過渡的關(guān)聯(lián)是有消耗的,而JIT并不能對此進(jìn)行優(yōu)化醉拓。當(dāng)你分配本地資源時 (本地堆上的內(nèi)存伟姐,文件說明符等),往往很難實(shí)時的回收這些資源亿卤。同時你也需要在各種結(jié)構(gòu)中編譯你的代碼(而非依賴JIT)愤兵。甚至可能需要針對相同的架構(gòu) 來編譯出不同的版本:針對ARM處理器的GI編譯的本地代碼,并不能充分利用Nexus One上的ARM排吴,而針對Nexus One上ARM編譯的本地代碼不能在G1的ARM上運(yùn)行秆乳。當(dāng)你想部署程序到存在本地代碼庫的Android平臺上時,本地代碼才顯得尤為有用钻哩,而并非為了Java應(yīng)用程序的提速屹堰。
6)???????復(fù)雜算法盡量用C完成
復(fù)雜算法盡量用C或者C++完成,然后用JNI調(diào)用街氢。但是如果是算法比較單間扯键,不必這么麻煩,畢竟JNI調(diào)用也會花一定的時間珊肃。請權(quán)衡荣刑。
7)???????減少不必要的全局變量
盡量避免static成員變量引用資源耗費(fèi)過多的實(shí)例,比如Context馅笙。Android提供了很健全的消息傳遞機(jī)制(Intent)和任務(wù)模型(Handler),可以通過傳遞或事件的方式,防止一些不必要的全局變量厉亏。
8)???????不要過多指望gc
Java的gc使用的是一個有向圖董习,判斷一個對象是否有效看的是其他的對象能到達(dá)這個對象的頂點(diǎn),有向圖的相對于鏈表爱只、二叉樹來說開銷是可想而知皿淋。所以不要過多指望gc。將不用的對象可以把它指向NULL恬试,并注意代碼質(zhì)量窝趣。同時,顯示讓系統(tǒng)gc回收忘渔,例如圖片處理時高帖,
if(bitmap.isRecycled()==false) {//如果沒有回收bitmap.recycle();
}
9)???????了解Java四種引用方式
JDK 1.2版本開始,把對象的引用分為4種級別畦粮,從而使程序能更加靈活地控制對象的生命周期散址。這4種級別由高到低依次為:強(qiáng)引用、軟引用宣赔、弱引用和虛引用预麸。
i.??? 強(qiáng)引用(StrongReference)
強(qiáng)引用是使用最普遍的引用。如果一個對象具有強(qiáng)引用儒将,那垃圾回收器絕不會回收它吏祸。當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯誤钩蚊,使程序異常終止贡翘,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題。
ii.??? 軟引用(SoftReference)
如果一個對象只具有軟引用砰逻,則內(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)存。不過鉴逞,由于垃圾回收器是一個優(yōu)先級很低的線程遗菠,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象联喘。
iv.??? 虛引用(PhantomReference)
顧名思義,就是形同虛設(shè)辙纬。與其他幾種引用都不同,虛引用并不會決定對象的生命周期叭喜。如果一個對象僅持有虛引用贺拣,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收捂蕴。了解并熟練掌握這4中引用方式譬涡,選擇合適的對象應(yīng)用方式,對內(nèi)存的回收是很有幫助的啥辨。
詳細(xì)請參考http://blog.csdn.net/feng88724/article/details/6590064
10)???? 使用實(shí)體類比接口好
假設(shè)你有一個HashMap對象涡匀,你可以將它聲明為HashMap或者M(jìn)ap:
Map map1 = new HashMap();
HashMap map2 = new HashMap();
哪個更好呢?
按照傳統(tǒng)的觀點(diǎn)Map會更好些溉知,因?yàn)檫@樣你可以改變他的具體實(shí)現(xiàn)類陨瘩,只要這個類繼承自Map接口。傳統(tǒng)的觀點(diǎn)對于傳統(tǒng)的程序是正確的级乍,但是它并不適合嵌入式系統(tǒng)舌劳。調(diào)用一個接口的引用會比調(diào)用實(shí)體類的引用多花費(fèi)一倍的時間。如果HashMap完全適合你的程序玫荣,那么使用Map就沒有什么價(jià)值甚淡。如果有些地方你不能確定,先避免使用Map捅厂,剩下的交給IDE提供的重構(gòu)功能好了贯卦。(當(dāng)然公共API是一個例外:一個好的API常常會犧牲一些性能)
11)???? 避免使用枚舉
枚舉變量非常方便,但不幸的是它會犧牲執(zhí)行的速度和并大幅增加文件體積焙贷。例如:
publicclassFoo {publicenumShrubbery { GROUND, CRAWLING, HANGING }
}
會產(chǎn)生一個900字節(jié)的.class文件(FooShubbery.class)撵割。在它被首次調(diào)用時,這個類會調(diào)用初始化方法來準(zhǔn)備每個枚舉變量盈厘。每個枚舉項(xiàng)都會被聲明成一個靜態(tài)變量睁枕,并被賦值。然后將這些靜態(tài)變量放在一個名為”Shubbery.class)沸手。在它被首次調(diào)用時外遇,這個類會調(diào)用初始化方法來準(zhǔn)備每個枚舉變量。每個枚舉項(xiàng)都會被聲明成一個靜態(tài)變量契吉,并被賦值跳仿。然后將這些靜態(tài)變量放在一個名為”VALUES”的靜態(tài)數(shù)組變量中。而這么一大堆代碼捐晶,僅僅是為了使用三個 整數(shù)菲语。
這樣:Shrubbery shrub =Shrubbery.GROUND妄辩;會引起一個對靜態(tài)變量的引用,如果這個靜態(tài)變量是final int山上,那么編譯器會直接內(nèi)聯(lián)這個常數(shù)眼耀。
一方面說,使用枚舉變量可以讓你的API更出色佩憾,并能提供編譯時的檢查哮伟。所以在通常的時候你毫無疑問應(yīng)該為公共API選擇枚舉變量。但是當(dāng)性能方面有所限制的時候妄帘,你就應(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)鍵是:我們定義的一個私有內(nèi)部類(FooInner)碎税,直接訪問外部類中的一個私有方法和私有變量。這是合法的勾邦,代碼也會打印出預(yù)期的“Valueis27”蚣录。但問題是,虛擬機(jī)認(rèn)為從FooInner)眷篇,直接訪問外部類中的一個私有方法和私有變量萎河。這是合法的,代碼也會打印出預(yù)期的“Valueis27”蕉饼。但問題是虐杯,虛擬機(jī)認(rèn)為從FooInner中直接訪問Foo的私有成員是非法的,因?yàn)樗麄兪莾蓚€不同的類昧港,盡管Java語言允許內(nèi)部類訪問外部類的私有成員擎椰,但是通過編譯器生成幾個綜合方法來橋接這些間隙的。
/*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)限趾代,而非私有贯底。但這也意味著這些字段會被其他處于同一個包中的類訪問,因此在公共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)鍵是禽捆,我們定義了一個內(nèi)部類(FooInner)笙什,它需要訪問外部類的私有域變量和函數(shù)。這是合法的胚想,并且會打印出我們希望的結(jié)果Valueis27琐凭。問題是在技術(shù)上來講(在幕后)FooInner),它需要訪問外部類的私有域變量和函數(shù)顿仇。這是合法的淘正,并且會打印出我們希望的結(jié)果Valueis27。問題是在技術(shù)上來講(在幕后)FooInner是一個完全獨(dú)立的類臼闻,它要直接訪問Foo的私有成員是非法的。要跨越這個鴻溝囤采,編譯器需要生成一組方法:
/*package*/staticintFoo.access$100(Foo foo) {returnfoo.mValue;
}/*package*/staticvoidFoo.access$200(Foo foo,intvalue) {
foo.doStuff(value);
}
內(nèi)部類在每次訪問mValueg和gdoStuffg方法時述呐,都會調(diào)用這些靜態(tài)方法。就是說蕉毯,上面的代碼說明了一個問題乓搬,你是在通過接口方法訪問這些成員變量和函數(shù)而不是直接調(diào)用它們。在前面我們已經(jīng)說過代虾,使用接口方法(getter进肯、setter)比直接訪問速度要慢。所以這個例子就是在特定語法下面產(chǎn)生的一個“隱性的”性能障礙棉磨。通過將內(nèi)部類訪問的變量和函數(shù)聲明由私有范圍改為包范圍江掩,我們可以避免這個問題。這樣做可以讓代碼運(yùn)行更快乘瓤,并且避免產(chǎn)生額外的靜態(tài)方法环形。(遺憾的是,這些域和方法可以被同一個包內(nèi)的其他類直接訪問衙傀,這與經(jīng)典的OO原則相違背抬吟。因此當(dāng)你設(shè)計(jì)公共API的時候應(yīng)該謹(jǐn)慎使用這條優(yōu)化原則)。
14)???? 緩存
適量使用緩存统抬,不要過量使用火本,因?yàn)閮?nèi)存有限,能保存路徑地址的就不要存放圖片數(shù)據(jù)聪建,不經(jīng)常使用的盡量不要緩存钙畔,不用時就清空。
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)一個listview中常用的layout,可以使用RelativeLayout減少嵌套愉烙,要知道每個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ù)操作盡可能放在一個單獨(dú)的線程中進(jìn)行,通過handler等方式把執(zhí)行的結(jié)果交于UI線程顯示蔗包。特別是針對的網(wǎng)絡(luò)訪問鸿秆,數(shù)據(jù)庫查詢季稳,和復(fù)雜的算法。目前Android提供了AsyncTask,Hanlder缎罢、Message和Thread的組合浮创。對于多線程的處理初厚,如果并發(fā)的線程很多炎码,同時有頻繁的創(chuàng)建和釋放,可以通過concurrent類的線程池解決線程創(chuàng)建的效率瓶頸裆装。另外值得注意的是踱承,應(yīng)用開發(fā)中自定義View的時候,交互部分米母,千萬不要寫成線程不斷刷新界面顯示,而是根據(jù)TouchListener事件主動觸發(fā)界面的更新铁瞒。
3)???????布局用Java完成比XML快
一般情況下對于Android程序布局往往使用XML文件來編寫妙色,這樣可以提高開發(fā)效率,但是考慮到代碼的安全性以及執(zhí)行效率慧耍,可以通過Java代碼執(zhí)行創(chuàng)建身辨,雖然Android編譯過的XML是二進(jìn)制的,但是加載XML解析器的效率對于資源占用還是比較大的芍碧,Java處理效率比XML快得多煌珊,但是對于一個復(fù)雜界面的編寫,可能需要一些套嵌考慮泌豆,如果你思維靈活的話定庵,使用Java代碼來布局你的Android應(yīng)用程序是一個更好的方法。
4)???????對大型圖片進(jìn)行縮放
圖片讀取是OOM(Out of Memory)的常客蔬浙,當(dāng)在Android手機(jī)上直接讀取4M的圖片時猪落,死神一般都會降臨,所以導(dǎo)致往往自己手機(jī)拍攝的照片都不能直接讀取畴博。對大型圖片進(jìn)行縮放處理圖片時我們經(jīng)常會用到BitmapFactory類笨忌,android系統(tǒng)中讀取位圖Bitmap時分給虛擬機(jī)中圖片的堆棧大小只有8M。用BitmapFactory解碼一張圖片時俱病,有時也會遇到該錯誤官疲。這往往是由于圖片過大造成的。這時我們需要分配更少的內(nèi)存空間來存儲亮隙。BitmapFactory.Options.inSampleSize設(shè)置恰當(dāng)?shù)膇nSampleSize可以使BitmapFactory分配更少的空間以消除該錯誤途凫。Android提供了一種動態(tài)計(jì)算的,如下:
讀取圖片之前先查看其大幸缥恰:
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);
對于過時的Bitmap對象一定要及時recycle颖榜,并且把此對象賦值為null。
bitmap.recycle();
bitmap=null;
5)???????合理使用ViewStub
ViewStub 是一個隱藏的煤裙,不占用內(nèi)存空間的視圖對象,它可以在運(yùn)行時延遲加載布局資源文件噪漾。當(dāng)ViewStub可見硼砰,或者調(diào)用 inflate()函數(shù)時,才會加載這個布局資源文件欣硼。 該ViewStub在加載視圖時在父容器中替換它本身题翰。因此,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.??? 快速滑動時不顯示圖片
當(dāng)快速滑動列表時(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view咆疗,可以不顯示出來漓帚;而處于其他兩種狀 態(tài)(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來午磁。
v.??? item盡可能的減少使用的控件和布局的層次尝抖;背景色與cacheColorHint設(shè)置相同顏色;ListView中item的布局至關(guān)重要迅皇,必須盡可 能的減少使用的控件昧辽,布局〉峭牵 RelativeLayout是絕對的利器搅荞,通過它可以減少布局的層次。同時要盡可能的復(fù)用控件框咙,這樣可以減少 ListView的內(nèi)存使用咕痛,減少滑動時gc次數(shù)。ListView的背景色與cacheColorHint設(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)重影響滑動時的性能愉粤。優(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)測試第一種方式耗時:25211ms,第二種方式耗時: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端時,建立連接本身比傳輸需要跟多的時間谱姓,如非必要借尿,不要將一交互可以做的事情分成多次交互(這需要與Server端協(xié)調(diào)好)。有效管理Service 后臺服務(wù)就相當(dāng)于一個持續(xù)運(yùn)行的Acitivity屉来,如果開發(fā)的程序后臺都會一個service不停的去服務(wù)器上更新數(shù)據(jù)路翻,在不更新數(shù)據(jù)的時候就讓它sleep,這種方式是非常耗電的茄靠,通常情況下茂契,可以使用AlarmManager來定時啟動服務(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ù)量下載時脐雪,盡量使用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)???????使用線程池
線程池召锈,分為核心線程池和普通線程池,下載圖片等耗時任務(wù)放置在普通線程池获询,避免耗時任務(wù)阻塞線程池后,導(dǎo)致所有異步任務(wù)都必須等待拐袜。
4)???????選擇合適的數(shù)據(jù)格式傳輸形式
其中Tree Parse 是DOM解析 Event/Stream是SAX方式解析吉嚣。
很明顯,使用流的方式解析效率要高一些蹬铺,因?yàn)镈OM解析是在對整個文檔讀取完后尝哆,再根據(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è)置連接超時時間和響應(yīng)超時時間。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)???????對于需要一次性修改多個數(shù)據(jù)時郭卫,可以考慮使用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í)行時間來查看:
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時間坝初,對于線程和堆內(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)錯誤或不足年扩,請斧正。