对于咱们设计的应用须要作到如下特征: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优化的,因此实际的效果应该是要比测量的数据稍微好些。