天天看点

Android 性能优化小技巧Android 性能优化小技巧

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 ,确认代码的性能有所改善。