對於咱們設計的應用須要作到如下特徵:build an app that's smooth, responsive(反應敏捷), and uses as little battery as possible。java
主要包含如下內容:算法
一般來講,高效的代碼須要知足下面兩個原則:編程
不要作冗餘的工做數組
儘可能避免執行過多的內存分配操做promise
To ensure your app performs well across a wide variety of devices, ensure your code is efficient at all levels and agressively optimize your performance. 目標:爲了確保App在各設備上都能良好運行,就要確保你的代碼在不一樣檔次的設備上都儘量的優化。緩存
如下方法能夠用於優化代碼:性能優化
方法一: 避免建立沒必要要的對象數據結構
Object creation is never free. 建立對象歷來不是免費的。Generational GC(with per-thread allocation pools)可使臨時對象的分配變得廉價一些,可是執行分配內存老是比不執行分配操做更昂貴。隨着你在App中分配更多的對象,你可能須要強制gc,而gc操做會給用戶體驗帶來一點點卡頓。雖然從Android 2.3開始,引入了併發gc,它能夠幫助你顯著提高gc的效率,減輕卡頓,但畢竟沒必要要的內存分配操做仍是應該儘可能避免。架構
所以請儘可能避免建立沒必要要的對象,有下面一些例子來講明這個問題:併發
If you have a method returning a string, and you know that its result will always be appended to a StringBuffer anyway, change your signature and implementation so that the function does the append directly, instead of creating a short-lived temporary object. 若是你須要返回一個String對象,而且你知道它最終會須要鏈接到一個StringBuffer,請修改你的函數實現方式,避免直接進行鏈接操做,應該採用建立一個臨時對象來作字符串的拼接這個操做。
When extracting strings from a set of input data, try to return a substring of the original data, instead of creating a copy. You will create a new String object, but it will share the char[] with the data. (The trade-off being that if you're only using a small part of the original input, you'll be keeping it all around in memory anyway if you go this route.) 當從已經存在的數據集中抽取出String的時候,嘗試返回原數據的substring對象,而不是建立一個重複的對象。使用substring的方式,你將會獲得一個新的String對象,可是這個string對象是和原string共享內部char[]空間的。
一個稍微激進點的作法是把全部多維的數據分解成一維的數組:
An array of ints is a much better than an array of Integer objects, but this also generalizes to the fact that two parallel arrays of ints are also a lot more efficient than an array of (int,int) objects. The same goes for any combination of primitive types. 一組int數據要比一組Integer對象要好不少。能夠得知,兩組一維數組要比一個二維數組更加的有效率。一樣的,這個道理能夠推廣至其餘原始數據類型。
If you need to implement a container that stores tuples of (Foo,Bar) objects, try to remember that two parallel Foo[] and Bar[] arrays are generally much better than a single array of custom (Foo,Bar) objects. (The exception to this, of course, is when you're designing an API for other code to access. In those cases, it's usually better to make a small compromise to the speed in order to achieve a good API design. But in your own internal code, you should try and be as efficient as possible.) 若是你須要實現一個數組用來存放(Foo,Bar)的對象,記住使用Foo[]與Bar[]要比(Foo,Bar)好不少。(例外的是,爲了某些好的API的設計,能夠適當作一些妥協。可是在本身的代碼內部,你應該多多使用分解後的容易)
一般來講,須要避免建立更多的臨時對象。更少的對象意味者更少的gc動做,gc會對用戶體驗有比較直接的影響。
方法二:使用static而不是virtual
若是你不須要訪問一個對象的值,請保證這個方法是static類型的,這樣方法調用將快15%-20%。這是一個好的習慣,由於你能夠從方法聲明中得知調用沒法改變這個對象的狀態。
If you don't need to access an object's fields, make your method static. Invocations will be about 15%-20% faster. It's also good practice, because you can tell from the method signature that calling the method can't alter the object's state.
方法三:常量聲明爲static final
考慮下述聲明:
static int intVal = 42;
static String strVal = "Hello, world!";
編譯器會使用一個初始化類的函數(名爲:clinit),而後當類第一次被使用的時候執行。這個函數將42存入intVal,還從class文件的常量表中提取了strVal的引用。當以後使用intVal或strVal的時候,他們會直接被查詢到。
可使用final聲明來進行優化:
static final int intVal = 42;
static final String strVal = "Hello, world!";
這時不再須要上面的方法了,由於final聲明的常量進入了靜態dex文件的域初始化部分。調用intVal的代碼會直接使用42,調用strVal的代碼也會使用一個相對廉價的「字符串常量」指令,而不是查表。
須要注意的是:這個優化方法只對原始類型和String類型有效,而不是任意引用類型。不過,在必要時使用static final是個很好的習慣。
方法四:避免內部的Getters/Setters
像C++等native language,一般使用getters(i = getCount())而不是直接訪問變量(i = mCount)。這是編寫C++的一種優秀習慣,並且一般也被其餘面向對象的語言所採用,例如C#與Java,由於編譯器一般會作inline訪問,並且你須要限制或者調試變量,你能夠在任什麼時候候在getter/setter裏面添加代碼。
然而,在Android上,這不是一個好的寫法。虛函數的調用比起直接訪問變量要耗費更多。在面向對象編程中,將getter和setting暴露給公用接口是合理的,但在類內部應該僅僅使用域直接訪問。
在沒有JIT(Just In Time Compiler)時,直接訪問變量的速度是調用getter的3倍。有JIT時,直接訪問變量的速度是經過getter訪問的7倍。
請注意,若是你使用ProGuard,你能夠得到一樣的效果,由於ProGuard能夠爲你inline accessors.
方法五:使用加強for循環
加強的For循環(也被稱爲 for-each 循環)能夠被用在實現了 Iterable 接口的 collections 以及數組上。使用collection的時候,Iterator會被分配,用於for-each調用hasNext()和next()方法。使用ArrayList時,手寫的計數式for循環會快3倍(無論有沒有JIT),可是對於其餘collection,加強的for-each循環寫法會和迭代器寫法的效率同樣。
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()是最慢的,由於JIT沒有辦法對它進行優化。
one()稍微快些。
two() 在沒有作JIT時是最快的,但是若是通過JIT以後,與方法one()是差很少同樣快的。它使用了加強的循環方法for-each。
因此請儘可能使用for-each的方法,可是對於ArrayList,請使用方法one()。
爲何ArrayList類中存在內部類:ArrayListIterator。該內部類聲明以下:
private class ArrayListIterator implements Iterator<E> { /** Number of elements remaining in this iteration */ private int remaining = size; /** Index of element that remove() would remove, or -1 if no such elt */ private int removalIndex = -1; /** The expected modCount value */ private int expectedModCount = modCount; public boolean hasNext() { return remaining != 0; } ......
方法六:使用包級訪問而不是私有的內部類訪問
考慮以下代碼:
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); } }
這裏重要的是,咱們定義了一個私有的內部類(Foo$Inner),它直接訪問了外部類中的私有方法以及私有成員對象。這是合法的,這段代碼也會如同預期同樣打印出"Value is 27"。問題是,VM由於Foo和Foo$Inner是不一樣的類,會認爲在Foo$Inner中直接訪問Foo類的私有成員是不合法的。即便Java語言容許內部類訪問外部類的私有成員。
/*package*/ static int Foo.access$100(Foo foo) { return foo.mValue; } /*package*/ static void Foo.access$200(Foo foo, int value) { foo.doStuff(value); }
每當內部類須要訪問外部類中的mValue成員或須要調用doStuff()函數時,它都會調用這些靜態方法。這意味着,上面的代碼能夠歸結爲,經過accessor函數來訪問成員變量。早些時候咱們說過,經過accessor會比直接訪問域要慢。因此,這是一個特定語言用法形成性能下降的例子。
若是你正在性能熱區(hotspot:高頻率、重複執行的代碼段)使用像這樣的代碼,你能夠把內部類須要訪問的域和方法聲明爲包級訪問,而不是私有訪問權限。不幸的是,這意味着在相同包中的其餘類也能夠直接訪問這些域,因此在公開的API中你不能這樣作。
方法七:避免使用float類型
Android系統中float類型的數據存取速度是int類型的一半,儘可能優先採用int類型。就速度而言,現代硬件上,float 和 double 的速度是同樣的。空間而言,double 是兩倍float的大小。在空間不是問題的狀況下,你應該使用 double 。一樣,對於整型,有些處理器實現了硬件幾倍的乘法,可是沒有除法。這時,整型的除法和取餘是在軟件內部實現的,這在你使用哈希表或大量計算操做時要考慮到。
方法八:使用庫函數
除了那些常見的讓你多使用自帶庫函數的理由之外,記得系統函數有時能夠替代第三方庫,而且還有彙編級別的優化,他們一般比帶有JIT的Java編譯出來的代碼更高效。典型的例子是:Android API 中的 String.indexOf(),Dalvik出於內聯性能考慮將其替換。一樣 System.arraycopy()函數也被替換,這樣的性能在Nexus One測試,比手寫的for循環並使用JIT還快9倍。
方法九:謹慎使用Native方法
結合Android NDK使用native代碼開發,並不老是比Java直接開發的效率更好的。Java轉native代碼是有代價的,並且JIT不能在這種狀況下作優化。若是你在native代碼中分配資源(好比native堆上的內存,文件描述符等等),這會對收集這些資源形成巨大的困難。你同時也須要爲各類架構從新編譯代碼(而不是依賴JIT)。你甚至對已一樣架構的設備都須要編譯多個版本:爲G1的ARM架構編譯的版本不能徹底使用Nexus One上ARM架構的優點,反之亦然。
Native 代碼是在你已經有本地代碼,想把它移植到Android平臺時有優點,而不是爲了優化已有的Android Java代碼使用。
若是你要使用JNI,請學習JNI Tips。
方法十:性能優化誤區
在沒有JIT的設備上,使用一種確切的數據類型確實要比抽象的數據類型速度要更有效率(例如,調用HashMap map要比調用Map map效率更高)。有誤傳效率要高一倍,實際上只是6%左右。並且,在JIT以後,他們直接並無大多差別。
在沒有JIT的設備上,讀取緩存域比直接讀取實際數據大概快20%。有JIT時,域讀取和本地讀取基本無差。因此優化並不值得除非你以爲能讓你的代碼更易讀(這對 final, static, static final 域一樣適用)。
方法十一:獲取性能優化的基準
在優化以前,你應該肯定你遇到了性能問題。你應該確保你可以準確測量出如今的性能,不然你也不會知道優化是否真的有效。
本章節中全部的技巧都須要Benchmark(基準測試)的支持。Benchmark能夠在 code.google.com "dalvik" project中找到。
Benchmark是基於Java版本的 Caliper microbenchmarking框架開發的。Microbenchmarking很難作準確,因此Caliper幫你完成這部分工做,甚至還幫你測了你沒想到須要測量的部分(由於,VM幫你管理了代碼優化,你很難知道這部分優化有多大效果)。咱們強烈推薦使用Caliper來作你的基準微測工做。
咱們也能夠用Traceview 來測量,可是測量的數據是沒有通過JIT優化的,因此實際的效果應該是要比測量的數據稍微好些。