原文地址: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官方开发文档Training系列课程中文版:性能优化建议的更多相关文章

  1. Android官方开发文档Training系列课程中文版:目录

    Android官方开发文档Training系列课程中文版:目录   引言 在翻译了一篇安卓的官方文档之后,我觉得应该做一件事情,就是把安卓的整篇训练课程全部翻译成英文,供国内的开发者使用,尤其是入门开 ...

  2. Android官方开发文档下载

    Android官方开发文档 docs-24_r02.rar(链接:https://pan.baidu.com/s/12xC998JeUHj3ndfDXPM2ww 密码:bxyk) ADT下载.Andr ...

  3. 在线API,桌面版,jquery,css,Android中文开发文档,JScript,SQL掌用实例

    学习帮助文档大全 jquery,css,Android中文开发文档,JScript,SQL掌用实例 http://api.jq-school.com/

  4. Android基础开发文档汇总

    一.Android 基本组件 1. Android中PackageManager使用示例 :  http://blog.csdn.net/qinjuning/article/details/68678 ...

  5. [翻译]开发文档:android Bitmap的高效使用

    内容概述 本文内容来自开发文档"Traning > Displaying Bitmaps Efficiently",包括大尺寸Bitmap的高效加载,图片的异步加载和数据缓存 ...

  6. 微信小程序 开发文档

    官方开发文档: 小程序公众平台 小程序开发者指南 小程序开发者文档 学习资源: 微信:官方入门教程 微信:WeUI 是一套同微信原生视觉体验一致的基础样式库 微信:微信小程序示例 视频: 学堂在线:学 ...

  7. Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)

    在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现..   例子相关博文:Androi ...

  8. Android 滑动界面实现---Scroller类别 从源代码和开发文档了解(让你的移动布局)

    在android学习,行动互动是软件的重要组成部分,其中Scroller是提供了拖动效果的类,在网上.比方说一些Launcher实现滑屏都能够通过这个类去实现.. 样例相关博文:Android 仿 窗 ...

  9. Android App签名打包 与 SDK开发文档

    Android App签名打包签名的意义1.为了保证每个程序开发者的合法权益2.放置部分人通过使用相同的Package Name来混淆替换已经安装的程序,从而出现一些恶意篡改3.保证我们每次发布的版本 ...

随机推荐

  1. python 全栈开发,Day8(文件操作)

    一.文件操作流程 文件以什么编码存储的,就以什么编码打开 参数: 1.文件路径 2.编码方式,encode 3.执行动作(打开方式):只读,只写,追加,读写,写读... 打开一个已经存在的文件 f = ...

  2. SqlServr分页存储过程的写法

    CREATE PROCEDURE [dbo].[GetDataByPager] ( --从第几条数据取 @startIndex INT, --分页的表 @tableName VARCHAR(50), ...

  3. 高版本js实现live

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. python多进程和多线程

    多任务才有多进程和线程: 线程是最小的执行单元,而进程由至少一个线程组成.如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间. 多进程和多线程的程序涉及到同步.数据共享 ...

  5. python全栈开发day19-面向对象初识

    1.昨日内容回顾 模块:            1.什么是模块,什么是包 py文件就是模块,包是包含一系列py文件(__init__.py)的文件夹. 2.模块的导入相当于相当于执行了导入的模块,首次 ...

  6. Immediate Decodability HDU1305

    类似phonelist  一次ac 判断失败主要有两个要点 1. 是否包含了某段的结尾结点   说明某段被此段包含 2.此段的结尾结点是否为某段的痕迹   说明此段被包含 #include<bi ...

  7. 8. 博客系统| 富文本编辑框和基于bs4模块防御xss攻击

    views.py @login_required def cn_backend(request): article_list = models.Article.objects.filter(user= ...

  8. Unity3D 之 console面板的停靠

    是否苦于不知如何停靠console, 是否后悔将它拉出来, 是否在纠结为何没办法拉回去. 将console或其他面板停靠的方法有两种: 1.Window -> Layouts -> Def ...

  9. hdu 1251:统计难题[【trie树】||【map】

    <题目链接> 统计难题                        Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131 ...

  10. codeforces-1111

    https://www.cnblogs.com/31415926535x/p/10397007.html codeforces 537 div2 A 题意就是给你两个字符串,然后如果s,t的对应位上的 ...