基于Android相關設備作為嵌入式設備范疇,在書寫App應用的時候要格外關注效率,并且受電池電量的限制。這就導致嵌入式設備有諸多考慮、有限處理能力,因此就要求我們盡量去寫高效的代碼。本文討論了很多能讓開發者使他們的程序運行更有效的方法,遵照這些方法,你可以使你的程序發揮最大的效力。
引言
對于占用資源的系統,有兩條基本原則:
不要做不必要的事
不要分配不必要的內存
所有下面的內容都遵照這兩個原則。
1、避免建立對象
世界上沒有免費的對象。雖然GC為每個線程都建立了臨時對象池,可以使創建對象的代價變得小一些,但是分配內存永遠都比不分配內存的代價大。
如果你在用戶界面循環中分配對象內存,就會引發周期性的垃圾回收,用戶就會覺得界面像打嗝一樣一頓一頓的。
所以,除非必要,應盡量避免盡力對象的實例。下面的例子將幫助你理解這條原則:
當你從用戶輸入的數據中截取一段字符串時,盡量使用substring函數取得原始數據的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一個新的String對象,它與原始數據共享一個char數組。
如果你有一個函數返回一個String對象,而你確切的知道這個字符串會被附加到一個StringBuffer,那么,請改變這個函數的參數和實現方式,直接把結果附加到StringBuffer中,而不要再建立一個短命的臨時對象。
一個更極端的例子是,把多維數組分成多個一維數組:
int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比(int,int)對象數組性能要好很多。同理,這試用于所有基本類型的組合。
如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個單獨的Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個API,讓別人調用它的時候。這時候你要注重對API借口的設計而犧牲一點兒速度。當然在API的內部,你仍要盡可能的提高代碼的效率)
總體來說,就是避免創建短命的臨時對象。減少對象的創建就能減少垃圾收集,進而減少對用戶體驗的影響。
2、使用本地方法
當你在處理字串的時候,不要吝惜使用String.indexOf(), String.lastIndexOf()等特殊實現的方法。這些方法都是使用C/C++實現的,比起Java循環快10到100倍。
但也并非要完全使用本地方法,調用本地方法的代價要高于調用解釋方法。所以如果可以避免,就不應使用本地方法去做一些并不復雜的運算。
3、選擇虛類而不是接口
假設你有一個HashMap對象,你可以將它聲明為HashMap或者Map:
Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();哪個更好呢?
按照傳統的觀點Map會更好些,因為這樣你可以改變他的具體實現類,只要這個類繼承自Map接口。傳統的觀點對于傳統的程序是正確的,但是它并不適合嵌入式系統。調用一個接口的引用會比調用實體類的引用多花費一倍的時間。如果HashMap完全適合你的程序,那么使用Map就沒有什么價值。如果有些地方你不能確定,先避免使用Map,剩下的交給IDE提供的重構功能好了。(當然公共API是一個例外:一個好的API常常會犧牲一些性能)
4、用靜態方法比虛方法好
如果你不需要訪問一個對象的成員變量,那么請把方法聲明成static。虛方法執行的更快,因為它可以被直接調用而不需要一個虛函數表。另外你也可以通過聲明體現出這個函數的調用不會改變對象的狀態。
5、不用getter和setter
在很多本地語言如C++中,都會使用getter(比如:i = getCount())來避免直接訪問成員變量(i = mCount)。在C++中這是一個非常好的習慣,因為編譯器能夠內聯訪問,如果你需要約束或調試變量,你可以在任何時候添加代碼。在Android上,這就不是個好主意了。虛方法的開銷比直接訪問成員變量大得多。在通用的接口定義中,可以依照OO的方式定義getters和setters,但是在一般的類中,你應該直接訪問變量。
6、將成員變量緩存到本地
訪問成員變量比訪問本地變量慢得多,下面一段代碼:
for (int i = 0; i < this.mCount; i++) dumpItem(this.mItems[i]);你應該寫成: int count = this.mCount; Item[] items = this.mItems; for (int i = 0; i < count; i++) dumpItems(items[i]);(顯示的使用”this”是為了表明這些是成員變量)另一個相似的原則是:永遠不要在for的第二個條件中調用任何方法。如下面方法所示,在每次循環的時候都會調用getCount()方法,這樣做比你在一個int先把結果保存起來開銷大很多。
for (int i = 0; i < this.getCount();i++)
dumpItems(this.getItem(i));同樣如果你要多次訪問一個變量,也最好先為它建立一個本地變量,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) { if (isHorizontalScrollBarEnabled()) { int size = 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次效率更高的棧變量訪問。
順便說明一下,就是方法的參數與本地變量的性能是相同的。
7、使用常量
static int intVal = 42;
static String strVal = “Hello, world!”;
編譯器會生成一個叫做
我們可以做一些改進,使用“final”關鍵字:
static final int intVal = 42;
static final String strVal = “Hello, world!”;
現在,類不再需要
將一個方法或類聲明為”final”不會帶來性能的提升,但是會幫助編譯器優化代碼。舉例說,如果編譯器知道一個”getter”方法不會被重載,那么編譯器會對其采用內聯調用。
你也可以將本地變量聲明為”final”,同樣,這也不會帶來性能的提升。使用”final”只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)。
8、謹慎使用foreach
9、避免使用枚舉
結束語:
為嵌入式系統編寫正確高效的代碼的最佳的方法就是去理解你的代碼究竟要做什么。如果你的確想要分配一個迭代器或者無論如何都要在Lists上面使用增強循環語法,那么一定是深思熟慮后的選擇,而不是一個不小心心的副作用。凡事預則立,不預則廢。一定要知道你在做什么。按照你自己的風格去編寫代碼,但一定要仔細考慮代碼所作的事,并找到提升速度的方法