Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android系列之Android系列之如何成為Android編程高手第三篇

Android系列之Android系列之如何成為Android編程高手第三篇

編輯:Android開發實例

避免建立對象
世界上沒有免費的對象。雖然GC為每個線程都建立了臨時對象池,可以使創建對象的代價變得小一些,但是分配內存永遠都比不分配內存的代價大。
如果你在用戶界面循環中分配對象內存,就會引發周期性的垃圾回收,用戶就會覺得界面像打嗝一樣一頓一頓的。
所以,除非必要,應盡量避免盡力對象的實例。下面的例子將幫助你理解這條原則:
當你從用戶輸入的數據中截取一段字符串時,盡量使用substring函數取得原始數據的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一個新的String對象,它與原始數據共享一個char數組。
如果你有一個函數返回一個String對象,而你確切的知道這個字符串會被附加到一個StringBuffer,那麼,請改變這個函數的參數和實現方式,直接把結果附加到StringBuffer中,而不要再建立一個短命的臨時對象。
一個更極端的例子是,把多維數組分成多個一維數組。
int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比(int,int)對象數組性能要好很多。同理,這試用於所有基本類型的組合。
如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個單獨的Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個API,讓別人調用它的時候。這時候你要注重對API借口的設計而犧牲一點兒速度。當然在API的內部,你仍要盡可能的提高代碼的效率)
總體來說,就是避免創建短命的臨時對象。減少對象的創建就能減少垃圾收集,進而減少對用戶體驗的影響。
使用本地方法
當你在處理字串的時候,不要吝惜使用String.indexOf(), String.lastIndexOf()等特殊實現的方法(specialty methods)。這些方法都是使用C/C++實現的,比起Java循環快10到100倍。
使用實類比接口好
假設你有一個HashMap對象,你可以將它聲明為HashMap或者Map:

 

Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();

 

 

 

哪個更好呢?
按照傳統的觀點Map會更好些,因為這樣你可以改變他的具體實現類,只要這個類繼承自Map接口。傳統的觀點對於傳統的程序是正確的,但是它並不適合嵌入式系統。調用一個接口的引用會比調用實體類的引用多花費一倍的時間。
如果HashMap完全適合你的程序,那麼使用Map就沒有什麼價值。如果有些地方你不能確定,先避免使用Map,剩下的交給IDE提供的重構功能好了。(當然公共API是一個例外:一個好的API常常會犧牲一些性能)
用靜態方法比虛方法好
如果你不需要訪問一個對象的成員變量,那麼請把方法聲明成static。虛方法執行的更快,因為它可以被直接調用而不需要一個虛函數表。另外你也可以通過聲明體現出這個函數的調用不會改變對象的狀態。
不用getter和setter
在很多本地語言如C++中,都會使用getter(比如:i = getCount())來避免直接訪問成員變量(i = mCount)。在C++中這是一個非常好的習慣,因為編譯器能夠內聯訪問,如果你需要約束或調試變量,你可以在任何時候添加代碼。
在Android上,這就不是個好主意了。虛方法的開銷比直接訪問成員變量大得多。在通用的接口定義中,可以依照OO的方式定義getters和setters,但是在一般的類中,你應該直接訪問變量。
將成員變量緩存到本地
訪問成員變量比訪問本地變量慢得多,下面一段代碼:

 

1 for (int i = 0; i < this.mCount; i++)   
2 dumpItem(this.mItems[i]);
3  //最好改成這樣:
4  int count = this.mCount;
5 Item[] items = this.mItems;
6  for (int i = 0; i < count; i++)
7 dumpItems(items[i]);

 (使用"this"是為了表明這些是成員變量)
另一個相似的原則是:永遠不要在for的第二個條件中調用任何方法。如下面方法所示,在每次循環的時候都會調用getCount()方法,這樣做比你在一個int先把結果保存起來開銷大很多。

 

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

 

同樣如果你要多次訪問一個變量,也最好先為它建立一個本地變量,例如:

 

 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 }
8
9 mScrollBar.setBounds(0, height - size, width, height);
10
11 mScrollBar.setParams(computeHorizontalScrollRange(),computeHorizontalScrollOffset(),computeHorizontalScrollExtent(), false);
12
13 mScrollBar.draw(canvas);
14
15 }
16
17 }
18
19  

這裡有4次訪問成員變量mScrollBar,如果將它緩存到本地,4次成員變量訪問就會變成4次效率更高的棧變量訪問。

另外就是方法的參數與本地變量的效率相同。

使用常量

讓我們來看看這兩段在類前面的聲明

 

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

必以其會生成一個叫做<clinit>的初始化類的方法,當類第一次被使用的時候這個方法會被執行。方法會將42賦給intVal,然後把一個指向類中常量表的引用賦給strVal。當以後要用到這些值的時候,會在成員變量表中查找到他們。

下面我們做些改進,使用“final"關鍵字:

 

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

 

現在,類不再需要<clinit>方法,因為在成員變量初始化的時候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字符串常量,而不是使用成員變量。

將一個方法或類聲明為"final"不會帶來性能的提升,但是會幫助編譯器優化代碼。舉例說,如果編譯器知道一個"getter"方法不會被重載,那麼編譯器會對其采用內聯調用。

你也可以將本地變量聲明為"final",同樣,這也不會帶來性能的提升。使用"final"只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)(xing:原文是 or you have to, e.g. for use in an anonymous inner class)

謹慎使用foreach

foreach可以用在實現了Iterable接口的集合類型上。foreach會給這些對象分配一個iterator,然後調用 hasNext()和next()方法。你最好使用foreach處理ArrayList對象,但是對其他集合對象,foreach相當於使用 iterator。

下面展示了foreach一種可接受的用法:

 

 1  public class Foo {   
2
3 int mSplat;
4
5 static Foo mArray[] = new Foo[27];
6
7 public static void zero() {
8
9 int sum = 0;
10
11 for (int i = 0; i < mArray.length; i++) {
12
13 sum += mArray[i].mSplat;
14 }
15
16 }
17
18
19
20 public static void one() {
21
22 int sum = 0;
23
24 Foo[] localArray = mArray;
25
26 int len = localArray.length;
27
28 for (int i = 0; i < len; i++) {
29
30 sum += localArray[i].mSplat;
31
32 }
33
34 }
35 public static void two() {
36
37 int sum = 0;
38
39 for (Foo a: mArray) {
40
41 sum += a.mSplat;
42
43 }
44
45 }
46
47 }
48
49  

在zero()中,每次循環都會訪問兩次靜態成員變量,取得一次數組的長度。
retrieves the static field twice and gets the array length once for every iteration through the loop.
在one()中,將所有成員變量存儲到本地變量。
pulls everything out into local variables, avoiding the lookups.
two()使用了在java1.5中引入的foreach語法。編譯器會將對數組的引用和數組的長度保存到本地變量中,這對訪問數組元素非常好。但是編譯器還會在每次循環中產生一個額外的對本地變量的存儲操作(對變量a的存取)這樣會比one()多出4個字節,速度要稍微慢一些。
綜上所述:foreach語法在運用於array時性能很好,但是運用於其他集合對象時要小心,因為它會產生額外的對象。
避免使用枚舉
枚舉變量非常方便,但不幸的是它會犧牲執行的速度和並大幅增加文件體積。例如:
public class Foo {public enum Shrubbery { GROUND, CRAWLING, HANGING }}
會產生一個900字節的.class文件(Foo$Shubbery.class)。在它被首次調用時,這個類會調用初始化方法來准備每個枚舉變量。每個枚舉項都會被聲明成一個靜態變量,並被賦值。然後將這些靜態變量放在一個名為"$VALUES"的靜態數組變量中。而這麼一大堆代碼,僅僅是為了使用三個整數。
這樣:
Shrubbery shrub = Shrubbery.GROUND;會引起一個對靜態變量的引用,如果這個靜態變量是final int,那麼編譯器會直接內聯這個常數。
一方面說,使用枚舉變量可以讓你的API更出色,並能提供編譯時的檢查。所以在通常的時候你毫無疑問應該為公共API選擇枚舉變量。但是當性能方面有所限制的時候,你就應該避免這種做法了。
有些情況下,使用ordinal()方法獲取枚舉變量的整數值會更好一些,舉例來說,將:

 

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

替換為:

 

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

 

會使性能得到一些改善,但這並不是最終的解決之道。

將與內部類一同使用的變量聲明在包范圍內

請看下面的類定義:

 

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

這其中的關鍵是,我們定義了一個內部類(Foo$Inner),它需要訪問外部類的私有域變量和函數。這是合法的,並且會打印出我們希望的結果"Value is 27"。

問題是在技術上來講(在幕後)Foo$Inner是一個完全獨立的類,它要直接訪問Foo的私有成員是非法的。要跨越這個鴻溝,編譯器需要生成一組方法:

 

 1 
2  static int Foo.access$100(Foo foo) {
3
4 return foo.mValue;
5
6 }
7
8 static void Foo.access$200(Foo foo, int value) {
9
10 foo.doStuff(value);
11
12 }
13  


內部類在每次訪問"mValue"和"doStuff"方法時,都會調用這些靜態方法。就是說,上面的代碼說明了一個問題,你是在通過接口方法訪問這些成員變量和函數而不是直接調用它們。在前面我們已經說過,使用接口方法(getter、setter)比直接訪問速度要慢。所以這個例子就是在特定語法下面產生的一個“隱性的”性能障礙。

通過將內部類訪問的變量和函數聲明由私有范圍改為包范圍,我們可以避免這個問題。這樣做可以讓代碼運行更快,並且避免產生額外的靜態方法。(遺憾的是,這些域和方法可以被同一個包內的其他類直接訪問,這與經典的OO原則相違背。因此當你設計公共API的時候應該謹慎使用這條優化原則)

避免使用浮點數

在奔騰CPU出現之前,游戲設計者做得最多的就是整數運算。隨著奔騰的到來,浮點運算處理器成為了CPU內置的特性,浮點和整數配合使用,能夠讓你的游戲運行得更順暢。通常在桌面電腦上,你可以隨意的使用浮點運算。

但是非常遺憾,嵌入式處理器通常沒有支持浮點運算的硬件,所有對"float"和"double"的運算都是通過軟件實現的。一些基本的浮點運算,甚至需要毫秒級的時間才能完成。

甚至是整數,一些芯片有對乘法的硬件支持而缺少對除法的支持。這種情況下,整數的除法和取模運算也是有軟件來完成的。所以當你在使用哈希表或者做大量數學運算時一定要小心謹慎。 ”

五,學會至少一門服務器端開發技術
可能有朋友會問:學習Android應用程序開發為什麼還需要學習學會至少一門服務器端開發技術呢?答案如下:一方面Android號稱是首個為移動終端打造的真正開放和完整的移動軟件。作為一種移動終端,必須與服務器端結合才能發揮巨大的作用。簡言之,需要:雲端+雲的方式。Android是為移動互聯網時代量身打造的,移動互聯網時代的服務模式是“手機終端+互聯網絡+應用軟件”,移動互聯網時代應用技術之一的Android只是用於開發移動終端軟件,而服務端技術用於開發互聯網絡應用,所以未來移動互聯網時代軟件的主流應用模式將是“手機客戶端+互聯網絡應用服務端”,這種模式要求做移動互聯網開發的程序員不但要掌握像Android這樣的手機終端軟件技術還要掌握開發互聯網絡應用的服務器端技術。目前,軟件企業普遍存在這樣的問題,做移動互聯網開發Android終端軟件的程序員不了解web應用技術,而做web應用的程序員不了解移動終端技術,這樣就導致了客戶端與服務端在銜接上出現了問題。目前的現狀是:既掌握移動互聯網Android終端技術,又掌握web應用技術的程序員比較稀缺,隨著中國步入移動互聯網時代,企業對這種移動互聯網時代綜合性人才的需求很旺盛。如果不了解web應用技術,最終會遇到了技術和發展的瓶頸;另一方面,Google聯合OHA推出的真正優勢之一也在於和和互聯網結合,Google的用意之一也是想開辟新的終端去使用Google的優勢服務。
服務器端開發技術目前主流的有Sun的Java EE、微軟的.NET,開源的以PHP和MySQL為代表的LAMP體系,我們該選擇哪一種呢?從理論上講,很多人傾向於選擇Java EE,畢竟它們都是使用Java作為開發語言的,但是很多人面對Java EE眾多的框架就望而生畏,其實在學習Java EE的時候可以從Struts入手,隨著業務的需求逐步深入。當然,選擇微軟的.NET也行,畢竟該技術體系也占有很大 市場份額。其實,筆者認為,選擇LAMP可以是會獲得最高的“性價比”的,一方面PHP是現在Web方面的主流語言,大多數新型的網站尤其是創業性質的網站一般都會選用PHP作為服務端開發語言,另一方面,前面也說過,Android是為移動互聯而生的,兩者達到了完美的契合。

轉自:http://www.cnblogs.com/jk1001/archive/2010/08/09/1795593.html

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved