自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

分分鐘學(xué)習(xí)編寫高效的 Android 代碼

移動開發(fā) Android
毫無疑問,基于Android平臺的設(shè)備一定是嵌入式設(shè)備。現(xiàn)代的手持設(shè)備不僅僅是一部電話那么簡單,它還是一個小型的手持電腦,但是,即使是最快的最高端的手持設(shè)備也遠(yuǎn)遠(yuǎn)比不上一個中等性能的桌面機(jī),這就是為什么在編寫Android程序時要時刻考慮執(zhí)行的效率。

毫無疑問,基于Android平臺的設(shè)備一定是嵌入式設(shè)備?,F(xiàn)代的手持設(shè)備不僅僅是一部電話那么簡單,它還是一個小型的手持電腦,但是,即使是最快的***端的手持設(shè)備也遠(yuǎn)遠(yuǎn)比不上一個中等性能的桌面機(jī)。 

這就是為什么在編寫Android程序時要時刻考慮執(zhí)行的效率,這些系統(tǒng)不是想象中的那么快,并且你還要考慮它電池的續(xù)航能力。這就意味著沒有多少剩余空間給你去浪費了,因此,在你寫Android程序的時候,要盡可能的使你的代碼優(yōu)化而提高效率。 

[[116551]]

本頁介紹了幾種可以讓開發(fā)者的Android程序運行的更加有效率的方法。通過下面的一些跳轉(zhuǎn)的連接,你可以學(xué)會怎么讓你的程序更加有效運行 

介紹 

對于如何判斷一個系統(tǒng)的不合理,這里有兩個基本的原則: 

不要做不必要做的事情。 

盡可能的節(jié)省內(nèi)存的使用。 

下面的所有方法都是基于這兩項的。 

有人會認(rèn)為本頁花了大量的篇幅去講如何進(jìn)行“初步優(yōu)化”( premature optimization)。雖然有時候微觀優(yōu)化對開發(fā)高效的數(shù)據(jù)結(jié)構(gòu)和算法很困難,但是在嵌入式手持設(shè)備上面你毫無選擇。例如,如果把桌面電腦的虛擬機(jī) 移植到你的Android系統(tǒng)中,你會發(fā)現(xiàn)你寫的程序會耗盡你的內(nèi)存。這就會導(dǎo)致程序運行起來極度緩慢,即使不考慮它對系統(tǒng)上其他的運行程序的影響。 

這就是為什么上面兩條原則這么重要。Android的成功在于開發(fā)程序提供給用戶的體驗,然而用戶體驗的好壞又決定于你的代碼是否能及時的響應(yīng)而不至于慢的 讓人崩潰。因為我們所有的程序都會在同一個設(shè)備上面運行,所以我們把它們作為一個整體來考慮。本文就像你考駕照需要學(xué)習(xí)的交通規(guī)則一樣:如果所有人遵守, 事情就會很流暢;但當(dāng)你不遵守時,你就會撞車。 

在我們討論實質(zhì)問題之前,有一個簡要的說 明:無論虛擬機(jī)是否是Java編譯器的一個特點,下面介紹的所有觀點都是正確的。如果我們有兩種方法完成同樣的事情,但是foo()的解釋執(zhí)行要快于 bar(),那么foo()的編譯速度一定不會比bar()慢,僅僅靠編譯器使你的代碼運行速度提升是不明智的做法。 

盡可能避免創(chuàng)建對象(Object) 

對象的創(chuàng)建并不是沒有代價的。一個帶有線程分配池的generational的內(nèi)存管理機(jī)制會使創(chuàng)建臨時對象的代價減少,不是分配內(nèi)存總比不上不分配內(nèi)存好。 

如果你在一個用戶界面的循環(huán)中分配一個對象,你不得不強(qiáng)制的進(jìn)行內(nèi)存回收,那么就會使用戶體驗出現(xiàn)稍微“打嗝”的現(xiàn)象。 

因此,如果沒有必要你就不應(yīng)該創(chuàng)建對象實例。下面是一件有幫助的例子: 

當(dāng)從原始的輸入數(shù)據(jù)中提取字符串時,試著從原始字符串返回一個子字符串,而不是創(chuàng)建一份拷貝。你將會創(chuàng)建一個新的字符串對象,但是它和你的原始數(shù)據(jù)共享數(shù)據(jù)空間。 

如果你有一個返回字符串地方法,你應(yīng)該知道無論如何返回的結(jié)果是StringBuffer,改變你的函數(shù)的定義和執(zhí)行,讓函數(shù)直接返回而不是通過創(chuàng)建一個臨時的對象。 

一個比較激進(jìn)的方法就是把一個多維數(shù)組分割成幾個平行的一維數(shù)組: 

一個Int類型的數(shù)組要比一個Integer類型的數(shù)組要好,但著同樣也可以歸納于這樣一個原則,兩個Int類型的數(shù)組要比一個(int,int)對象數(shù)組的效率要高的多。對于其他原始數(shù)據(jù)類型,這個原則同樣適用。 

如果你需要創(chuàng)建一個包含一系列Foo和Bar對象的容器(container)時,記住:兩個平行的Foo[]和Bar[]要比一個(Foo,Bar)對 象數(shù)組的效率高得多。(這個例子也有一個例外,當(dāng)你設(shè)計其他代碼的接口API時;在這種情況下,速度上的一點損失就不用考慮了。但是,在你的代碼里面,你 應(yīng)該盡可能的編寫高效代碼。) 

一般來說,盡可能的避免創(chuàng)建短期的臨時對象。越少的對象創(chuàng)建意味著越少的垃圾回收,這會提高你程序的用戶體驗質(zhì)量。 

使用自身方法(Use Native Methods) 

當(dāng)處理字符串的時候,不要猶豫,盡可能多的使用諸如String.indexOf()、String.lastIndexOf()這樣對象自身帶有的方法。因為這些方法使用C/C++來實現(xiàn)的,要比在一個java循環(huán)中做同樣的事情快10-100倍。 

還有一點要補(bǔ)充說明的是,這些自身方法使用的代價要比那些解釋過的方法高很多,因而,對于細(xì)微的運算,盡量不用這類方法。 

使用虛擬優(yōu)于使用接口 

假設(shè)你有一個HashMap對象,你可以聲明它是一個HashMap或則只是一個Map: 

Java代碼

  1. Map myMap1 = new HashMap();   
  2. HashMap myMap2 = new HashMap();  

哪一個更好呢? 

一般來說明智的做法是使用Map,因為它能夠允許你改變Map接口執(zhí)行上面的任何東西,但是這種“明智”的方法只是適用于常規(guī)的編程,對于嵌入式系統(tǒng)并不適合。通過接口引用來調(diào)用會花費2倍以上的時間,相對于通過具體的引用進(jìn)行虛擬函數(shù)的調(diào)用。 

如 果你選擇使用一個HashMap,因為它更適合于你的編程,那么使用Map會毫無價值。假定你有一個能重構(gòu)你代碼的集成編碼環(huán)境,那么調(diào)用Map沒有什么 用處,即使你不確定你的程序從哪開頭。(同樣,public的API是一個例外,一個好的API的價值往往大于執(zhí)行效率上的那點損失) 

使用靜態(tài)優(yōu)于使用虛擬 

如果你沒有必要去訪問對象的外部,那么使你的方法成為靜態(tài)方法。它會被更快的調(diào)用,因為它不需要一個虛擬函數(shù)導(dǎo)向表。這同時也是一個很好的實踐,因為它告訴你如何區(qū)分方法的性質(zhì)(signature),調(diào)用這個方法不會改變對象的狀態(tài)。 

盡可能避免使用內(nèi)在的Get、Set方法 

像 C++iyangde編程語言,通常會使用Get方法(例如 i = getCount())去取代直接訪問這個屬性(i=mCount)。 這在C++編程里面是一個很好的習(xí)慣,因為編譯器會把訪問方式設(shè)置為Inline,并且如果想約束或調(diào)試屬性訪問,你只需要在任何時候添加一些代碼。 

在Android編程中,這不是一個很不好的主意。虛方法的調(diào)用會產(chǎn)生很多代價,比實例屬性查詢的代價還要多。我們應(yīng)該在外部調(diào)用時使用Get和Set函數(shù),但是在內(nèi)部調(diào)用時,我們應(yīng)該直接調(diào)用。 

緩沖屬性調(diào)用Cache Field Lookups 

訪問對象屬性要比訪問本地變量慢得多。你不應(yīng)該這樣寫你的代碼: 

Java代碼

  1. for (int i = 0; i < this.mCount; i++)   
  2.       dumpItem(this.mItems[i]);   

而是應(yīng)該這樣寫: 

Java代碼

  1. int count = this.mCount;   
  2. Item[] items = this.mItems;   
  3.    
  4. for (int i = 0; i < count; i++)   
  5.     dumpItems(items[i]);   

(我們直接使用“this”表明這些是它的成員變量) 

一個相似的原則就是:決不在一個For語句中第二次調(diào)用一個類的方法。例如,下面的代碼就會一次又一次地執(zhí)行g(shù)etCount()方法,這是一個極大地浪費相比你把它直接隱藏到一個Int變量中。 

Java代碼

  1. for (int i = 0; i < this.getCount(); i++)   
  2.     dumpItems(this.getItem(i));   

這是一個比較好的辦法,當(dāng)你不止一次的調(diào)用某個實例時,直接本地化這個實例,把這個實例中的某些值賦給一個本地變量。例如: 

Java代碼

  1. protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {   
  2.         if (isHorizontalScrollBarEnabled()) {   
  3.             int size = mScrollBar.getSize(false);   
  4.             if (size <= 0) {   
  5.                 size = mScrollBarSize;   
  6.             }   
  7.             mScrollBar.setBounds(0, height - size, width, height);   
  8.             mScrollBar.setParams(   
  9.                     computeHorizontalScrollRange(),   
  10.                     computeHorizontalScrollOffset(),   
  11.                     computeHorizontalScrollExtent(), false);   
  12.             mScrollBar.draw(canvas);   
  13.         }   
  14.     }   

這里有四次mScrollBar的屬性調(diào)用,把mScrollBar緩沖到一個堆棧變量之中,四次成員屬性的調(diào)用就會變成四次堆棧的訪問,這樣就會提高效率。 

附帶說一下,對于方法同樣也可以像本地變量一樣具有相同的特點。 

聲明Final常量

我們可以看看下面一個類頂部的聲明: 

Java代碼

  1. static int intVal = 42;   
  2. static String strVal = "Hello, world!";   

當(dāng)一個類***次使用時,編譯器會調(diào)用一個類初始化方法——<clinit>,這個方法將42存入變量intVal,并且為strVal在類文件字符串常量表中提取一個引用,當(dāng)這些值在后面引用時,就會直接屬性調(diào)用。 

我們可以用關(guān)鍵字“final”來改進(jìn)代碼: 

Java代碼

  1. static final int intVal = 42;   
  2. static final String strVal = "Hello, world!";   

這個類將不會調(diào)用es a <clinit>方法,因為這些常量直接寫入了類文件靜態(tài)屬性初始化中,這個初始化直接由虛擬機(jī)來處理。代碼訪問intVal將會使用 Integer類型的42,訪問strVal將使用相對節(jié)省的“字符串常量”來替代一個屬性調(diào)用。 

將一個類或者方法聲明為“final”并不會帶來任何的執(zhí)行上的好處,它能夠進(jìn)行一定的***化處理。例如,如果編譯器知道一個Get方法不能被子類重載,那么它就把該函數(shù)設(shè)置成Inline。 

同時,你也可以把本地變量聲明為final變量。但是,這毫無意義。作為一個本地變量,使用final只能使代碼更加清晰(或者你不得不用,在匿名訪問內(nèi)聯(lián)類時)。 

慎重使用增強(qiáng)型For循環(huán)語句 

增 強(qiáng)型For循環(huán)(也就是常說的“For-each循環(huán)”)經(jīng)常用于Iterable接口的繼承收集接口上面。在這些對象里面,一個iterator被分配 給對象去調(diào)用它的hasNext()和next()方法。在一個數(shù)組列表里面,你可以自己接的敷衍它,在其他的收集器里面,增強(qiáng)型的for循環(huán)將相當(dāng)于 iterator的使用。 

盡管如此,下面的源代碼給出了一個可以接受的增強(qiáng)型for循環(huán)的例子: 

Java代碼

  1. public class Foo {   
  2.     int mSplat;   
  3.     static Foo mArray[] = new Foo[27];   
  4.    
  5.     public static void zero() {   
  6.         int sum = 0;   
  7.         for (int i = 0; i < mArray.length; i++) {   
  8.             sum += mArray[i].mSplat;   
  9.         }   
  10.     }   
  11.    
  12.     public static void one() {   
  13.         int sum = 0;   
  14.         Foo[] localArray = mArray;   
  15.         int len = localArray.length;   
  16.    
  17.         for (int i = 0; i < len; i++) {   
  18.             sum += localArray[i].mSplat;   
  19.         }   
  20.     }   
  21.    
  22.     public static void two() {   
  23.         int sum = 0;   
  24.         for (Foo a: mArray) {   
  25.             sum += a.mSplat;   
  26.         }   
  27.     }   
  28. }   

zero() 函數(shù)在每一次的循環(huán)中重新得到靜態(tài)屬性兩次,獲得數(shù)組長度一次。 

one() 函數(shù)把所有的東西都變?yōu)楸镜刈兞?,避免類查找屬性調(diào)用 

two() 函數(shù)使用Java語言的1.5版本中的for循環(huán)語句,編輯者產(chǎn)生的源代碼考慮到了拷貝數(shù)組的引用和數(shù)組的長度到本地變量,是例遍數(shù)組比較好的方法,它在 主循環(huán)中確實產(chǎn)生了一個額外的載入和儲存過程(顯然保存了“a”),相比函數(shù)one()來說,它有一點比特上的減慢和4字節(jié)的增長。 

總結(jié)之后,我們可以得到:增強(qiáng)的for循環(huán)在數(shù)組里面表現(xiàn)很好,但是當(dāng)和Iterable對象一起使用時要謹(jǐn)慎,因為這里多了一個對象的創(chuàng)建。 

#p#

避免列舉類型Avoid Enums

列舉類型非常好用,當(dāng)考慮到尺寸和速度的時候,就會顯得代價很高,例如: 

Java代碼

  1. public class Foo {   
  2.    public enum Shrubbery { GROUND, CRAWLING, HANGING }   
  3. }   

這會轉(zhuǎn)變成為一個900字節(jié)的class文件(Foo$Shrubbery.class)。***次使用時,類的初始化要在獨享上面調(diào)用方法去 描述列舉的每一項,每一個對象都要有它自身的靜態(tài)空間,整個被儲存在一個數(shù)組里面(一個叫做“$VALUE”的靜態(tài)數(shù)組)。那是一大堆的代碼和數(shù)據(jù),僅僅 是為了三個整數(shù)值。 

Java代碼

  1. Shrubbery shrub = Shrubbery.GROUND;   

這會引起一個靜態(tài)屬性的調(diào)用,如果GROUND是一個靜態(tài)的Final變量,編譯器會把它當(dāng)做一個常數(shù)嵌套在代碼里面。 

還有一點要說的,通過列舉,你可以得到更好地API和一些編譯時間上的檢查。因此,一種比較平衡的做法就是:你應(yīng)該盡一切方法在你的公用API中使用列舉型變量,當(dāng)處理問題時就盡量的避免。 

在一些環(huán)境下面,通過ordinal()方法獲取一個列舉變量的整數(shù)值是很有用的,例如:把下面代碼 

Java代碼

  1. for (int n = 0; n < list.size(); n++) {   
  2.     if (list.items[n].e == MyEnum.VAL_X)   
  3.        // do stuff 1   
  4.     else if (list.items[n].e == MyEnum.VAL_Y)   
  5.        // do stuff 2   
  6. }  

替換為: 

Java代碼

  1. int valX = MyEnum.VAL_X.ordinal();   
  2.   int valY = MyEnum.VAL_Y.ordinal();   
  3.   int count = list.size();   
  4.   MyItem items = list.items();   
  5.    
  6.   for (int  n = 0; n < count; n++)   
  7.   {   
  8.        int  valItem = items[n].e.ordinal();   
  9.    
  10.        if (valItem == valX)   
  11.          // do stuff 1   
  12.        else if (valItem == valY)   
  13.          // do stuff 2   
  14.   }  

在一些條件下,這會執(zhí)行的更快,雖然沒有保障。 

通過內(nèi)聯(lián)類使用包空間 

我們看下面的類聲明 

Java代碼

  1. public class Foo {   
  2.     private int mValue;   
  3.    
  4.     public void run() {   
  5.         Inner in = new Inner();   
  6.         mValue = 27;   
  7.         in.stuff();   
  8.     }   
  9.    
  10.     private void doStuff(int value) {   
  11.         System.out.println("Value is " + value);   
  12.     }   
  13.    
  14.     private class Inner {   
  15.         void stuff() {   
  16.             Foo.this.doStuff(Foo.this.mValue);   
  17.         }   
  18.     }   
  19. }  

這里我們要注意的是我們定義了一個內(nèi)聯(lián)類,它調(diào)用了外部類的私有方法和私有屬性。這是合法的調(diào)用,代碼應(yīng)該會顯示"Value is 27"。 

問題是Foo$Inner在理論上(后臺運行上)是應(yīng)該是一個完全獨立的類,它違規(guī)的調(diào)用了Foo的私有成員。為了彌補(bǔ)這個缺陷,編譯器產(chǎn)生了一對合成的方法: 

Java代碼

  1. /*package*/ static int Foo.access$100(Foo foo) {   
  2.     return foo.mValue;   
  3. }   
  4. /*package*/ static void Foo.access$200(Foo foo, int value) {   
  5.     foo.doStuff(value);   
  6. }  

當(dāng)內(nèi)聯(lián)類需要從外部訪問“mValue”和調(diào)用“doStuff”時,內(nèi)聯(lián)類就會調(diào)用這些靜態(tài)的方法,這就意味著你不是直接訪問類成員,而是通過公共的方法來訪問的。前面我們談過間接訪問要比直接訪問慢,因此這是一個按語言習(xí)慣無形執(zhí)行的例子。 

讓 擁有包空間的內(nèi)聯(lián)類直接聲明需要訪問的屬性和方法,我們就可以避免這個問題,哲理詩是包空間而不是私有空間。這運行的更快并且去除了生成函數(shù)前面東西。 (不幸的是,它同時也意味著該屬性也能夠被相同包下面的其他的類直接訪問,這違反了標(biāo)準(zhǔn)的面向?qū)ο蟮氖顾袑傩运接械脑瓌t。同樣,如果是設(shè)計公共的API 你就要仔細(xì)的考慮這種優(yōu)化的用法) 

避免浮點類型的使用

在奔騰CPU發(fā)布之前,游戲作者盡可能的使用Integer類型的數(shù)學(xué)函數(shù)是很正常的。在奔騰處理器里面,浮點數(shù)的處理變?yōu)樗粋€突出的特點,并且浮點數(shù)與 整數(shù)的交互使用相比單獨使用整數(shù)來說,前者會使你的游戲運行的更快,一般的在桌面電腦上面我們可以自由的使用浮點數(shù)。 

不幸的是,嵌入式的處理器通常并不支持浮點數(shù)的處理,陰齒所有的“float”和“double”操作都是通過軟件進(jìn)行的,一些基本的浮點數(shù)的操作就需要花費毫秒級的時間。 

同事,即使是整數(shù),一些芯片也只有乘法而沒有除法。在這些情況下,整數(shù)的除法和取模操作都是通過軟件實現(xiàn)。當(dāng)你創(chuàng)建一個Hash表或者進(jìn)行大量的數(shù)學(xué)運算時,這都是你要考慮的。 

一些標(biāo)準(zhǔn)操作的時間比較

為了距離說明我們的觀點,下面有一張表,包括一些基本操作所使用的大概時間。注意這些時間并不是絕對的時間,絕對時間要考慮到CPU和時鐘頻率。系統(tǒng)不同, 時間的大小也會有所差別。當(dāng)然,這也是一種有意義的比較方法,我們可以比叫不同操作花費的相對時間。例如,添加一個成員變量的時間是添加一個本地變量的四 倍。 

Action Time
Add a local variable 1
Add a member variable 4
Call String.length() 5
Call empty static native method 5
Call empty static method 12
Call empty virtual method 12.5
Call empty interface method 15
Call Iterator:next() on a HashMap 165
Call put() on a HashMap 600
Inflate 1 View from XML 22,000
Inflate 1 LinearLayout containing 1 TextView 25,000
Inflate 1 LinearLayout containing 6 View objects 100,000
Inflate 1 LinearLayout containing 6 TextView objects 135,000
Launch an empty activity 3,000,000

結(jié)束語

寫高效的嵌入式程序的***方法就是要搞清楚你寫的程序究竟做了些什么。如果你真的想分配一個iterator類,進(jìn)一切方法的在一個List中使用增強(qiáng)型的for循環(huán),使它成為一個有意而為之的做法,而不是一個無意的疏漏而產(chǎn)生負(fù)面影響。 

有備無患,搞清楚你在做什么!你可以假如你自己的一些行為準(zhǔn)則,但是一定要注意你的代碼正在做什么,然后開始尋找方法去優(yōu)化它。

責(zé)任編輯:閆佳明 來源: cnblogs
相關(guān)推薦

2015-01-28 14:30:31

android代碼

2010-08-31 13:32:12

CSS

2011-11-25 10:35:20

Java

2014-11-10 09:59:08

jQuery

2020-02-15 16:48:28

機(jī)器學(xué)習(xí)算法人工智能

2011-03-24 13:09:11

數(shù)據(jù)庫代碼

2021-04-26 09:58:26

MySQL數(shù)據(jù)庫安全盲區(qū)

2020-09-21 06:58:56

TS 代碼建議

2024-01-30 08:54:05

JavaScript技巧代碼

2021-02-23 10:48:30

Python代碼開發(fā)

2017-09-15 13:35:11

JavaScript神經(jīng)網(wǎng)絡(luò)

2017-09-18 08:08:33

JavaScript神經(jīng)網(wǎng)絡(luò)代碼

2023-07-06 14:37:05

2021-05-08 12:10:49

Python代碼抓點圖片

2023-07-30 17:10:32

TypeScript開發(fā)

2016-01-22 11:40:07

2016-09-04 15:42:03

Linux虛擬集群

2015-09-16 11:15:28

戴爾云計算Windows Ser

2010-09-09 13:59:55

CSS

2024-06-24 10:31:46

點贊
收藏

51CTO技術(shù)棧公眾號