這篇文章主要是介紹了一些小細節的優化技巧,當這些小技巧綜合使用起來的時候,對於整個App的性能提升還是有作用的,只是不能較大幅度的提升性能而已。選擇合適的算法與數據結構才應該是你首要考慮的因素,在這篇文章中不會涉及這方面。你應該使用這篇文章中的小技巧作爲平時寫代碼的習慣,這樣能夠提升代碼的效率。
通常來說,高效的代碼需要滿足下面兩個規則:
- 不要做冗餘的動作
- 如果能避免,儘量不要分配內存
代碼的執行效果會受到設備CPU,設備內存,系統版本等諸多因素的影響。爲了確保代碼能夠在不同設備上都運行良好,需要最大化代碼的效率。
避免創建不必要的對象
雖然GC可以回收不用的對象,可是爲這些對象分配內存,並回收它們同樣是需要耗費資源的。
因此請儘量避免創建不必要的對象,有下面一些例子來說明這個問題:
- 如果你需要返回一個String對象,並且你知道它最終會需要連接到一個StringBuffer,請修改你的實現方式,避免直接進行連接操作,應該採用創建一個臨時對象來做這個操作.
- 當從輸入的數據集中抽取出Strings的時候,嘗試返回原數據的substring對象,而不是創建一個重複的對象。
一個稍微激進點的做法是把所有多維的數據分解成1維的數組:
- 一組int數據要比一組Integer對象要好很多。可以得知,兩組1維數組要比一個2維數組更加的有效率。同樣的,這個道理可以推廣至其他原始數據類型。
- 如果你需要實現一個數組用來存放(Foo,Bar)的對象,嘗試分解爲Foo[]與Bar[]要比(Foo,Bar)好很多。(當然,爲了某些好的API的設計,可以適當做一些妥協。但是在自己的代碼內部,你應該多多使用分解後的容易。
通常來說,需要避免創建更多的對象。更少的對象意味者更少的GC動作,GC會對用戶體驗有比較直接的影響。
選擇Static而不是Virtual
如果你不需要訪問一個對象的值域,請保證這個方法是static類型的,這樣方法調用將快15%-20%。這是一個好的習慣,因爲你可以從方法聲明中得知調用無法改變這個對象的狀態。
常量聲明爲Static Final
先看下面這種聲明的方式
1 2 |
|
編譯器會使用方法來初始化上面的值,之後訪問的時候會需要先到它那裏查找,然後才返回數據。我們可以使用static final來提升性能:
1 2 |
|
這時再也不需要上面的那個方法來做多餘的查找動作了。
所以,請儘可能的爲常量聲明爲static final類型的。
避免內部的Getters/Setters
像C++等native language,通常使用getters(i = getCount())而不是直接訪問變量(i = mCount).這是編寫C++的一種優秀習慣,而且通常也被其他面向對象的語言所採用,例如C#與Java,因爲編譯器通常會做inline訪問,而且你需要限制或者調試變量,你可以在任何時候在getter/setter裏面添加代碼。
然而,在Android上,這是一個糟糕的寫法。Virtual method的調用比起直接訪問變量要耗費更多。那麼合理的做法是:在面向對象的設計當中應該使用getter/setter,但是在類的內部你應該直接訪問變量.
沒有JIT(Just In Time Compiler)時,直接訪問變量的速度是調用getter的3倍。有JIT時,直接訪問變量的速度是通過getter訪問的7倍。
請注意,如果你使用ProGuard, 你可以獲得同樣的效果,因爲ProGuard可以爲你inline accessors.
使用增強的For循環
請比較下面三種循環的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
- zero()是最慢的,因爲JIT沒有辦法對它進行優化。
- one()稍微快些。
- two() 在沒有做JIT時是最快的,可是如果經過JIT之後,與方法one()是差不多一樣快的。它使用了增強的循環方法for-each。
所以請儘量使用for-each的方法,但是對於ArrayList,請使用方法one()。
使用包級訪問而不是內部類的私有訪問
參考下面一段代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Foo$Inner裏面有訪問外部類的一個變量。這樣的做法會給系統造成額外的麻煩,請儘量避免。
避免使用float類型
Android系統中float類型的數據存取速度是int類型的一半,儘量優先採用int類型。
使用庫函數
儘量使用System.arraycopy()等一些封裝好的庫函數,它的效率是手動編寫copy實現的9倍多。
Tip: Also see Josh Bloch’s Effective Java, item 47.
謹慎使用native函數
當你需要把已經存在的native code遷移到Android,請謹慎使用JNI。如果你要使用JNI,請學習JNI Tips
關於性能的誤區
在沒有做JIT之前,使用一種確切的數據類型確實要比抽象的數據類型速度要更有效率。(例如,使用HashMap要比Map效率更高。) 有誤傳效率要高一倍,實際上只是6%左右。而且,在JIT之後,他們直接並沒有大多差異。
關於測量
上面文檔中出現的數據是Android的實際運行效果。我們可以用Traceview 來測量,但是測量的數據是沒有經過JIT優化的,所以實際的效果應該是要比測量的數據稍微好些。
關於如何測量與調試,還可以參考下面兩篇文章: