Android官方开发文档Training系列课程中文版:性能优化建议

简介: 原文地址:http://android.xsoftlab.net/training/articles/perf-tips.html本篇文章主要介绍那些可以提升整体性能的微小优化点。

原文地址:http://android.xsoftlab.net/training/articles/perf-tips.html

本篇文章主要介绍那些可以提升整体性能的微小优化点。它与那些能突然改观性能效果的优化手段并不属于同一类。选择正确的算法与数据结构必然是我们的第一总则,但是这不是我们这篇文章要介绍的。你应该将这篇文章所提及的知识点作为编码的日常习惯,这可以提升常规代码的执行效率。

下面是书写代码的基本准则:

  • 绝不要做你不需要的工作。
  • 如果可以不申请内存就不要申请,要合理复用已有的对象。

另一个较复杂的问题就是被优化过的APP肯定是要运行在各种类型的硬件平台上。不同版本的虚拟机运行在不同的处理器上肯定会有不同的运行速度。需要特别说明的是,在模拟器上测试很少会得知其它设备的性能。在不同设备上还有一个很大的不同点就是有没有JIT(JIT的意思是即时编译器):在JIT设备上运行的最优代码并不总在没有JIT设备上有效。

为了确保APP可以在各类设备上运行良好,要确保代码在各个版本的平台上都是高效的。

避免创建不必要的对象

创建对象绝不是没有成本的。虽然分代垃圾收集器可以使临时对象的分配成本变得很低,但是内存分配的成本总是远高于非内存分配的成本。

随着更多对象的生成,你可能就开始关注垃圾收集器了。虽然Android 2.3中出现的并发收集器可能会帮到你,但是不必要的工作总是应该避免的。

因此,要避免创建不需要的对象。下面的示例可能会帮到你:

  • 如果你有个返回字符串的方法,该方法所返回的字符串总是被接在一个StringBuffer对象后面。那么就可以更改此方法的实现方式:让该字符串直接跟在StringBuffer的后面返回。这样就可以避免创建那些临时性的变量。
  • 当从字符串中提取子串时,应该尝试返回原始数据的子串,而不是创建一个副本。子串将会创建一个新的String对象,但是它与char[]共用的是同一数据。采用这种方式的唯一不足就是:虽然使用了其中的一部分数据,但是剩余的数据还都保留在内存中。

一条更为先进的法则就是,将多维数组转换为平行数组使用:

  • int数组的效率要比Integer数组的效率高的多。
  • 如果你需要实现一个用于存储(Foo,Bar)对象的数组,要记得使用两个平行的Foo[],Bar[]数组,这要比单一的(Foo,Bar)数组效率好太多。

通常来说,要尽量避免创建那些生命周期很短的临时变量。更少的对象创建意味着更低频率的垃圾回收,这会直接反应到用户体验上。

首选静态

如果不需要访问对象的属性,那么就可以将方法设置为静态方法。这样调用将会增加15%-20%的速度。这还是一个好的习惯,因为这样可以告诉其它方法一个信号:它们更改不了对象的状态。

使用常量

请先考虑以下声明:

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文件中。代码可以直接引用intVal为42的值,并且访问strVal也会直接得到字符串”string constant” ,这样可以省去了查找字符串的过程。

Note: 这样优化手段仅仅适用于基本数据类型以及字符串常量,不要作用其它类型。

避免内部的get\set方法

像C++这种本地语言通常都会使用get方法来访问属性。这对C++来说是一个非常好的习惯,并且C#、Java等面向对象语言也广泛使用这种方式,因为编译器通常会进行内联访问,并且如果你需要限制访问或者调试属性的话,只需要添加代码就可以。

不过,这在Android上并不是个好习惯。方法调用的开销是非常大的。虽然为了遵循面向对象语言提供get、set方法是合理的,但是在Android中最好是可以直接访问对象的字段。

在没有JIT的设备中,直接访问对象字段的速度要比通过get方法访问的速度快3倍。在含有JIT的设备中,这个效率会达到7倍之多。

注意:如果你使用了ProGuard,那么就有了一个两全其美的结果,因为ProGuard会直接为你进行内联访问。

使用增强for循环

增强for循环可用于实现了Iterable接口的集合或数组。在集合内部,迭代器需要实现接口方法:hasNext()以及next()。

有以下几种访问数组的方式:

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()方法是最快的。它使用了增强for循环。

所以应当在默认情况下使用增强for循环。

Tip: 也可以查看Josh Bloch 的 Effective Java,第46条。

考虑使用包内访问

请先思考以下类定义:

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会认为Foo$Inner直接访问Foo对象的私有成员是非法的,因为Foo和Foo$Inner是两个不同的类,虽然Java语言允许内部类可以直接访问外部类的私有成员(PS:虚拟机与语言是两种互不干扰的存在)。为了弥补这种差异,编译器专门为此生成了一组方法:

/*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()方法时会调用上面这些静态方法。上面的代码归结为你所访问的成员属性都是通过访问器方法访问的。早期我们说通过访问器访问要比直接访问慢很多,所以这是一段特定语言形成的隐性性能开销示例。

避免使用浮点型

一般来说,在Android设备上浮点型要比整型慢大概2倍的速度。

在速度方面,float与double并没有什么区别。在空间方面,double是float的两倍大。所以在桌面级设备上,假设空间不是问题,那么我们应当首选double,而不是float。

还有,在对待整型方面,某些处理器擅长乘法,不擅长除法。在这种情况下,整型的除法与取模运算都是在软件中进行的,如果你正在设计一个哈希表或者做其它大量的数学运算的话,这些东西应该考虑到。

使用本地方法要当心

使用本地代码开发的APP并不一定比Java语言编写的APP高效多少。首先,它会花费在Java-本地代码的转换过程中,并且JIT也不能优化到这些边界。如果你正在申请本地资源,那么对于这些资源的收集能明显的感觉到困难。除此之外,你还需要对每一种CPU架构进行单独编译。你可能甚至还需要为同一个CPU架构编译多个不同的版本:为G1的ARM处理器编译的代码不能运行在Nexus One的ARM处理上,为Nexus One的ARM处理器编译的代码也同样不能运行在G1的ARM处理器上。

本地代码在这种情况下适宜采用:当你有一个已经存在的本地代码库,你希望将它移植到Android上时,不要为了改善Java语言所编写的代码速度而去使用本地代码。

如果你需要使用本地代码,那么应该读一读JNI Tips.

Tip: 相关信息也可以查看Josh Bloch 的 Effective Java,第54条。

性能误区

在没有JIT的设备中,通过具体类型的变量调用方法要比抽象接口的调用要高效,这是事实。举个例子,通过HashMap map调用方法要比Map map调用方法要高效的多,开销也少,虽然这两个实现都是HashMap。事实上速度并不会慢2倍这么多;真实的不同大概有6%的减缓。进一步讲,JIT会使两者的差别进一步缩小。

在没有JIT的设备上,通过缓存访问属性要比反复访问属性要快将近20%的速度。在JIT的设备中,属性访问的花销与本地访问的花销基本一致,所以这不是一项有多少价值的优化手段,除非你觉得这样做的话代码更易读(这对static,final,常量同样适用)。

经常估测

在开始优化之前,要确保你有个问题需要解决:要确保你可以精准测量现有的性能,否则将不能观察到优化所带来的提升。

基准点由Caliper的微型基准点框架创建。基准点很难正确获得,所以Caliper将这份很难处理的工作做了,甚至是在你没有在测量那些你想测量的地方的时候它也在工作。我们强烈的推荐你使用Caliper来创建自己的微型基准点。

你可能还发现Traceview非常有助于提升性能,不过你应该意识到Traceview工作的时候JIT并没有开启。这会错误的认为JIT会将损失掉的时间弥补回来。这尤其重要:根据Traceview所更改的结果会使实际代码运行的更快。

有关更多提升APP性能的工具及方法,请参见以下文档:

目录
相关文章
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
234 10
|
传感器 安全 Android开发
探索iOS与安卓应用开发的性能差异
在移动操作系统领域,iOS和安卓的较量从未停歇。本文将深入探讨两大平台在应用开发中的性能表现,揭示它们各自的优势与局限。通过对比分析,我们将理解开发者如何在这两个不同的生态系统中做出权衡,以及这些选择如何影响最终用户的体验。
293 27
|
算法 JavaScript Android开发
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
492 31
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
1286 9
|
安全 Android开发 数据安全/隐私保护
安卓与iOS的对决:移动操作系统的性能与创新
在当今智能手机市场,安卓和iOS两大操作系统一直处于竞争状态。本文将深入探讨它们在性能、安全性和用户体验方面的不同,并分析这些差异如何影响用户的选择。
531 3
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
534 4
|
数据库 Android开发 开发者
打造高效安卓应用:从代码优化到性能提升
【8月更文挑战第2天】在移动设备的海洋中,安卓应用的效能直接关系到用户体验的好坏。本文旨在深入探讨如何通过代码层面的优化技巧和策略来提升安卓应用的性能。我们将一起探索减少内存消耗、避免不必要的CPU使用以及提高应用响应速度的方法。文章将结合具体的代码示例,为开发者提供可行的优化建议,帮助他们构建更流畅、更高效的安卓应用。
289 2
|
安全 搜索推荐 Android开发
探索安卓12新特性:用户体验与性能的全面提升
随着安卓12的发布,开发者和用户迎来了一场移动操作系统的重大更新。本文将深入探讨安卓12带来的创新特性,这些特性如何提升用户体验,并分析它们对设备性能的影响。我们将从界面设计、隐私安全、以及性能优化等方面进行详细解读,帮助读者全面了解安卓12的新面貌。

热门文章

最新文章