Android性能优化之Performance Tips

如果你真的愿意去努力,你人生最坏的结果,也不过是大器晚成。

原文链接:http://developer.android.com/training/articles/perf-tips.html#UseFinal

概述

这篇文档主要包含一些微小的最佳优化,当把这些组合起来的时候,可以提高App的整体性能,但它不太可能对性能造成戏剧性的影响。选择合适的算法和数据结构应该是你优先考虑的内容,但超出了本文的范围。这个文档更适合作为通用的编码技巧,通过这些技巧使我们的代码更高效。

编写高效代码有两个基本规则:

  • 不执行不必要的操作
  • 不分配不必要的内存

我们面临的最棘手的一个问题是我们的Android应用程序肯定会在多种类型的硬件上运行。不同版本的VM虚拟机运行在不同的处理器的运行速度显然是不同的。你甚至不能简单地说“设备X快/慢于设备Y大概F倍”,然后想当然的认为其在他设备上这个倍数也是成立的。但是,我们仅仅能从模拟器获得很少关于设备性能的信息,它与真机还是有很大不同的。还有,有或没有JIT(即时编译)的设备之间存在巨大差异:在有JIT的设备上完美运行的代码在没有JIT的设备上并不一定能顺畅运行。

我们要尽量通过优化性能来确保我们的应用在各种各样的设备上都表现良好,确保代码在支持的不同版本上是有效的。

避免创建不必要的对象

对象创建永远不会是免费的。虽然一个带有线程池的垃圾回收器(garbage collector)可以使分配临时对象的内存占用降低,但分配内存总是比不分配内存更昂贵的。

当我们在我们的应用分配更多的对象时,将迫使一个周期性的垃圾回收,创建“打嗝”的用户体验,即卡顿。在Android 2.3中引入的并发垃圾收集器(GC)可以帮助我们,但不必要的工作能避免要尽量避免。

因此,这样我们应该避免创建我们不需要的对象,以下是一些实例:

  • 如果你有一个方法返回一个字符串,你知道它的结果需要附加到StringBuffer上,改变你的实现方法使它可以直接添加,而不是创建一个短暂的临时对象。
  • 当要从一组输入数据中提取字符串时,试着返回一个原始数据的子字符串(substring),而不是创建一个重复的对象。使用substring的方式,你将创建一个新的String对象,但它将与原数据共享内部char[]空间的。(如果你只使用原始输入的一小部分,无论怎么操作,你都会把它保持在内存中,这点需要权衡)

一个更激进的想法是把一个多维数组分割成平行的一维数组:

  • int类型的数组比Integer对象数组要好,由此可以知道两个平行的int数组比(int,int)二维数组效率更高。这同样适用于所有的原始数据类型。
  • 如果你需要实现一个容器存储(Foo,Bar)对象的,试着记住两个平行的Foo[]和Bar[]数组通常比单一阵列的自定义(Foo,Bar)对象要好。(但是有例外,比如当你设计一个API访问其他代码的时候。在这些情况下,通常可以牺牲小部分的性能达到良好的API设计。但在自己的内部代码,我们应该试着尽可能的提高效率。)

一般来说,避免创建临时对象。更少的对象创建意味着更少的垃圾收集,这对用户体验有直接的影响。

选择Static而不是Virtual

如果你不需要访问一个对象的字段,可以使用static修饰你的方法。调用将快15%~20%。这是一种很好的做法,因为通过static你还可以知道调用的方法不能改变对象的状态。

使用static final修饰常量

像这样在一个类的顶部声明常量:

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

编译器生成一个类初始化方法,叫做< clinit >,当这个类第一次被使用时执行。这个函数将42存入intVal,还从class文件的常量表中提取了strVal的引用。当之后使用intVal或strVal的时候,他们会直接被查询到。

我们可以用final关键字来优化:

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

这时再也不需要上面的方法了,因为final声明的常量进入了静态dex文件的域初始化部分。调用intVal的代码会直接使用42,调用strVal的代码也会使用一个相对廉价的“string constant”指令,而不是查表。

Notes:这个优化方法只对原始类型和String类型有效,而不是任意引用类型。不过,在必要时使用static final是个很好的习惯。

避免内部的Getters/Setters

像C++等native language,通常使用getters(i = getCount())而不是直接访问变量(i = mCount)。这是编写C++的一种优秀习惯,而且通常也被其他面向对象的语言所采用,例如C#与Java,因为编译器通常会做inline访问,而且你需要限制或者调试变量,你可以在任何时候在getter/setter里面添加代码。

然而,在Android上,这不是一个好的写法。虚函数的调用比起直接访问变量要耗费更多。在面向对象编程中,将getter和setting暴露给公用接口是合理的,但在类内部应该仅仅使用域直接访问。

在没有JIT(Just In Time Compiler)时,直接访问变量的速度是调用getter的3倍。有JIT时,直接访问变量的速度是通过getter访问的7倍。

请注意,如果你使用ProGuard,你可以获得同样的效果,因为ProGuard可以为你内联访问.

使用for-each循环

增强的For循环(也被称为 for-each 循环)可以被用在实现了 Iterable 接口的 collections 以及数组上。使用collection的时候,Iterator会被分配,用于for-each调用hasNext()和next()方法。使用ArrayList时,手写的计数式for循环会快3倍(不管有没有JIT),但是对于其他collection,增强的for-each循环写法会和迭代器写法的效率一样。

请比较下面三种循环的方法:

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()是差不多一样快的。它使用了增强的循环方法for-each。

所以请尽量使用for-each的方法,但是对于ArrayList,请使用方法one()。

Tips:你还可以参考 Josh Bloch 的 《Effective Java》这本书的第46条

使用package代替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);
    }
}

这里重要的是,我们定义了一个私有的内部类(Foo$Inner),它直接访问了外部类中的私有方法以及私有成员对象。这是合法的,这段代码也会如同预期一样打印出”Value is 27”。

问题是,VM(虚拟机)因为Foo和FooInner是不同的类,会认为在FooInner中直接访问Foo类的私有成员是不合法的。即使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()函数时,它都会调用这些静态方法。这意味着,上面的代码可以归结为,通过accessor函数来访问成员变量。早些时候我们说过,通过accessor会比直接访问域要慢。所以,这是一个特定语言用法造成性能降低的例子。

如果你正在性能热区(hotspot:高频率、重复执行的代码段)使用像这样的代码,你可以把内部类需要访问的域和方法声明为包级访问,而不是私有访问权限。不幸的是,这意味着在相同包中的其他类也可以直接访问这些域,所以在公开的API中你不能这样做。

避免使用浮点类型

Android系统中float类型的数据存取速度是int类型的一半,尽量优先采用int类型。

就速度而言,现代硬件上,float 和 double 的速度是一样的。空间而言,double 是两倍float的大小。在空间不是问题的情况下,你应该使用 double 。

同样,对于整型,有些处理器实现了硬件几倍的乘法,但是没有除法。这时,整型的除法和取余是在软件内部实现的,这在你使用哈希表或大量计算操作时要考虑到。

了解并使用库函数

除了那些常见的让你多使用自带库函数的理由以外,记得系统函数有时可以替代第三方库,并且还有汇编级别的优化,他们通常比带有JIT的Java编译出来的代码更高效。典型的例子是:Android API 中的 String.indexOf(),Dalvik出于内联性能考虑将其替换。同样 System.arraycopy()函数也被替换,这样的性能在Nexus One测试,比手写的for循环并使用JIT还快9倍。

Tips:参见 Josh Bloch 的 《Effective Java》这本书的第47条

小心使用底层方法

结合Android NDK使用native代码开发,并不总是比Java直接开发的效率更好的。Java转native代码是有代价的,而且JIT不能在这种情况下做优化。如果你在native代码中分配资源(比如native堆上的内存,文件描述符等等),这会对收集这些资源造成巨大的困难。你同时也需要为各种架构重新编译代码(而不是依赖JIT)。你甚至对已同样架构的设备都需要编译多个版本:为G1的ARM架构编译的版本不能完全使用Nexus One上ARM架构的优势,反之亦然。

Native 代码是在你已经有本地代码,想把它移植到Android平台时有优势,而不是为了优化已有的Android Java代码使用。

如果你要使用JNI,请学习JNI Tips

Tips:参见 Josh Bloch 的 《Effective Java》这本书的第54条

性能谬见

在没有JIT的设备上,使用一种确切的数据类型确实要比抽象的数据类型速度要更有效率(例如,调用HashMap map要比调用Map map效率更高)。有误传效率要高一倍,实际上只是6%左右。而且,在JIT之后,他们直接并没有大多差异。

在没有JIT的设备上,读取缓存域比直接读取实际数据大概快20%。有JIT时,域读取和本地读取基本无差。所以优化并不值得除非你觉得能让你的代码更易读(这对 final, static, static final 域同样适用)。

关于测量

在优化之前,你应该确定你遇到了性能问题。你应该确保你能够准确测量出现在的性能,否则你也不会知道优化是否真的有效。

本章节中所有的技巧都需要Benchmark(基准测试)的支持。Benchmark可以在 code.google.com “dalvik” project 中找到。

Benchmark是基于Java版本的 Caliper microbenchmarking框架开发的。Microbenchmarking很难做准确,所以Caliper帮你完成这部分工作,甚至还帮你测了你没想到需要测量的部分(因为,VM帮你管理了代码优化,你很难知道这部分优化有多大效果)。我们强烈推荐使用Caliper来做你的基准微测工作。

我们也可以用Traceview 来测量,但是测量的数据是没有经过JIT优化的,所以实际的效果应该是要比测量的数据稍微好些。

关于如何测量与调试,还可以参考下面两篇文章:

时间: 2024-07-29 17:13:05

Android性能优化之Performance Tips的相关文章

(一)Android性能优化系列---Performance Tips(文章出处:http://developer.android.com/training/articles/perf-tips.html#Myths)

本文列出的优化技巧主要是一些微小的性能提升,可能不会给你的程序性能改善产生显著的效果.决定程序整体性能的仍然取决于程序的业务逻辑设计.代码的数据结构和算法,这超出了本文的范围.你需要将这些优化技巧应用到平时的编码过程中,积少成多,也会对性能有很大的影响. 下面是写高效代码的两个基本原则: 1.不要写不需要的代码: 2.不要分配不必要的内存. android应用程序优化一个非常棘手的问题就是android硬件差异很大.不同的虚拟机.不同的SDK版本.app在不同的设备环境上运行速度和性能自然不同:

(二)Android性能优化系列---Improving Layout Performance(一)(转载自:http://xhmj12.iteye.com/blog/2064258)

Android性能优化系列---Improving Layout Performance(一) Layouts是Android应用里直接影响用户体验的一个关键部分.如果Layout设计的不好,可能导致你的应用大量的内存占用从而导致UI响应很慢.Android SDK提供了工具帮助你分析你的Layouts的性能问题.结合这个工具同时查看本文,你能实现滑动流畅.占用内存最小的用户界面. Use the <merge> Tag 某些时候,自定义可重用的布局包含了过多的层级标签,比如我们需要在Line

Android 性能优化探究

使用ViewStub动态加载布局,避免一些不经常的视图长期握住引用: ViewStub的一些特点: 1. ViewStub只能Inflate一次,之后ViewStub对象被置空:某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了. 2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中. 基于以上的特点,那么可以考虑使用ViewStub的情况有: 1. 在程序的运行期间,某个布局在Inf

Android性能优化典范(二)

原文出处: 胡凯的博客(@胡凯me)   欢迎分享原创到伯乐头条 Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义View的性能,提升设置alpha之后View的渲染性能,以及Lint,StictMode等等工具的使用技巧. 下面是对这些课程的总结摘要,认知有限,理解偏差的地方请多多指教! 1)Battery

Android性能优化总结(转)

前言 性能优化本身是一个很大的主题,涵盖程序的方方面面,任何不慎的操作,都有可能对性能造成比较大的影响,要知道程序的性能是可以累加的,多处的性能低下, 会影响整体的性能,其后果可能也是多方面的,本文总结了目前工作中,所需要知道的大部分性能优化点,一部分个人总结,一部分来自于互联网.但整体上,都是 提纲性的,并没有列出具体的实例,因为写这方面主题的达人实在太多了,所以,我得站在巨人的肩膀上,具体细节,请参考对应的链接. 性能低下的现象 游戏:界面很卡,FPS低 搜索性能差 服务器响应速度慢 OS:

android app性能优化大汇总(google官方Android性能优化典范 - 第2季)

Google前几天刚发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义View的性能,提升设置alpha之后View的渲染性能,以及Lint,StictMode等等工具的使用技巧. (1)Battery Drain and Networking 对于手机程序,网络操作相对来说是比较耗电的行为.优化网络操作能够显著节约电量的消耗.在性

Android性能优化典范 - 第2季

Google发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩,自定义View的性能,提升设置alpha之后View的渲染性能,以及Lint,StictMode等等工具的使用技巧. 下面是对这些课程的总结摘要,认知有限,理解偏差的地方请多多指教! 1)Battery Drain and Networking 对于手机程序,网络操作相对来说是

【Android开发】Android性能优化

Android性能优化 根据Android的层次结构,性能优化也是分层次进行的,本文会分别对Application.Framework.Native.Kernel各层做总结,每层主要会从性能优化的基本思想.优化技巧.优化工具几个方面进行说明. 第一章Android应用性能优化(概述) 应用程序的性能问题是最明显.最容易体现的一类,表现形式也五花八门,举几个例子: 应用程序第一次启动速度慢,或者进入某一界面速度慢: 启动某一有动画效果的界面,动画执行过程不流畅,或者动画执行前卡顿时间长: List

Google 发布 Android 性能优化典范

2015年伊始,Google发布了关于Android性能优化典范的专题, 一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同时也介绍了如何通过工具来找出性能问题以及提升性能的建议.主要从三个 方面展开,Android的渲染机制,内存与GC,电量优化.下面是对这些问题和建议的总结梳理. 0)Render Performance 大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能.从设计