[译] 提升 Android 应用性能的几个建议

   2016-12-01 0
核心提示:本文译自Android开发者网站,主要介绍了提升Android应用性能表现的几个建议。阅读本文时还要务必记得“过早优化是万恶之源”,优化起码应该放在实现了应用的MVP版本之后。本文主要介绍了提升Android应用性能的一些小方法,组合使用这些方法往往能够改善我们所

本文译自Android开发者网站,主要介绍了提升Android应用性能表现的几个建议。阅读本文时还要务必记得“过早优化是万恶之源”,优化起码应该放在实现了应用的MVP版本之后。

本文主要介绍了提升Android应用性能的一些小方法,组合使用这些方法往往能够改善我们所开发的应用的性能表现。但是,我们应优先关注应用所选用的数据结构和算法,切勿本末倒置。

想写出高效代码,有两个基本原则:

  • 不要做本不必做的工作;

  • 不要分配不必要的内存。

以上两个基本原则很好理解,第一条是让我们尽可能降低应用的时间复杂度,第二条是让我们尽可能降低应用的空间复杂度。下文的各个建议实际上也是围绕着这两个基本原则展开的。

当我们对Android应用做优化时,我们必须面对的一个最蛋疼的问题之一,便是我们的应用将会在各种类型的设备上运行,这意味着它们往往有着不同的硬件架构。不同版本的Android虚拟机在各种硬件架构上会以不同的速率运行。而我们大多数情况下无法简单地断定,X设备的速度会是Y设备的多少倍。我们在模拟器上的测试结果,对我们做真机上的性能评估又往往没什么帮助。

更令人蛋疼的是,支持即时编译(JIT)的设备和不支持即时编译的设备又有着巨大的差异——支持即时编译的设备上的最优代码,对于不支持即时编译的设备来说,不总是最优的。

要确保你的应用在各种各样的设备上都有着良好的性能表现,要确保你的代码在各种情况下都是高效的,这需要我们下一番功夫来对代码进行优化。以下是一些性能优化的建议。

避免创建不必要的对象

创建对象总是会产生代价的。一个支持线程级分配池的分代垃圾回收器可以使得一次内存分配所花的代价更少,但是再少也少不过“根本不进行内存分配”。

当你的应用中创建了足够多的对象,便会导致垃圾回收经常发生,这可能会带来用户界面的“卡顿”。因此,你应该避免创建不必要的对象,比如:若你有一个返回一个String的方法,并且你确信返回结果总是会被添加到一个StringBuffer(StringBuilder)中,那么应对该方法做出修改,让它不再返回String,而是直接把结果添加到相应的StringBuffer(StringBuilder)中,这样一来便可以避免创建一个临时的String对象。

还有一个更“激进”的建议是把多维数组都“展开”成一维数组:

  • 多个int数组要比一个int[]对象数组更加高效,这对于其他原始数据类型(primitive data type)同样适用;

  • 若你需要一个存储(Foo, Bar)元组的容器,要记得使用Foo数组和Bar数组要比使用(Foo, Bar)对象数组高效的多(例外情况是当你设计API时,为了一个良好的API设计,我们应该在性能上做出小小的妥协);

对于本条建议,概括起来就是 尽可能避免创建短时存活的对象

尽量使用static而不是virtual

若你不需要访问对象的字段,那么请定义你的方法为静态(static)方法而不是虚(virtual)方法,这会带来15%到20%的性能提升。这同样也是一个好的编程实践,因为如此一来我们可以清楚的知道,调用该方法不会改变对象的状态。

对于常量使用static final

考虑下面的声明:

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

当声明了以上语句的类被初次使用时,编译器会为之创建一个名为“<clinit>“的类初始化器方法。这个方法会将42存储在intVal中,并将字符串“Hello, world!”的引用存储在strVal中,稍后我们引用到这两个变量时,便会通过“字段查找(field lookup)”来访问它们。

然而通过为以上两句变量声明加上“final”关键字,那么intVal和strVal就会变为两个常量,访问它们时便无需通过字段查找,这样会提升性能。

注意:这个优化建议只对String和原始数据类型有效。

使用增强版循环语法

增强版循环语法指的就是for-each写法,它可以用于数组以及实现了Iterable接口的集合类。for-each只有对ArrayList使用时,要比常规的for循环慢,对于其他情况,for-each与常规for速度相差无几。由于for-each能够简化代码编写,我们优先考虑使用它;只有我们使用ArrayList并且追求“极致性能”时,才应使用常规for循环。

对以下场景考虑使用包(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中访问了Foo类的私有方法和私有字段,尽管这在Java语法中是合法的。但是对虚拟机来说,会将Foo$Inner和Foo视为两个不同的类,所以虚拟机为了实现内部类对外围类私有方法/字段的访问,需要创建两个充当“沟通纽带”的方法,如下:

static int Foo.access$100(Foo foo) {    
  return foo.mValue;
}
static void Foo.access$200(Foo foo, int value) {    
  foo.doStuff(value);
}

也就是说,内部类Inner要通过上面两个方法来访问外围类的私有字段/方法,这显然比直接字段访问的开销要大。这种情况下,我们可以考虑将mValue和doStuff()的可见性改为默认的包范围。当然,我们不应该对公共API应用这一点。

避免使用浮点型

对于Android设备,使用浮点数要比整数大概慢两倍;而双精度浮点和单精度浮点在时间效率上相差无几,只是前者会占用二倍于后者的存储空间。

还应该注意的是,有些Android设备在硬件层面上不支持除法。我们在开发中也要注意这一点。

学习并使用系统API

有时候对于某种业务逻辑,与自己实现相比,我们更应该优先使用SDK提供给我们的方法,因为系统提供给我们的实现往往更加高效。一个典型的例子是System.arrayCopy()方法要比我们手动用循环进行数组复制快9倍左右(在支持即时编译的Nexus One设备上)。

可能无需进行的优化

我们先来考虑以下两个方法:

void doWork(Map map);
void doWork(HashMap map);

在一个不支持即时编译的设备上,第一个方法只比第二个方法慢一点(6%);而支持即时编译的设备上,二者效率的差异就更小了。

我们再来考虑一下”重复访问字段”和”把字段缓存为局部变量”所带来的性能差异。在不支持即时编译的设备上,缓存要比重复访问快20%,而对于支持即时编译的设备,两者几乎一样快。

因此对于以上两种情况,无需我们费心进行“优化”。

记得做性能测试

在你着手进行优化之前,确保你已经发现了性能问题,毕竟“过早优化是万恶之源”。此外,还要确保你已经精确测试过应用现阶段的性能表现,否则我们难以衡量优化工作的成效。我们可以使用SysTrace和TraceView来量化我们应用的性能表现。

长按或扫描二维码关注我们,让您利用每天等地铁的时间就能学会怎样写出优质app。

 [译] 提升 Android 应用性能的几个建议

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Android插件化(三):OpenAtlas的插件重建以及使用时安装
    Android插件化(三):OpenAtlas的插件重建以及使
    在上一篇博客 Android插件化(二):OpenAtlas插件安装过程分析 中深入分析了OpenAtlas的随宿主启动的插件安装过程,本文将对插件的重建和使用时插件的安装过程进行分析,其中使用时安装这是我自己的定义,它类似懒加载机制,比如在需要用到插件中的某个组件时,
  • 深入理解Android虚拟机体系结构
    深入理解Android虚拟机体系结构
    来源:LeoLiang连接:http://www.cnblogs.com/lao-liang/p/5111399.html1.什么是Dalvik虚拟机Dalvik虚拟机是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式
  • Android虚拟机调试器原理与实现
    Android虚拟机调试器原理与实现
    * 本文原创作者:渔村安全,本文属FreeBuf原创奖励计划,未经许可禁止转载 本文主要讲解Android虚拟机动态调试背后涉及到的技术原理,除了JDWP协议细节,还包括任意位置断点、堆栈输出、变量值获取等基础调试功能的具体实现。另外本文提供了一款新的android动
点击排行