前言java
本文翻译自Android开发者官网的一篇文档,主要用于介绍app开发中性能优化的一实践要点。android
中国版官网原文地址为:https://developer.android.google.cn/training/articles/perf-tips。算法
路径为:Android Developers > Docs > 指南 > Best practies > Performance > Performance Tips编程
正文数组
本文主要覆盖了细微的优化,虽然他们组合起来可以提升整个应用的性能,可是这些改变会致使显著的性能影响是不太可能的。选择正确的算法和数据结构应该始终是您优先要考虑的,可是这在本文的范围以外。您应该使用本文中的这些提示来做为常规的编码实践,这样为了常规的代码效率,您能够将这些编码实践融入您的习惯中。缓存
这里有两个编写高效代码的基本规则:性能优化
其中一个您在细微优化Android应用时要面对的棘手的问题是,您的应用肯定在多种类型的硬件上运行。不一样版本的虚拟机在不一样的处理器上以不一样的速度运行。通常来讲,您甚至不能简单地说“设备X是一个比设备Y更快/慢的因素F”,而且将您的结果从一个设备缩放当另一个设备。通常来讲,模拟器上的测量机会不会告诉您任何关于设备的性能。一样,在拥有或者没有JIT的设备之间也存在着巨大的差别:有JIT的设备上最好的代码,在没有JIT的设备上并不老是最好的代码。数据结构
为了确保您的应用在各类各样的设备上都运行良好,请确保您的代码在全部级别中都是有效率的,而且积极地优化您的性能。架构
避免建立没必要要的对象并发
对象建立历来就不是免费的。一个带有为每一个线程分配临时对象池的分代垃圾收集器可能让分配更加便宜,可是分配内存老是比不分配内存要更加昂贵。
当您在应用中分配更多的对象时,您将强制执行一个周期性的垃圾收集,从而在用户体验方面建立小的“打嗝”。在Android2.3中引入的并发垃圾收集器帮上了忙,可是应该避免没必要要的工做。
这样,您应该避免建立您不须要的对象实例。以下一些实例能够帮到您:
一个更加完全的主意是将多维数组划分为并行的一维数组:
通常来讲,若是能够,请避免建立短时间的临时对象。建立越少的对象意味着越低频率的垃圾收集,这对用户体验会有直接的影响。
更喜欢静态的而不是虚拟的
若是您无需访问对象的字段,让方法成为静态的。这样调用将会快15%-20%。这也是一个很好的实践,由于从方法签名能够辨别出调用该方法不会改变对象的状态。
为常量使用static final
在类的顶部考虑以下的声明:
1 static int intVal = 42; 2 static String strVal = "Hello, world!";
编译器产生了一个被称为<clinit>的类初始化器方法,当类第一次使用的时候它会被执行。这个方法将42存入intVal,而且从类文件字符串常量表中为strVal选取引用。当这些值稍后被引用时,它们会经过字段查找被访问。
咱们可使用“final”关键字来改善这些问题:
1 static final int intVal = 42; 2 static final String strVal = "Hello, world!";
该类再也不须要<clinit>方法,由于这些常量进入了dex文件中的静态字段初始化器。指向intVal的代码将直接使用整形值42,而且对strVal的访问将使用一个相对不那么贵的“字符串常量”指令,而不是字段查找。
★ 注意:这个优化只提供了原始类型和String常量,而不是任意的引用类型。尽量在任什么时候候声明常量为static final 仍然是一个很好的实践。
使用增强的for循环语法
增强的for循环(有时也被称为“for-each”循环)能够用于实现了Iterable接口的集合和数组。对于集合,将分配迭代器对hasNext()和next()进行接口调用。对于ArrayList,手写的计数循环速度大约快3倍(有或者没有JIT),可是对于其它的集合,增强的for循环语法将彻底等价于显示的迭代器使用。
对数组进行迭代有若干种选择:
1 static class Foo { 2 int splat; 3 } 4 5 Foo[] array = ... 6 7 public void zero() { 8 int sum = 0; 9 for (int i = 0; i < array.length; ++i) { 10 sum += array[i].splat; 11 } 12 } 13 14 public void one() { 15 int sum = 0; 16 Foo[] localArray = array; 17 int len = localArray.length; 18 19 for (int i = 0; i < len; ++i) { 20 sum += localArray[i].splat; 21 } 22 } 23 24 public void two() { 25 int sum = 0; 26 for (Foo a : array) { 27 sum += a.splat; 28 } 29 }
zero()方法是最慢的,由于JIT还不能优化循环中每一次迭代中获取数组长度的花费。
one()方法稍微快一些。它将一切都推入本地变量,从而避免了查找。只有数组长度提供了性能上的好处。
two()在没有JIT的设备上是最快的,在有JIT的设备上和one()没有区别。它使用了增强的for循环语法,其在Java编程语言的1.5版本中引入。
因此,您应该默认使用增强的for循环,可是为性能要求较高的ArrayList迭代考虑手写计数循环。
★ 提示:也能够查阅 Josh Bloch 的 《Effective Java》,项目46。
考虑使用包而不是私有内部类的私有访问
考虑以下类定义:
1 public class Foo { 2 private class Inner { 3 void stuff() { 4 Foo.this.doStuff(Foo.this.mValue); 5 } 6 } 7 8 private int mValue; 9 10 public void run() { 11 Inner in = new Inner(); 12 mValue = 27; 13 in.stuff(); 14 } 15 16 private void doStuff(int value) { 17 System.out.println("Value is " + value); 18 } 19 }
在这里,重点的是定义一个私有的内部类(Foo$Inner),它直接访问外部类中的一个私有方法和一个私有的实例字段。这是合法的,而且该代码会如指望的那样打印“Value is 27”。
问题是虚拟机认为从Foo$Inner中直接访问Foo的私有成员是非法的,由于Foo和Foo$Inner是不一样的类,虽然Java语言容许内部类访问外部类的私有成员。为了链接这个沟壑,编译器生成了两个合成的方法:
1 /*package*/ static int Foo.access$100(Foo foo) { 2 return foo.mValue; 3 } 4 /*package*/ static void Foo.access$200(Foo foo, int value) { 5 foo.doStuff(value); 6 }
不管何时须要访问外部类中的mValue字段或者调用doStuff()方法时,内部类代码会调用这些静态的方法。这意味着上面的代码已经概括为经过访问器方法访问成员字段的情形。更早咱们讨论了访问器是如何比直接字段访问更慢的,因此这是一个特定语言习惯的例子,致使了“不可见的”性能打击。
若是您正在性能热点中像这样使用代码,您能够经过声明被内部类访问的字段和方法为包访问来避免这个开销,而不是私有访问。遗憾的是,这意味着字段能够被同一个包中的其它类直接访问,全部您不该该再公共API中使用它。
避免使用浮点型
根据经验,在Android驱动设备上,浮点型大约比整型慢两倍。
从速度方面看,在更现代的硬件上float和double之间没有区别。从空间上看,double是float的两倍大。和台式机同样,假设空间不是问题,您应该使用double而不是float。
即便是整型也同样,一些处理器有硬件乘法却没有硬件除法。在这些状况下,整数相除和模运算是在软件中执行的——若是您正在设计hash表或者处理大量数学问题,应该考虑这个问题。
了解并使用库
除了全部通用的更喜欢库代码而不是调用本身的代码的缘由以外,请记住,系统能够自由地使用手动编码的汇编程序来取代库方法调用,这可能比JIT可以为等效于Java而生成的最好代码更好。这里一个典型的例子就是String.indexOf()以及相关的API,Dalvik使用内联的内部函数来取代它们。相似地,System.arraycopy()方法的速度大约是带有JIT的Nexus One上手动编码循环速度的9倍。
★ 提示:也能够查阅 Josh Bloch 的 《Effective Java》,项目47。
慎重使用原生方法
使用Android NDK包含开发含有原生代码的应用不必定比使用Java语言编程更有效。首先,有一笔花费与java到原生的转移有关,而且JIT没法跨越边界进行优化。若是您正在分配原生资源(原生堆上的内存,文件描述符,或者其它),及时安排这些资源的收集多是明显更加困难的。您也须要为每个您但愿在上面运行的架构编译代码(而不是依赖它拥有JIT)。您甚至可能不得不为那些您认为相同的架构编译多个版本:为G1中ARM处理器编译的原生代码不能充分利用Nexus One中的ARM,而且为Nexus One中ARM编译的代码在G1的ARM上也不会运行。
当您拥有已经存在的想移植到Android的原生代码库,而不是为了“加速”用Java语言编写的Android应用的部分功能时,原生代码主体上是有用的。
若是您确实须要使用原生代码,您应该阅读咱们的【JNI提示】。
★ 提示:也能够查阅 Josh Bloch 的 《Effective Java》,项目54。
性能神话
在没有JIT的设备上,经过具备准确类型而非接口的变量调用方法稍微更有效是个事实。(例如,在HashMap映射上调用方法比在Map映射上要便宜,虽然在这两种状况下映射都是HashMap。)这并非变慢两倍的情形;实际的差异更有多是慢6%。此外,JIT让这二者有效地没有区别。
没有JIT的设备,缓存字段访问大约比重复访问该字段快20%。有JIT的设备上,字段访问和本地访问花费大体相同,因此这不是有价值的优化,除非您感受它让您的代码更容易阅读。(对于final,static和static final字段也是如此。)
老是测量
在开始优化以前,确保您有问题须要解决。确保您能够准确测量存在的性能,不然您将不能测量到您所尝试的选择所带来的好处。
您也可能发现【TraceView】对分析是有用的,可是意识到它让JIT当前不可以使用是很重要的,这可能致使它错误地分配代码时间,而JIT可能会赢回来。尤其重要的是,在按照TraceView数据提供的建议更改后,确保实际上生成的代码在没有TraceView时运行得更快。
更多帮助分析和调试应用的信息,请查阅以下文档:
结语
本文最大限度保持原文的意思,因为笔者水平有限,如有翻译不许确或不稳当的地方,请指正,谢谢!