Android程序性能提高第一章--小小的编程技巧篇
原文地址:https://developer.android.com/training/articles/perf-tips.html
这篇文档只是用来描述我们在编码过程中的一些细小的性能优化技巧。当然数据结构的优化,框架的选择等宏观的技术,不在本篇的范围内。(从整体来说,数据结构优化、框架优化等取得的效果肯定是更明显,这也是为什么会有架构师这个职务存在的原因了。)
以下是两条基本的优化规则:
- 能不做的工作,就不要做;
- 能不申请的内存,就不要申请;
-
避免创建不用的Object
创建对象永远不是免费的。常见的操作如下:
-
- 不要创建不适用的局部变量;
- 返回值\函数参数也一样是需要花费时间创建的。比如返回String,实际上在返回的时候,会重新创建一个String来用于返回。如果可以,将其优化。比如直接传入StringBuffer;
- 一个int数组比一个Integer对象的数组要好得多。基本数据类型尽量避免使用其对应的对象;
一般来说,避免创建短期临时对象,如果可以的话。创建的对象越少意味着垃圾收集越少,直接影响用户体验
-
优先使用Static函数
如果函数不需要使用对象的内部数据,应该将其改为Static函数。Static函数的执行效率要高于一般数据15~20%。同时也是很好的软件编写习惯,因为别人可以从函数的描述符(Static)中得知此函数不会改变对象的状态;
-
对于常量,使用Const修饰符
考虑下如下在类开头的声明
static int intVal = 42; static String strVal = "Hello, world!";
当类被第一次使用的时候,编译器就会生成一个类的initializer 叫<clinit>,并执行之。clinit会将42复制给一个变量(intVal),然后在类文件的StringMap中使用一个引用以指向字符串。每次使用这两个值,都需要在类的成员变量中查找对应的值。
我们可以通过final来提高性能:
static final int intVal = 42; static final String strVal = "Hello, world!";
如果我们使用了final修饰符,那么这两个值就会被认为是常量,然后就直接以42、字符串替换代码中使用的地方,而不再需要执行过程中动态查找。
注意:此优化仅适用于原始类型和字符串常量,而不适用于任意引用类型。 不过,尽可能地声明常量静态final是个好习惯。
-
使用优化过的for循环
使用foreach可以加速循环速度,对于可以使用foreach的情况(容器类或实现了iterable接口的类),速度可以有三倍的提高。
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()函数是最慢的,因为mArray.length也一样是需要计算的。
One()速度稍微快一些,因为节省了计算数组长度的开销;
Two()速度最快,因为foreach在执行过程中,通过iterator对于循环进行了优化。
-
对于私有的内部类,应该考虑将外部类的变量设置为包可见而不是私有的
阅读如下的代码
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); } }
这里体现了是内部类的一个特点,即虽然内部类也是一个独立的类(在经过编译后,会生成独立的类.java文件),但是其依旧可以直接调用外部类的所有成员变量和函数。
但是对于java的VM来说,类Foo#Inner也不能直接调用Foo的私有成员变量和函数,其认为这是非法的。(即使在Java语言中是合法的)为了解决这个问题,java的编译器会给Foo类增加两个函数:
/*package*/ static int Foo.access$100(Foo foo) { return foo.mValue; } /*package*/ static void Foo.access$200(Foo foo, int value) { foo.doStuff(value); }
通过这样的Bridge方式,外部类Inner也可以调用Foo的内部类了,但是这种操作真的是坑爹啊。这就会导致变量的使用无法通过直接引用的方式,而是通过函数调用的方式。当然也就会引起程序性能下降。所以这也是一个语言的愚蠢实现导致“不可见”的性能损失的一个反面例子。
为了避免这种性能损失,我们可以通过申明变量或者函数为包可见。这样内部类的使用方式就不在需要通过函数调用了。当然同时我们也会把变量暴露给包里面的其他类,因此这种方式不建议作为公共API来提供给客户使用。