Android性能优化建议

翻译自:http://developer.android.com/training/articles/perf-tips.html
一、避免创建不必要的对象
1、如果一个方法返回String,而这个返回值要附加到StringBuffer,那么,应该直接append(方法),不要再有中间临时变量。
2、当从一组输入数据中提取字符串时,尝试使用子串,不要创建一个拷贝。你会创建一个新的String,但是会与原数据共享char[]。需要权衡的是,所有原始输入数据都在在内存中,如果你只使用原数据的一小部分,会浪费一些内存。

更激进彻底的方法是,把多维数组转成多个一维数组。
1、int数组比Integer数组更好,两个平行数组int[]比一个二维数组int[][]更有效,其他基本类型也一样
2、如果要实现一个容器类来保存Foo、Bar,记住:两个一维数组Foo[]和Bar[]比容器数组更高效。(如果是开发提供给别人使用的API,还是使用类封装一下比较好,更容易理解。但自己用的话,你应该尝试更高效的写法)
一般而言,尽可能避免创建短期临时变量。越少的对象创建,意味着GC的频率越低,对提升用户体验有直接效果。

二、使用static
如果不需要访问实例变量,应该把方法声明成static,调用速度会提高15%-20%。这也是一个很好的实践,因为你可以从方法声明辨认出该方法不能改变对象状态。

三、常量使用static final
看下面的声明:


static int intVal = 42;
static String strVal = "Hello, world!";

编译器生成一个叫的类初始化方法,当类第一次加载时执行。该方法保存42到intVal,并且从class文件的string常量表提取一个引用给strVal。当这些值被引用的时候,通过字段查找来找到这些变量。
可以通过加入final来改善:


static final int intVal = 42;
static final String strVal = "Hello, world!";

该类不再需要方法,因为常量进入dex文件的静态字段初始化器中,引用intVal的代码将会直接使用42,而访问strVal会使用相对高效的”string constant” 指令替代字段查找。这条优化建议只适用于原始数据类型和String,非所有引用类型。但尽可能地用static final声明常量是一条很好的实践。
尼玛,上面是直接翻的,自己感觉都看不懂,我的理解是,编译生成的class文件,所有引用intVal的地方直接换成了42,而字符串,会有一个常量池,不知道对不对。

四、避免内部调用Getter/Setter
在像C++这样的Native(相对Java的虚拟机)语言中,通常做法是使用Getter(i = getCount())代替直接访问字段(i = mCount),这是一个极好的习惯,并且在其他面向对象语言中像C#,Java也经常这么用,因为编译器通常可以内联访问,并且如果你需要限制访问或Debug字段访问,可以随时在setter/getter中添加代码。
然而,在Android这样做不是个主意。Virtual method调用是非常昂贵的,远超字段查找。跟随通用的面对对象编程实践,使用Getter/Setter是合理的,但在一个类中,你应该直接访问字段。(根据wikipedia,Virtual method指的是可以被子类覆盖的方法,http://en.wikipedia.org/wiki/Virtual_function)
没有JIT,直接访问字段比调用Getter快3倍,有JIT(字段访问与局部变量访问开销一样),直接访问字段比调用Getter快7倍。
如果你用ProGuard,你可以鱼与熊掌兼得,因为ProGuard会内联存取器(setter/getter).

五、使用增强的for循环语法。
增强的for循环(for-each)可以用于实现Iterable接口的集合和数组。在集合中,迭代器分配给接口调用hasNext() 和next()方法。使用ArrayList,hand-written counted loop大概要快3倍(不管有没有JIT),但是其他集合类使用for-each循环和显示使用迭代器相当。
注:hand-written counted loop,我也不知道是啥,我猜应该是for(int i=0;i 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()相同。它使用了在Java 1.5(5.0)中介绍的增强的for循环。
所以,除了ArrayList外,能用for each的都用for each.

六、使用package代替内部private类访问内部private字段方法。
水平有限,这段不翻了,从举的例子来看大部分人也不会这么用,最终原因参考以上第四条,因为内部私有类访问内部私有字段方法会转换成访问Setter/Getter,建议把private换成package(默认)

七、避免使用Float
一般来说,在Android设备上,浮点数要比整数慢2倍。
在速度上,float和double在现代的设备上没有什么区别,在空间上,double是float的两倍。在电脑上,假设空间不是问题,你应该使用double而不是float.
同样,即使是整数,一些处理器有硬件乘法而没有硬件除法。In such cases, integer division and modulus operations are performed in software—something to think about if you're designing a hash table or doing lots of math.好吧,这句不会翻。

八、了解并使用库
这条偷懒。Google写的代码通常比你自己写的性能好,有现成的库就用现成的。System.arraycopy()在Nexus one上比手写循环快9倍。

九、小心使用Native方法
使用NDK写Native代码不一定比用Java写更高效。首先,JNI转换有消耗,并且JIT不能优化。如果你分配了Native资源(堆内存,文件或其他),回收资源更麻烦。你还需要为每个需要支持的构架编译so文件(而不是依靠JIT),你甚至不得不为相同的构架编译多个不同的版本,为G1 ARM处理器编译的Native代码,在Nexus one的Arm处理器上不能充分利用.为Nexus one的ARM处理器编译的native代码不能在G1上工作。当你想把现有的Native代码库移植到Android上是,Native代码是很有用的,但不能为了加速你的应用而把Java换成Native.

十、性能误区
在没有JIT的设备上,调用一个准确类型变量的方法比调用该类型实现接口的方法稍微高效一些(向上转型要低效一些).举例来说,声明HashMap map比声明Map map更高效,即使两个都是HashMap.大概有6%的性能损失,用了JIT,基本没区别。
在没有JIT的设备中,缓存字段访问比重复访问字段要快20%,有JIT,字段访问与局部变量访问开销一样,所以这不值得你去优化,除非你觉得这使代码可读性更高。(这条对static,final,static final同样适用)

十一、测量
在你开始优化之前,确保你有问题需要解决(没问题优化个毛)。确保你能精确的测量你已存在的性能,否则你不能衡量修改后的好处。

© 2014, 冰冻鱼. 请尊重作者劳动成果,复制转载保留本站链接! 应用开发笔记

发表评论

电子邮件地址不会被公开。 必填项已用*标注