本文翻译自Android官方文档

文章出处:http://developer.android.com/training/articles/perf-tips.html#Myths

本文列出的优化技巧主要是一些微小的性能提升,可能不会给你的程序性能改善产生显著的效果。因为程序的整体性能仍然主要取决于程序的业务逻辑设计、代码的数据结构和算法。但下面讨论的微小的性能提升,积少成多,也会对性能有很大的影响,你需要将这些优化技巧应用到平时的编码过程中。

下面是写高效代码的两个基本原则:

  • 不要写不需要的代码

  • 不要分配不必要的内存

Android应用程序优化一个非常棘手的问题就是Android硬件差异很大。不同的虚拟机、不同的SDK版本,App在不同的设备环境上运行速度和性能自然不同;在有无JIT的设备上也各不同相同。因此,为了确保你的App性能在最大范围上的设备都比较高,你应该最大化的优化你的代码逻辑和性能。

Avoid Creating Unnecessary Objects

创建对象总是有代价的。每个线程的分代GC给临时对象分配一个地址池以降低分配开销,但往往内存分配比不分配的代价大。如果你的app创建的对象越来越多,分配的内存也越来越多,就会强制一个周期性的垃圾回收,这会给用户体验产生小小的停顿间隙,让用户感觉到卡顿。GingerBread(Android 2.3)中提到的并发回收也许有用,但不必要的工作应当是被避免的。

那么,你应该避免产生并不需要的对象实例。下面是一些例子:

  • 如果有一个返回值为String的方法,并且它的返回值是附加在一个StringBuffer上,改变声明和实现,让函数直接在其后面附加,而非创建一个短暂存在的临时变量。

  • 当从输入的数据集合中读取数据时,考虑返回原始数据的子串,而非新建一个拷贝。这样你虽然创建一个新的对象,但是它们共享该数据的char数组(结果是即使仅仅使用原始输入的一部分,你也需要保证它的整体一直存在于内存中)。

一个更彻底的方案是将多维数组切割成平行的一位数组:

  • int的数组比Integer对象数组要好的多。推而广之,两个并行的int数组要不一个(int,int)型的二维对象数组高效。这对于其他任何基本数据类型的组合都通用。

  • 如果需要实现一个容器来存放元组(Foo,Bar),两个并行一维数组Foo[]、Bar[]会优于一个(Foo,Bar)对象的二维数组(例外情况是:当你设计API给其他代码调用时,应使用好的API设计来换取小的速度提升。但在自己的内部代码中,尽量尝试高效的实现)。

通常来讲,尽量避免创建短时临时的对象,少的对象创建意味着低频的垃圾回收。而这对用户体验会产生直接的影响。

用静态代替虚拟

如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。

Use Static Final For Constants

看看下面类首的声明:

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!"

类不再需要方法,因为常量通过静态字段初始化器进入Dex文件中。引用intVal的代码,将直接调用整型值42;而访问strVal,也会采用相对开销较小的“字符串常量”指令替代字段查找。

注:这种优化仅仅是针对基本数据类型和String类型常量的,而非任意的引用类型。但尽可能的将常量声明为static final是一种好的做法

Avoid Internal Getters/Setters

在原生语言像C++中,通常做法是用Getters(i=getCount())代替直接字段访问(i=mCount)。这是C++中一个好的习惯,因为编译器会内联这些访问,并且如果需要约束或者调试这些域的访问,你可以在任何时候添加代码。

而在Android中,这不是一个好的做法。虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在类中一个字段经常被访问的话宜采用直接访问

Use Enhanced For Loop Syntax

改进for循环(有时被称为“for-each”循环)能够用于实现了iterable接口的集合类和数组。在集合类中,迭代器接口将调用hasNext()和next()方法。

在ArrayList中,手写的计数循环迭代要快3倍(无论有没有JIT),但其他集合类中,增强for循环语法和迭代器具有相同的效率。

这里有一些迭代数组的实现:

    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()不分上下。它采用了JDK1.5中的改进for循环语法。

结论:优先采用增强for循环,但在性能要求苛刻的ArrayList迭代中,考虑采用手写计数循环(参见Effectivie Java item 46)。

Consider Package Instead of Private Access with Private Inner Classes

考虑下面的类定义:

public class Foo {private class Inner {void stuff() {Foo.this.doStuff(Foo.this.mValue);//内部类持有外部类的应用:this}}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”。

但问题是,虚拟机不建议这么做,认为从Foo$Inner中直接访问Foo的私有成员是不可取的,因为它们是两个不同的类,尽管Java语言允许内部类访问外部类的私有成员,但是虚拟机通过编译器优化成生成几个综合方法来桥接这些间隙的。

内部类会在外部类中任何需要访问mValue字段或调用doStuff方法的地方调用这些静态方法。这意味着这些代码将直接存取成员变量表现为通过存取器方法访问。之前提到过存取器访问如何比直接访问慢,这例子说明,某些语言约会定导致不可见的性能问题。

如果你在高性能的Hotspot中使用这些代码,可以通过声明被内部类访问的字段和成员为包访问权限,而非私有。但这也意味着这些字段会被其他处于同一个包中的类访问,因此在公共API中不宜采用。

Avoid Using Floating-Point

通常的经验是,在Android设备中,浮点数会比整型慢两倍。

从速度方面说,在现代硬件上,float和double之间没有任何不同。更广泛的讲,double大2倍。在台式机上,由于不存在空间问题,double的优先级高于float。

但即使是整型,有的芯片拥有硬件乘法,却缺少除法。这种情况下,整型除法和求模运算是通过软件实现的,就像当你设计Hash表,或是做大量的算术那样。

Know and Use the Libraries

选择Library中的代码而非自己重写,除了通常的那些原因外,考虑到系统空闲时会用汇编代码调用来替代library方法,这可能比JIT中生成的等价的最好的Java代码还要好。典型的例子就是String.indexOf,Dalvik用内部内联来替代。同样的,System.arraycopy方法在有JIT的Nexus One上,自行编码的循环快9倍。(参见 Effective Java item 47)

那即是Java标准库和Android Framework中包含了大量高效且健壮的库函数,很多函数还采用了native实现,通常情况下比我们用Java实现同样功能的代码的效率要高很多。所以善于使用系统库函数可以节省开发时间,并且也不容易出错

Use Native Methods Carefully

native方法并不是一定比Java高效。最起码,Java和native之间的关联是有消耗的,而JIT并不能对此进行优化。当你分配本地资源时(native堆上的内存,文件说明符等),往往很难实时的回收这些资源。同时你也需要在各种结构中编译你的代码(而非依赖JIT)。

甚至可能需要针对相同的架构来编译出不同的版本:针对ARM处理器的GI编译的native代码,并不能充分利用Nexus One上的ARM,而针对Nexus One上ARM编译的本地代码不能在G1的ARM上运行。

当你想部署程序到存在native代码库的Android平台上时,本地代码才显得尤为有用,而并非为了Java应用程序的提速。(参见Effective Java item 54)

Performance Myths

在没有JIT的设备上,调用方法所传递的对象采用具体的类型而非接口类型会更高效(比如,传递HashMapmap比Map map调用一个方法的开销小,尽管两个map都是HashMap)。但这并不是两倍慢的情形,事实上,他们只相差6%,而有JIT时这两种调用的效率不相上下。

在没有JIT的设备上,缓存后的字段访问比直接访问快大概20%。而在有JIT的情况下,字段访问的代价等同于局部访问,因此这里不值得优化,除非你觉得他会让你的代码更易读(对于final ,static及static final 变量同样适用)

结束语

最后:通常考虑的是:先确定存在问题,再进行优化。并且你知道当前系统的性能,否则无法衡量你进行尝试所得到的提升。

这份文档中的每个主张都有标准的基准测试作为支持。你可以在code.google.com “dalvik”项目中找到基准测试的代码。

这个标准的基准测试是建立在Caliper Java标准微基准测试框架之上的。标准微基准测试很难找到正确的路,所以Caliper帮你完成了其中的困难部分的工作。并且当你会察觉到某些情况的测试结果并想象中的那样(虚拟机总是在优化你的代码的)。我们强烈推荐你用Caliper来运行你自己的标准微基准测试。

同时你也会发现Traceview对分析很有用。但它目前是不支持JIT的,这可能导致那些在JIT上可以胜出的代码运行超时。特别重要的是,根据Taceview的数据作出更改后,请确保代码在没有Traceview时,确实跑的快了。

看完本文有收获?请转发分享给更多人


欢迎关注“互联网架构师”,我们分享最有价值的互联网技术干货文章,助力您成为有思想的全栈架构师,我们只聊互联网、只聊架构,不聊其他!打造最有价值的架构师圈子和社区。

本公众号覆盖中国主要首席架构师、高级架构师、CTO、技术总监、技术负责人等人 群。分享最有价值的架构思想和内容。打造中国互联网圈最有价值的架构师圈子。

  • 长按下方的二维码可以快速关注我们

  • 如想加群讨论学习,请点击右下角的“加群学习”菜单入群

Android性能优化系列——Performance Tips相关推荐

  1. Android性能优化系列:启动优化

    文章目录 1 应用启动类型 1.1 冷启动 1.2 温启动 1.3 热启动 2 查看启动耗时 2.1 adb命令查看 2.2 Logcat Displayed查看启动耗时 2.3 手动记录启动耗时 2 ...

  2. Android性能优化系列之apk瘦身

    Android性能优化系列之布局优化 Android性能优化系列之内存优化 为什么APK要瘦身.APK越大,在下载安装过程中,他们耗费的流量会越多,安装等待时间也会越长:对于产品本身,意味着下载转化率 ...

  3. Android性能优化系列:CPU收敛优化(线程优化)

    文章目录 线程调度 线程调度的原理 线程调度模型 Android 的线程调度 线程调度小结 Android 异步方式汇总 Thread HandlerThread IntentService Asyn ...

  4. Android性能优化系列之内存优化

    在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,上篇博客,我介 ...

  5. Android性能优化系列 + Android官方培训课程中文版

    Android性能优化典范 - 第6季 http://hukai.me/android-performance-patterns-season-6/ Android性能优化典范 - 第5季 http: ...

  6. 抖音 Android 性能优化系列:新一代全能型性能分析工具 Rhea

    本文选自「抖音 Android 性能优化」系列文章. 「抖音 Android 性能优化」系列文章是由抖音 Android 基础技术部门技术专家倾力打造的技术干货内容,和大家分享基础技术团队在打造极致用 ...

  7. Android性能优化系列之电量优化

    电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情,随着Android开的性能要求越来越高,电量的优化,也显得格外重要,一个耗电的应用,用户肯定会毫不犹豫的进行卸载,所 ...

  8. Android 性能优化系列之 —— 存储优化(3)

    序 昨晚公司进行团建 ,突突了两瓶汾酒结果都嗨了 ,亲眼见证程序员其实也是多才多艺的 ,有几个小伙伴简直就是被代码耽误的歌手 . PS :今天在家使用室友的 MacBookPro(TouchBar)的 ...

  9. Android性能优化系列总篇

    目前性能优化专题已完成以下部分: 性能优化总纲--性能问题及性能调优方式 性能优化第四篇--移动网络优化 性能优化第三篇--Java(Android)代码优化 性能优化第二篇--布局优化 性能优化第一 ...

  10. Android性能优化系列之Bitmap图片优化

    在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将 ...

最新文章

  1. linux 如何下载svn插件安装,Linux SVN服务端安装和eclipse svn插件配置
  2. JQuery根据值设置radio选中
  3. python基础之异常处理、面向对象
  4. stata 线性回归分析基本操作
  5. NodeJS下安装Express的小问题
  6. [STC单片机] STC32G单片机双CAN通信测试
  7. 物联网空气质量监测系统
  8. 基于JAVA的GUI编程的的迷宫游戏 2020-12-15
  9. 文件夹批量重命名编号的方法
  10. lisp实心圆点怎么画_cad中怎么样画实心圆点
  11. Sketch 68下载 最好的产品设计工具
  12. android多行文本输入,android EditText多行文本输入的若干问题
  13. 管理and电子商务相关资源
  14. 和平精英服务器维护多少钱,和平精英因充钱太多服务器崩溃?王小歪充52W,只用一小时...
  15. PAT : 基础编程题目集_函数题答案(6-1 ~ 6-13)(C语言)
  16. java改变数据库配置文件信息_JAVA应用修改数据库链接信息一般在哪个配置文件中?...
  17. datastage dsjob命令
  18. 2022谷歌浏览器插件FOFA Pro View失效更新方法
  19. C语言猜数字游戏(详解)
  20. 第二章例2.1软件实现(spss,sas,stata,r)

热门文章

  1. Centos 7忘记密码,如何重置
  2. 《MPLS在Cisco IOS上的配置》一2.3 配置命令参考
  3. VBA读取word中的内容到Excel中
  4. 第二十二章 职业道德规范
  5. hdu 4311 Meeting point-1 递推 多校联合赛(二) 第二题
  6. poj 2502 Subway dijkstra基础 !!!!入门题
  7. OCRKit Pro for mac (OCR文字识别工具)
  8. Idea 中的快捷键(mac)
  9. 老司机 iOS 周报 #42 | 2018-11-05
  10. ORACLE数据恢复方法(提交事务也可以)