Android最佳性能实践(三)——高性能编码优化

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/42318689

在前两篇文章当中,我们主要学习了Android内存方面的相关知识,包括如何合理地使用内存,以及当发生内存泄露时如何定位出问题的原因。那么关于内存的知识就讨论到这里,今天开始我们将学习一些性能编码优化的技巧。

这里先事先提醒大家一句,本篇文章中讨论的编码优化技巧都是属于一些“微优化”,也就是说即使我们都按照本篇文章的技巧来优化代码,在性能方面也是看不出有什么显著的提升的。使用合适的算法与数据结构将永远是你优化程序性能的最主要手段,但本篇文章中不会讨论这一块的内容。因此,这里我们即将学习的并不是什么灵丹妙药,而是大家应该把这些技巧当作一种好的编码规范,我们在平时写代码时就可以潜移默化地使用这些编码规范,不仅能够在微观层面提升程序一定的性能,也可以让我们的代码变得更加专业,下面就让我们来一起学习一下这些技巧。

避免创建不必要的对象

创建对象从来都不应该是一件随意的事情,因为创建一个对象就意味着垃圾回收器需要回收一个对象,而这两步操作都是需要消耗时间的。虽说创建一个对象的代价确实非常小,并且Android 2.3版本当中又增加了并发垃圾回收器机制(详见 Android最佳性能实践(二)——分析内存的使用情况),这让GC操作时的停顿时间也变得难以察觉,但是这些理由都不足以让我们可以肆意地创建对象,需要创建的对象我们自然要创建,但是不必要的对象我们就应该尽量避免创建。

下面来看一些我们可以避免创建对象的场景:

  • 如果我们有一个需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
  • 在没有特殊原因的情况下,尽量使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是一样。
  • 当一个方法的返回值是String的时候,通常可以去判断一下这个String的作用是什么,如果我们明确地知道调用方会将这个返回的String再进行拼接操作的话,可以考虑返回一个StringBuffer对象来代替,因为这样可以将一个对象的引用进行返回,而返回String的话就是创建了一个短生命周期的临时对象。
  • 正如前面所说,基本数据类型要优于对象数据类型,类似地,基本数据类型的数组也要优于对象数据类型的数组。另外,两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的两个数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效得多。

当然上面所说的只是一些代表性的例子,我们所要遵守的一个基本原则就是尽可能地少创建临时对象,越少的对象意味着越少的GC操作,同时也就意味着越好的程序性能和用户体验。

静态优于抽象

如果你并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)。

对常量使用static final修饰符

我们先来看一下在一个类的最顶部定义如下代码:

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类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种非常好的习惯。

使用增强型for循环语法

增强型for循环(也被称为for-each循环)可以用于去遍历实现Iterable接口的集合以及数组,这是jdk 1.5中新增的一种循环模式。当然除了这种新增的循环模式之外,我们仍然还可以使用原有的普通循环模式,只不过它们之间是有效率区别的,我们来看下面一段代码:

static class Counter {
    int mCount;
}

Counter[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mCount;
    }
}

public void one() {
    int sum = 0;
    Counter[] localArray = mArray;
    int len = localArray.length;
    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mCount;
    }
}

public void two() {
    int sum = 0;
    for (Counter a : mArray) {
        sum += a.mCount;
    }
}

可以看到,上述代码当中我们使用了三种不同的循环方式来对mArray中的所有元素进行求和。其中zero()方法是最慢的一种,因为它是把mArray.length写在循环当中的,也就是说每循环一次都需要重新计算一次mArray的长度。而one()方法则相对快得多,因为它使用了一个局部变量len来记录数组的长度,这样就省去了每次循环时字段搜寻的时间。two()方法在没有JIT(Just In Time Compiler)的设备上是运行最快的,而在有JIT的设备上运行效率和one()方法不相上下,唯一需要注意的是这种写法需要JDK 1.5之后才支持。

但是这里要跟大家提一个特殊情况,对于ArrayList这种集合,自己手写的循环要比增强型for循环更快,而其他的集合就没有这种情况。因此,对于我们来说,默认情况下可以都使用增强型for循环,而遍历ArrayList时就还是使用传统的循环方式吧。

多使用系统封装好的API

Java语言当中其实给我们提供了非常丰富的API接口,我们在编写程序时如果可以使用系统提供的API就应该尽量使用,系统提供的API完成不了我们需要的功能时才应该自己去写,因为使用系统的API在很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。

比如说String类当中提供的好多API都是拥有极高的效率的,像indexOf()方法和一些其它相关的API,虽说我们通过自己编写算法也能够完成同样的功能,但是效率方面会和这些方法差的比较远。这里举个例子,如果我们要实现一个数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然是可行的,但是如果我们直接使用系统中提供的System.arraycopy()方法将会让执行效率快9倍以上。

避免在内部调用Getters/Setters方法

我们平时写代码时都被告知,一定要使用面向对象的思维去写代码,而面向对象的三大特性我们都知道,封装、多态和继承。其中封装的基本思想就是不要把类内部的字段暴漏给外部,而是提供特定的方法来允许外部操作相应类的内部字段,从而在Java语言当中就出现了Getters/Setters这种封装技巧。

然而在Android上这个技巧就不再是那么的受推崇了,因为字段搜寻要比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。不过我们肯定不能仅仅因为效率的原因就将封装这个技巧给抛弃了,编写代码还是要按照面向对象思维的,但是我们可以在能优化的地方进行优化,比如说避免在内部调用getters/setters方法。

那什么叫做在内部调用getters/setters方法呢?这里我举一个非常简单的例子:

public class Calculate {

	private int one = 1;

	private int two = 2;

	public int getOne() {
		return one;
	}

	public int getTwo() {
		return two;
	}

	public int getSum() {
		return getOne() + getTwo();
	}
}

可以看到,上面是一个Calculate类,这个类的功能非常简单,先将one和two这两个字段进行了封装,然后提供了getOne()方法获取one字段的值,提供了getTwo()方法获取two字段的值,还提供了一个getSum()方法用于获取总和的值。

这里我们注意到,getSum()方法当中的算法就是将one和two的值相加进行返回,但是它获取one和two的值的方式也是通过getters方法进行获取的,其实这是一种完全没有必要的方式,因为getSum()方法本身就是Calculate类内部的方法,它是可以直接访问到Calculate类中的封装字段的,因此这种写法在Android上是不推崇的,我们可以进行如下修改:

public class Calculate {

	private int one = 1;

	private int two = 2;

	......

	public int getSum() {
		return one + two;
	}
}

改成这种写法之后,我们就避免了在内部调用getters/setters方法,而对于外部而言Calculate类仍然是具有很好的封装性的。

当然,本篇文章中推荐的这些技巧呢也并不全面,只是从Android官方文档抽取了几个感觉比较实用的分享给大家,更多技巧大家也可以到Android官网上去阅读。另外在高性能编码方面《Efficient Java》这本书当中也提供了非常多的技巧,有兴趣的朋友也可以去阅读一下这本书。那么本篇文章就到这里,下篇文章当中将会介绍Android布局优化的技巧,敬请期待。

第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注我的微信公众号,扫一扫下方二维码或搜索微信号guolin_blog,即可关注。

时间: 2024-11-05 11:26:34

Android最佳性能实践(三)——高性能编码优化的相关文章

Android最佳性能实践(四)——布局优化技巧

在前面几篇文章当中,我们学习了如何通过合理管理内存,以及高性能编码技巧的方式来提升应用程序的性能.然而实际上界面布局也会对应用程序的性能产生比较大的影响,如果布局写得糟糕的话,那么程序加载UI的速度就会非常慢,从而造成不好的用户体验.那么本篇文章我们就来学习一下,如何通过优化布局来提供应用程序的性能.还没有看过前面前面一篇文章的朋友建议可以先去阅读 Android最佳性能实践(三)——高性能编码优化 . 重用布局文件 Android系统中已经提供了非常多好用的控件,这让我们在编写布局的时候可以很

Android最佳性能实践(一)——合理管理内存

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/42238627 内存(RAM)对于任何一个软件开发环境都是种非常珍贵的资源,而对于移动操作系统来讲的话,则会显得更加珍贵,因为手机的硬件条件相对于PC毕竟是比较落后的.尽管Android系统的虚拟机拥有自动回收垃圾的机制,但这并不代表我们就可以忽视应该在什么时候分配和释放内存. 为了使垃圾回收器可以正常释放程序所占用的内存,在编写代码的时候就一定要注意尽量避免出现内存泄漏的情况(通

Android最佳性能实践(二)——分析内存的使用情况

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/42238633 虽说现在的手机内存都已经非常大了,但是我们大家都知道,系统是不可能将所有的内存都分配给我们的应用程序的.没错,每个程序都会有可使用的内存上限,这被称为堆大小(Heap Size).不同的手机,堆大小也不尽相同,随着现在硬件设备不断提高,堆大小也已经由Nexus One时的32MB,变成了Nexus 5时的192MB.如果大家想要知道自己手机的堆大小是多少,可以调用如

Android最佳性能实践

摘要:有人推崇产品,有人推崇运营,也有人推崇战略-到底该推崇什么?李智勇系统地分析了这三者之间的思路,并引用黑格尔的一句话,给出了自己的看法:在尺度中已经蕴含本质,这在产品.运营.战略的侧重上体现的非常好. 视野不拉升或者认知不深入时,就容易在盲人摸象层面上反复,看到微信火了,那就产品最重要,看到阿里火了,那就平台最重要.实际上一个比较显然的事实是,没有锥子一样的产品,那你就火不起来:不能从产品升级为平台,那就很可能活不下去.当然,有些人会说平台也是一种产品,但就和人与猴子都是灵长目,实际上仍是

Android最佳性能实践(一)

摘要:有人推崇产品,有人推崇运营,也有人推崇战略-到底该推崇什么?李智勇系统地分析了这三者之间的思路,并引用黑格尔的一句话,给出了自己的看法:在尺度中已经蕴含本质,这在产品.运营.战略的侧重上体现的非常好. 视野不拉升或者认知不深入时,就容易在盲人摸象层面上反复,看到微信火了,那就产品最重要,看到阿里火了,那就平台最重要.实际上一个比较显然的事实是,没有锥子一样的产品,那你就火不起来:不能从产品升级为平台,那就很可能活不下去.当然,有些人会说平台也是一种产品,但就和人与猴子都是灵长目,实际上仍是

Android Studido下的应用性能优化总结-内存优化

转载请标明出处(请勿转载删除底部微博.微信等信息): http://blog.csdn.net/Y1258429182/article/details/51176424 本文出自:杨哲丶的博客 上一篇文章总结的布局优化的问题,如果对布局优化不是很熟悉的,可以看一下Android Studido下的应用性能优化总结–布局优化, 这周一直筹划总结一下内存优化的问题,因为现在对于应用优化的文章很多,但是还是想完善一下才想分享这篇文章的,我会从项目中遇到的一个问题,通过解决问题的过程来分享知识,希望大家

Android Studio下的应用性能优化总结-内存优化

转载请标明出处(请勿转载删除底部微博.微信等信息): http://blog.csdn.net/Y1258429182/article/details/51176424 本文出自:杨哲丶的博客 上一篇文章总结的布局优化的问题,如果对布局优化不是很熟悉的,可以看一下Android Studido下的应用性能优化总结–布局优化, 这周一直筹划总结一下内存优化的问题,因为现在对于应用优化的文章很多,但是还是想完善一下才想分享这篇文章的,我会从项目中遇到的一个问题,通过解决问题的过程来分享知识,希望大家

Android最佳实践之高效的应用导航

设计(一)- 规划Screens和他们之间的关系 原文地址:http://developer.android.com/training/design-navigation/screen-planning.html#beyond-simplistic-design 设计和开发Android应用程序的第一个步骤是确定用户能够查看和处理应用.一旦你知道用户与之交互的应用程序之间交互什么数据,下一步就是设计交互,允许用户导航到app的不同部分,进入和退出应用程序中的界面. 这篇文章开始向你展示如何规划高

sql优化(oracle)- 第三部分 &#160;sql优化总结

第三部分  sql优化总结        1. 优化一般原则        2. 具体注意事项 1. SQL优化一般性原则 1)目标:减少服务器资源消耗(主要是磁盘IO) 2)设计: 1. 尽量依赖oracle优化器 2. 合适的索引(数据重复量大的列不要简历二叉树索引,可以使用位图索引: 对应数据操作频繁的表,索引需要定期重建,减少失效的索引和碎片) 3)编码: 1. 利用索引 2. 合理利用临时表 3. 避免写过于复杂的sql: 4. 尽量减小事务的粒度 2. 具体注意事项 1)查询时尽量使