原文地址:http://developer.android.com/training/articles/perf-tips.html
代码优化技巧
这篇文章的重点在于为你讲解一些细微优化程序性能的小技巧,请你不要指望能给你的程序带来翻天覆地的变化。性能强悍的程序最要紧的当然是选对算法和数据结构,但这不在本文的讨论范围。希望你以后编程能熟练运用这些技巧写出高效的代码并把它们化为你的习惯。
高效的代码有两个基本原则:
1.不要去做根本不需要做的事情
2.避免分配不必要的内存空间
当你对你的程序做一些小优化的时候,最让你头疼的问题可能是你的程序要在许多类型的设备上跑。不同的处理器有不同的虚拟机运行的速度也不尽相同。而且你也不能简单地说x设备就比y设备快多少倍。其实从对模拟器的测试你很难看出性能优化在不同设备上得差异。对于一台设备,真正会产生巨大区别的是这个设备是否有JIT(Just in time compiler),对于有JIT的设备运行的优质代码不一定在没有JIT的设备上也能表现优异。
为了保证你的程序在不同设备上良好运行,请你确保你的代码在方方面面都表现优异。
避免创建不必要的对象
对象的创建从来就不是一个省油的灯。带有单线分配池的垃圾收集器对于临时对象的确可以减省开销,但最好的方式是尽量避免这种开销。
如果你在程序里创建了过多对象,将会导致周期性的对象回收,而这恰恰会影响用户体验。android 2.3引入的同步垃圾收集器也许可以救你一命,但最好还是想办法避免一些不必要的麻烦。
因此,你应该避免创建你不需要的对象实例。举个例子来说:
*假设现在你有一个方法,它要返回的类型是string,而你知道不管怎样结果还是会被添加到StringBuffer里去,试着改变返回参数的类型,而不是创建一个短命的临时对象。
*当你想从输入中获取字符串,试着返回原数据的子串,而不是创建一个副本。
还有一个可能有点极端的办法是,把多维数组削减成平行的一位数组。
*一个int类型的数组要比Integer类型的数组来的更棒,因此可以推导出一个事实就是两个平行的int类型数组要比一个由(int,int)对象组成的数组高效,
*如果你需要实现一个容器来存放一组一组的(Foo,Bar)对象,这时候你需要注意啦,分别定义两个Foo[]和Bar[]数组效果往往会比定义一个由(Foo,Bar)对象组成的数组更好。
总的来说就是,尽可能避免创建不必要的临时对象。对象的减少会使垃圾清理的频率下降,而这对用户体验有着直接的影响。
使用static
如果你的方法不需要使用到类中的数据域,把它声明成static的,当你调用这个方法的时候大概会快15%-20%。还有一个用处就是,你可以从这个方法的修饰符判断它不会修改对象的状态。
用static final 声明常量
现在我们假设在一个类里有以下声明:
static int intVal=42;
static String strVal="Hello, world!";
当类第一次被使用的时候编译器会先生成一个叫<clinit>的用于初始化类的方法,这个方法会将42存入intVal中,从类文件的字符常量表中提取出引用给strVal。当这些变量在待会儿被使用的时候,要通过域查找的方式来获取。
下面我们用final来改进以下:
static final int intVal=42;
static final String strVal="Hello, world!";
这时候这个类就不再需要<clinit>方法了,因为常量直接被初始化在dex文件里,之后你使用这些变量的时候就可以直接获取到值而不需要麻烦地去查找域。
避免在类内部使用Getters/Setters
在C++中使用getter可能很常见,比如说我们用i=getCount()而不是i=mCount,这对C++而言无疑是个好习惯,而且对java C#也适用,因为编译器都可以直接访问,如果你需要添加什么限制你可以在任何时候添加上你想要的代码。
但是最好不要再Android中这样用,抽象方法的调用开销是十分昂贵的,跟对象数据域查找的耗费有的一拼,添加Getter和Setter对面向对象程序设计而言当然是极其合理地事情,但当你在这个类里面的时候你就不要去调什么getter setter了,直接访问数据域就好。
对于没有JIT的设备来说,直接获取数据域是调用GetterSetter的三倍,而由JIT的则达到了七倍。
使用for-each loop
for-each循环语句可以在实现了Iterable接口的集合或数组中使用。
这里展示了几种迭代数组的方式:
static classFoo
{
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()要快一点,因为它把那些需要查找的变量都定义为了临时变量,弥补了zero()的缺陷。
two()对没有JIT的设备最快,几乎跟带JIT的设备运行one()难分高下。
所以最好默认使用for-each循环,但有个特例就是ArrayList,对于ArrayList还是用one()这种方式。
对内部类的数据域声明默认包权限而不是private
看看下面的代码:
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);
}
}
我们在这个类的内部定义了一个内部类来直接访问外部类的数据域和方法。如你所料,程序会输出Value is 27,完全没有什么不对的地方。
但VM不这么认为,VM会把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);
}
每次内部类访问外部类的数据域或调用外部类的方法都会调用到这些静态方法,这就相当于我们前面讲过的在类内部使用Getter,而之前你已经见识过Getter的可怕了。
所以当你写类似的代码时候,最好把内部类要访问的数据域设为默认的包访问权限,而不是设为private,但这样做有个问题就是这些数据域也会被包中的其他类访问到,所以当你写公共API的时候就不要用。
避免使用浮点数
在Android设备上,float类型通常要比integer类型慢2倍。
就速度而言,float和double没啥区别。关系到使用空间的话,double大概会占用float空间的两倍。当你不需要太节省空间的时候还是考虑用double吧。
了解并学会使用JAVA内置库
使用库有很多理由,这里的一个就是系统可以直接把库方法替换为汇编语言。比如说String.indexOf()方法,就直接在dalvik虚拟机中替换为原来的,还有System.arraycopy()比你任何自己写得方法要快至少九倍。
以上。