關(guān)于提高Android程序效率的幾點(diǎn)建議

本文為官網(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)化方式

  1. JIT : Just In Time Compiler 又譯及時(shí)編譯束凑、實(shí)時(shí)編譯晒旅,動(dòng)態(tài)編譯的一種形式,是一種提高程序運(yùn)行效率的方法汪诉。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废恋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扒寄,更是在濱河造成了極大的恐慌鱼鼓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件该编,死亡現(xiàn)場(chǎng)離奇詭異蚓哩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)上渴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門岸梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人稠氮,你說我怎么就攤上這事曹阔。” “怎么了隔披?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵赃份,是天一觀的道長。 經(jīng)常有香客問我,道長抓韩,這世上最難降的妖魔是什么纠永? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谒拴,結(jié)果婚禮上尝江,老公的妹妹穿的比我還像新娘。我一直安慰自己英上,他們只是感情好炭序,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苍日,像睡著了一般惭聂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上相恃,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天辜纲,我揣著相機(jī)與錄音,去河邊找鬼拦耐。 笑死耕腾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揩魂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼炮温,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼火脉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柒啤,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤倦挂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后担巩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體方援,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年涛癌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犯戏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拳话,死狀恐怖先匪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弃衍,我是刑警寧澤呀非,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響岸裙,放射性物質(zhì)發(fā)生泄漏猖败。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一降允、第九天 我趴在偏房一處隱蔽的房頂上張望恩闻。 院中可真熱鬧,春花似錦拟糕、人聲如沸判呕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侠草。三九已至,卻和暖如春犁嗅,著一層夾襖步出監(jiān)牢的瞬間边涕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工褂微, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留功蜓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓宠蚂,卻偏偏與公主長得像式撼,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子求厕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于android性能呀癣,內(nèi)存優(yōu)化 看了些資料整理了下美浦,安卓的性能和內(nèi)存優(yōu)化的一些方法和注意事項(xiàng)。分享出來项栏。 隨著技...
    ifeng_max閱讀 1,054評(píng)論 0 14
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理浦辨,服務(wù)發(fā)現(xiàn),斷路器沼沈,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評(píng)論 25 707
  • 生活流酬,奉獻(xiàn)它的熱情 讓白晝歌唱 真理,奉獻(xiàn)它的瓊漿 讓靈魂飽滿 只為呼吸這一片蔚藍(lán) 愿我的愛 在這片土地上生長
    夢(mèng)鷹閱讀 280評(píng)論 0 0
  • nodejs代碼: 請(qǐng)求接口:
    adtk閱讀 13,807評(píng)論 0 0