[译]提升Android应用性能的小贴士

本文译自Android开发者网站,主要介绍了提升Android应用性能表现的几个建议。阅读本文时还要务必记得“过早优化是万恶之源”,优化起码应该放在实现了应用的MVP版本之后。
原文地址:https://developer.android.com/training/articles/perf-tips.html


本文主要介绍了提升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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • 关于android性能,内存优化 看了些资料整理了下,安卓的性能和内存优化的一些方法和注意事项。分享出来。 随着技...
    ifeng_max阅读 1,020评论 0 14
  • 有些恶习 如同这枯萎的头发 越是舍不得剪断 枯萎腐败的分支就越多 使人看不到丝毫的美好 不如拿起剪刀 狠心勇敢的剪...
    Katrina_小月月阅读 119评论 6 3
  • 美年体检,九阳豆浆机加盟
    东莞colin阅读 130评论 0 0