新聞中心
這篇文章主要介紹一些小細(xì)節(jié)的優(yōu)化技巧,雖然這些小技巧不能較大幅度的提升應(yīng)用性能,但是恰當(dāng)?shù)倪\(yùn)用這些小技巧并發(fā)生累積效應(yīng)的時(shí)候,對(duì)于整個(gè)App的性能提升還是有不小作用的。通常來(lái)說(shuō),選擇合適的算法與數(shù)據(jù)結(jié)構(gòu)會(huì)是你首要考慮的因素,在這篇文章中不會(huì)涉及這方面的知識(shí)點(diǎn)。你應(yīng)該使用這篇文章中的小技巧作為平時(shí)寫代碼的習(xí)慣,這樣能夠提升代碼的效率。

10余年的贊皇網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。網(wǎng)絡(luò)營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整贊皇建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)從事“贊皇網(wǎng)站設(shè)計(jì)”,“贊皇網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
通常來(lái)說(shuō),高效的代碼需要滿足下面兩個(gè)原則:
不要做冗余的工作
盡量避免執(zhí)行過(guò)多的內(nèi)存分配操作
在優(yōu)化App時(shí)其中一個(gè)難點(diǎn)就是讓App能在各種型號(hào)的設(shè)備上運(yùn)行。不同版本的虛擬機(jī)在不同的處理器上會(huì)有不同的運(yùn)行速度。你甚至不能簡(jiǎn)單的認(rèn)為“設(shè)備X的速度是設(shè)備Y的F倍”,然后還用這種倍數(shù)關(guān)系去推測(cè)其他設(shè)備。另外,在模擬器上的運(yùn)行速度和在實(shí)際設(shè)備上的速度沒(méi)有半點(diǎn)關(guān)系。同樣,設(shè)備有沒(méi)有JIT也對(duì)運(yùn)行速度有重大影響:在有JIT情況下的***化代碼不一定在沒(méi)有JIT的情況下也是***的。
為了確保App在各設(shè)備上都能良好運(yùn)行,就要確保你的代碼在不同檔次的設(shè)備上都盡可能的優(yōu)化。
避免創(chuàng)建不必要的對(duì)象
創(chuàng)建對(duì)象從來(lái)不是免費(fèi)的。Generational GC可以使臨時(shí)對(duì)象的分配變得廉價(jià)一些,但是執(zhí)行分配內(nèi)存總是比不執(zhí)行分配操作更昂貴。
隨著你在App中分配更多的對(duì)象,你可能需要強(qiáng)制gc,而gc操作會(huì)給用戶體驗(yàn)帶來(lái)一點(diǎn)點(diǎn)卡頓。雖然從Android 2.3開始,引入了并發(fā)gc,它可以幫助你顯著提升gc的效率,減輕卡頓,但畢竟不必要的內(nèi)存分配操作還是應(yīng)該盡量避免。
因此請(qǐng)盡量避免創(chuàng)建不必要的對(duì)象,有下面一些例子來(lái)說(shuō)明這個(gè)問(wèn)題:
如果你需要返回一個(gè)String對(duì)象,并且你知道它最終會(huì)需要連接到一個(gè)StringBuffer,請(qǐng)修改你的函數(shù)實(shí)現(xiàn)方式,避免直接進(jìn)行連接操作,應(yīng)該采用創(chuàng)建一個(gè)臨時(shí)對(duì)象來(lái)做字符串的拼接這個(gè)操作。
當(dāng)從已經(jīng)存在的數(shù)據(jù)集中抽取出String的時(shí)候,嘗試返回原數(shù)據(jù)的substring對(duì)象,而不是創(chuàng)建一個(gè)重復(fù)的對(duì)象。使用substring的方式,你將會(huì)得到一個(gè)新的String對(duì)象,但是這個(gè)string對(duì)象是和原string共享內(nèi)部char[]空間的。
一個(gè)稍微激進(jìn)點(diǎn)的做法是把所有多維的數(shù)據(jù)分解成一維的數(shù)組:
一組int數(shù)據(jù)要比一組Integer對(duì)象要好很多??梢缘弥?,兩組一維數(shù)組要比一個(gè)二維數(shù)組更加的有效率。同樣的,這個(gè)道理可以推廣至其他原始數(shù)據(jù)類型。
如果你需要實(shí)現(xiàn)一個(gè)數(shù)組用來(lái)存放(Foo,Bar)的對(duì)象,記住使用Foo[]與Bar[]要比(Foo,Bar)好很多。(例外的是,為了某些好的API的設(shè)計(jì),可以適當(dāng)做一些妥協(xié)。但是在自己的代碼內(nèi)部,你應(yīng)該多多使用分解后的容易)。
通常來(lái)說(shuō),需要避免創(chuàng)建更多的臨時(shí)對(duì)象。更少的對(duì)象意味者更少的gc動(dòng)作,gc會(huì)對(duì)用戶體驗(yàn)有比較直接的影響。
選擇Static而不是Virtual
如果你不需要訪問(wèn)一個(gè)對(duì)象的值,請(qǐng)保證這個(gè)方法是static類型的,這樣方法調(diào)用將快15%-20%。這是一個(gè)好的習(xí)慣,因?yàn)槟憧梢詮姆椒暶髦械弥{(diào)用無(wú)法改變這個(gè)對(duì)象的狀態(tài)。
常量聲明為Static Final
考慮下面這種聲明的方式
- static final int intVal = 42;
- static final String strVal = "Hello, world!";
編譯器會(huì)使用一個(gè)初始化類的函數(shù),然后當(dāng)類***次被使用的時(shí)候執(zhí)行。這個(gè)函數(shù)將42存入intVal,還從class文件的常量表中提取了strVal的引用。當(dāng)之后使用intVal或strVal的時(shí)候,他們會(huì)直接被查詢到。
我們可以用final關(guān)鍵字來(lái)優(yōu)化:
- static final int intVal = 42;
- static final String strVal = "Hello, world!";
這時(shí)再也不需要上面的方法了,因?yàn)閒inal聲明的常量進(jìn)入了靜態(tài)dex文件的域初始化部分。調(diào)用intVal的代碼會(huì)直接使用42,調(diào)用strVal的代碼也會(huì)使用一個(gè)相對(duì)廉價(jià)的“字符串常量”指令,而不是查表。
Notes:這個(gè)優(yōu)化方法只對(duì)原始類型和String類型有效,而不是任意引用類型。不過(guò),在必要時(shí)使用static final是個(gè)很好的習(xí)慣。
避免內(nèi)部的Getters/Setters
像C++等native language,通常使用getters(i = getCount())而不是直接訪問(wèn)變量(i = mCount)。這是編寫C++的一種優(yōu)秀習(xí)慣,而且通常也被其他面向?qū)ο蟮恼Z(yǔ)言所采用,例如C#與Java,因?yàn)榫幾g器通常會(huì)做inline訪問(wèn),而且你需要限制或者調(diào)試變量,你可以在任何時(shí)候在getter/setter里面添加代碼。
然而,在Android上,這不是一個(gè)好的寫法。虛函數(shù)的調(diào)用比起直接訪問(wèn)變量要耗費(fèi)更多。在面向?qū)ο缶幊讨?,將getter和setting暴露給公用接口是合理的,但在類內(nèi)部應(yīng)該僅僅使用域直接訪問(wèn)。
在沒(méi)有JIT(Just In Time Compiler)時(shí),直接訪問(wèn)變量的速度是調(diào)用getter的3倍。有JIT時(shí),直接訪問(wèn)變量的速度是通過(guò)getter訪問(wèn)的7倍。
請(qǐng)注意,如果你使用ProGuard,你可以獲得同樣的效果,因?yàn)镻roGuard可以為你inline accessors.
使用增強(qiáng)的For循環(huán)
增強(qiáng)的For循環(huán)(也被稱為 for-each 循環(huán))可以被用在實(shí)現(xiàn)了 Iterable 接口的 collections 以及數(shù)組上。使用collection的時(shí)候,Iterator會(huì)被分配,用于for-each調(diào)用hasNext()和next()方法。使用ArrayList時(shí),手寫的計(jì)數(shù)式for循環(huán)會(huì)快3倍(不管有沒(méi)有JIT),但是對(duì)于其他collection,增強(qiáng)的for-each循環(huán)寫法會(huì)和迭代器寫法的效率一樣。
請(qǐng)比較下面三種循環(huán)的方法:
- 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沒(méi)有辦法對(duì)它進(jìn)行優(yōu)化。
one()稍微快些。
two() 在沒(méi)有做JIT時(shí)是最快的,可是如果經(jīng)過(guò)JIT之后,與方法one()是差不多一樣快的。它使用了增強(qiáng)的循環(huán)方法for-each。
所以請(qǐng)盡量使用for-each的方法,但是對(duì)于ArrayList,請(qǐng)使用方法one()。
Tips:你還可以參考 Josh Bloch 的 《Effective Java》這本書的第46條
使用包級(jí)訪問(wèn)而不是內(nèi)部類的私有訪問(wè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);
- }
- }
這里重要的是,我們定義了一個(gè)私有的內(nèi)部類(Foo$Inner),它直接訪問(wèn)了外部類中的私有方法以及私有成員對(duì)象。這是合法的,這段代碼也會(huì)如同預(yù)期一樣打印出”Value is 27”。
問(wèn)題是,VM因?yàn)镕oo和Foo$Inner是不同的類,會(huì)認(rèn)為在Foo$Inner中直接訪問(wèn)Foo類的私有成員是不合法的。即使Java語(yǔ)言允許內(nèi)部類訪問(wèn)外部類的私有成員。為了去除這種差異,編譯器會(huì)產(chǎn)生一些仿造函數(shù):
- /*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)部類需要訪問(wèn)外部類中的mValue成員或需要調(diào)用doStuff()函數(shù)時(shí),它都會(huì)調(diào)用這些靜態(tài)方法。這意味著,上面的代碼可以歸結(jié)為,通過(guò)accessor函數(shù)來(lái)訪問(wèn)成員變量。早些時(shí)候我們說(shuō)過(guò),通過(guò)accessor會(huì)比直接訪問(wèn)域要慢。所以,這是一個(gè)特定語(yǔ)言用法造成性能降低的例子。
如果你正在性能熱區(qū)(hotspot:高頻率、重復(fù)執(zhí)行的代碼段)使用像這樣的代碼,你可以把內(nèi)部類需要訪問(wèn)的域和方法聲明為包級(jí)訪問(wèn),而不是私有訪問(wèn)權(quán)限。不幸的是,這意味著在相同包中的其他類也可以直接訪問(wèn)這些域,所以在公開的API中你不能這樣做。
避免使用float類型
Android系統(tǒng)中float類型的數(shù)據(jù)存取速度是int類型的一半,盡量?jī)?yōu)先采用int類型。
就速度而言,現(xiàn)代硬件上,float 和 double 的速度是一樣的。空間而言,double 是兩倍float的大小。在空間不是問(wèn)題的情況下,你應(yīng)該使用 double 。
同樣,對(duì)于整型,有些處理器實(shí)現(xiàn)了硬件幾倍的乘法,但是沒(méi)有除法。這時(shí),整型的除法和取余是在軟件內(nèi)部實(shí)現(xiàn)的,這在你使用哈希表或大量計(jì)算操作時(shí)要考慮到。
使用庫(kù)函數(shù)
除了那些常見的讓你多使用自帶庫(kù)函數(shù)的理由以外,記得系統(tǒng)函數(shù)有時(shí)可以替代第三方庫(kù),并且還有匯編級(jí)別的優(yōu)化,他們通常比帶有JIT的Java編譯出來(lái)的代碼更高效。典型的例子是:Android API 中的 String.indexOf(),Dalvik出于內(nèi)聯(lián)性能考慮將其替換。同樣 System.arraycopy()函數(shù)也被替換,這樣的性能在Nexus One測(cè)試,比手寫的for循環(huán)并使用JIT還快9倍。
Tips:參見 Josh Bloch 的 《Effective Java》這本書的第47條
謹(jǐn)慎使用native函數(shù)
結(jié)合Android NDK使用native代碼開發(fā),并不總是比Java直接開發(fā)的效率更好的。Java轉(zhuǎn)native代碼是有代價(jià)的,而且JIT不能在這種情況下做優(yōu)化。如果你在native代碼中分配資源(比如native堆上的內(nèi)存,文件描述符等等),這會(huì)對(duì)收集這些資源造成巨大的困難。你同時(shí)也需要為各種架構(gòu)重新編譯代碼(而不是依賴JIT)。你甚至對(duì)已同樣架構(gòu)的設(shè)備都需要編譯多個(gè)版本:為G1的ARM架構(gòu)編譯的版本不能完全使用Nexus One上ARM架構(gòu)的優(yōu)勢(shì),反之亦然。
Native 代碼是在你已經(jīng)有本地代碼,想把它移植到Android平臺(tái)時(shí)有優(yōu)勢(shì),而不是為了優(yōu)化已有的Android Java代碼使用。
如果你要使用JNI,請(qǐng)學(xué)習(xí)JNI Tips
Tips:參見 Josh Bloch 的 《Effective Java》這本書的第54條
關(guān)于性能的誤區(qū)
在沒(méi)有JIT的設(shè)備上,使用一種確切的數(shù)據(jù)類型確實(shí)要比抽象的數(shù)據(jù)類型速度要更有效率(例如,調(diào)用HashMap map要比調(diào)用Map map效率更高)。有誤傳效率要高一倍,實(shí)際上只是6%左右。而且,在JIT之后,他們直接并沒(méi)有大多差異。
在沒(méi)有JIT的設(shè)備上,讀取緩存域比直接讀取實(shí)際數(shù)據(jù)大概快20%。有JIT時(shí),域讀取和本地讀取基本無(wú)差。所以優(yōu)化并不值得除非你覺(jué)得能讓你的代碼更易讀(這對(duì) final, static, static final 域同樣適用)。
關(guān)于測(cè)量
在優(yōu)化之前,你應(yīng)該確定你遇到了性能問(wèn)題。你應(yīng)該確保你能夠準(zhǔn)確測(cè)量出現(xiàn)在的性能,否則你也不會(huì)知道優(yōu)化是否真的有效。
本章節(jié)中所有的技巧都需要Benchmark(基準(zhǔn)測(cè)試)的支持。Benchmark可以在 code.google.com “dalvik” project 中找到
Benchmark是基于Java版本的 Caliper microbenchmarking框架開發(fā)的。Microbenchmarking很難做準(zhǔn)確,所以Caliper幫你完成這部分工作,甚至還幫你測(cè)了你沒(méi)想到需要測(cè)量的部分(因?yàn)?,VM幫你管理了代碼優(yōu)化,你很難知道這部分優(yōu)化有多大效果)。我們強(qiáng)烈推薦使用Caliper來(lái)做你的基準(zhǔn)微測(cè)工作。
我們也可以用Traceview 來(lái)測(cè)量,但是測(cè)量的數(shù)據(jù)是沒(méi)有經(jīng)過(guò)JIT優(yōu)化的,所以實(shí)際的效果應(yīng)該是要比測(cè)量的數(shù)據(jù)稍微好些。
分享標(biāo)題:AndroidTraining-代碼性能優(yōu)化小技巧
文章起源:http://m.fisionsoft.com.cn/article/dhhocpd.html


咨詢
建站咨詢
