Android 性能优化小技巧
说明:本文翻译来源谷歌官网
非完全翻译,按理解来的,水平有限,勿喷!
原文地址
本文主要包括能够细微改善所有APP性能,通过这些改进不能造成性能的翻天覆地变化。选择正确的算法和数据结构才是的优先考虑的事,但是这已经超过了本文档的范围。你应该使用这些小技巧培养你的编码习惯,来创造高效的代码。
这里有两条基本的规则去写高效的代码:
- 不要做不需要做的事;
-
不要分配你可以避免分配的内存;
最复杂的问题之一是当进行细微优化的你需要确保你的APP能够运行在多种硬件上。运行在不同处理器上不同版本的虚拟机运行的速度不一样。这不是一个简单的问题你说“设备X比设备Y运行的快或慢”,需要测量你的结果在不同的设备。特殊的情况,在模拟器上的测量告诉你很少的性能差异在任何设备上。是否使用JIT即时有巨大的区别:有JIT的设备最好的代码不一定是没有JIT的设备上最好的代码。
确保你的APP在不多种设备上运行良好,确保你的代码是高效在各种层级,积极的优化你的APP性能。
避免创建不需要的对象
对象的创建从来都不是免费。垃圾回收和为分配临时对象的每个线程池使得分配代价更小,但是分配内存总是会比不分配内存代价更加的高。
当你分配对象在你的APP,你将强制进行周期性的垃圾回收,会造成卡顿的用户体验,并发的垃圾回收器在Android 2.3引入,但是不需要的工作还是应该被避免。
因此,你应该避避免创建对象你不需要的。下面有一些有用的例子:
- 假如你有个返回String,你知道这个结果总是被附加到一个StringBuffer上面,改变你的函数签名和实现,让函数直接附加而不是创建一个短生命周期的临时对象。
- 当获得额外的Strings从一个输入数据集,努力的返回一个原始数据的子字符串,而不是产生一个复制。 你将创建一个新的字符串对象,但是它共享char[]字符(博主注:这里是说明是字符保存在字符数组中)。
有一些更加彻底的想法就是拆分多维数组到对应的一维数组。
- 一个int类型的数组要比一个Integer对象的数组要更好,进一步的事实是两个平行的数组更加的高效对比一个拥有(int,int)的数组。其它的基本数据类型也是类似的道理。
- 假如你需要实现一个容器用来存储(Foo,Bar)元组对象,尝试去记住平行的Foo[]和Bar[]数组是更好的。
通常来说,只要可以就应该避免创建短生命周期的临时对象。更好的创建对象就意味着更少的频次的垃圾回收,这对用户体验有直接的影响。
选择Static而不是Virtual
加入你不要或者对象的成员域,使你的方法为Static.调用将快15%-20%。这也是一个好的习惯,因为你能通过方法签名来告诉这个方法不能修改对象状态。
使用Static Final 给常量 (博主注:很有用)
思考下面的声明在顶级类。
static int intVal = 42;
static String strVal = "hello world!";
编译器产生一个类的初始化方法叫做,这个方法将被执行,当类第一次被使用。这个方法存储数值42到intVal,以及提取一个引用从类的字符串常量表给strVal。当这些数值之后被引用,它们能够通过域查找来获取。
我们能够改善这个问题通过”final”关键字
static final int intVal = 42;
static final String strVal = "Hello, world!";
这个类不再需要方法,因为这个常量进入在Dex文件中的静态域初始化器。代码引用intVal将直接使用整数42,并且获取strVal使用相对消耗较低的“字符串常量”结果而不是域查找。
避免内部的Getters/Setters
在native语言像C++,使用(i==getCount())而不是(i = mCount)是一个好习惯。它是一个优秀的习惯对C++来说,也是惯例对C#和java来说。因为编译器能偶内联这种获取,假如你需要重构或者Debug域的获取,您能够在任何时候添加代码。
但是,这是一个坏主意在Android上。虚方法的调用是昂贵的,比实例域的查找更昂贵。公共接口拥有getters和setters是合理的,但是在类的内部应该直接进行调用。
没有JIT,直接域的获取比调用一个不重要的getter快三倍。如果是拥有JIT,则快了7倍。
使用增强过的循环语法
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() 是最慢的
one() 快一点
two() 是最快的的
针对私有内部类,使用包可见权限而不是私用权限
考虑下面的一种定义:
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获取Foo的的私有成员变量是不合法的,因为Foo和Foo Inner是不合法的,即使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(),内部方法将调用以上静态方法。这意味着就本质而言,上述方式中你获取成员变量是通过getter方法。更早之前我们谈论过,为什么getter要直接获取域要慢。
避免使用浮点类型
根据经验来说,在安卓设备上,浮点类型要比整数类型慢两倍。
在速度方面,float和double没有区别在大多数的现代硬件。控件方面,double要大两倍。对桌面机器而言,空间不是问题,你应该选择double而不是float.
了解并使用Libraries
使用更多的Library代码,像 String.indexOf(),System.arraycopy().
谨慎的使用natvie 方法
使用已有的natvie代码来对接Android,native是有用的,不要为了速度优化而是用native
性能谬见
没有JIT的设备(Android2.2版本提供了JIT机制提升性能),具体的类型比接口类型高效。
但是JIT的设备上两者没有太大的差距
拥有JIT的设备上会缓存域的获取。拥有JIT的设备对域变量和本地变量获取拥有相同的耗费,所以这不值得优化,除非是为了代码的可读性。
持续的测量
在你开始优化之前,确保你有需要解决的问题。确保你能够精确的测量你已经存在的程序的性能,否则你没法测量使用常识方案后性能改善。
使用TraceView工具是有用的。根据TraceView的数据建议改善代码后,不使用TraceView ,确认代码的性能有所改善。