在前两篇文章当中,咱们主要学习了Android内存方面的相关知识,包括如何合理地使用内存,以及当发生内存泄露时如何定位出问题的缘由。那么关于内存的知识就讨论到这里,今天开始咱们将学习一些性能编码优化的技巧。html
这里先事先提醒你们一句,本篇文章中讨论的编码优化技巧都是属于一些“微优化”,也就是说即便咱们都按照本篇文章的技巧来优化代码,在性能方面也是看不出有什么显著的提高的。使用合适的算法与数据结构将永远是你优化程序性能的最主要手段,但本篇文章中不会讨论这一块的内容。所以,这里咱们即将学习的并非什么灵丹妙药,而是你们应该把这些技巧看成一种好的编码规范,咱们在平时写代码时就能够潜移默化地使用这些编码规范,不只可以在微观层面提高程序必定的性能,也可让咱们的代码变得更加专业,下面就让咱们来一块儿学习一下这些技巧。java
建立对象历来都不该该是一件随意的事情,由于建立一个对象就意味着垃圾回收器须要回收一个对象,而这两步操做都是须要消耗时间的。虽然说建立一个对象的代价确实很是小,而且Android 2.3版本当中又增长了并发垃圾回收器机制(详见 Android最佳性能实践(二)——分析内存的使用状况),这让GC操做时的停顿时间也变得难以察觉,可是这些理由都不足以让咱们能够肆意地建立对象,须要建立的对象咱们天然要建立,可是没必要要的对象咱们就应该尽可能避免建立。android
下面来看一些咱们能够避免建立对象的场景:算法
若是咱们有一个须要拼接的字符串,那么能够优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号链接符,由于使用加号链接符会建立多余的对象,拼接的字符串越长,加号链接符的性能越低。编程
在没有特殊缘由的状况下,尽可能使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是同样。数组
当一个方法的返回值是String的时候,一般能够去判断一下这个String的做用是什么,若是咱们明确地知道调用方会将这个返回的String再进行拼接操做的话,能够考虑返回一个StringBuffer对象来代替,由于这样能够将一个对象的引用进行返回,而返回String的话就是建立了一个短生命周期的临时对象。数据结构
正如前面所说,基本数据类型要优于对象数据类型,相似地,基本数据类型的数组也要优于对象数据类型的数组。另外,两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的两个数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效得多。并发
固然上面所说的只是一些表明性的例子,咱们所要遵照的一个基本原则就是尽量地少建立临时对象,越少的对象意味着越少的GC操做,同时也就意味着越好的程序性能和用户体验。性能
若是你并不须要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么能够将这个方法设置成静态方法,这会让调用的速度提高15%-20%,同时也不用为了调用这个方法而去专门建立对象了,这样还知足了上面的一条原则。另外这也是一种好的编程习惯,由于咱们能够放心地调用静态方法,而不用担忧调用这个方法后是否会改变对象的状态(静态方法内没法访问非静态字段)。学习
咱们先来看一下在一个类的最顶部定义以下代码:
1
2
|
static
int
intVal =
42
;
static
String strVal =
"Hello, world!"
;
|
编译器会为上述代码生成一个初始化方法,称为<clinit>方法,该方法会在定义类第一次被使用的时候调用。而后这个方法会将42的值赋值到intVal当中,并从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后,咱们就能够经过字段搜寻的方式来去访问具体的值了。
可是咱们还能够经过final关键字来对上述代码进行优化:
1
2
|
static
final
int
intVal =
42
;
static
final
String strVal =
"Hello, world!"
;
|
通过这样修改以后,定义类就再也不须要一个<clinit>方法了,由于全部的常量都会在dex文件的初始化器当中进行初始化。当咱们调用intVal时能够直接指向42的值,而调用strVal时会用一种相对轻量级的字符串常量方式,而不是字段搜寻的方式。
另外须要你们注意的是,这种优化方式只对基本数据类型以及String类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种很是好的习惯。
加强型for循环(也被称为for-each循环)能够用于去遍历实现Iterable接口的集合以及数组,这是jdk 1.5中新增的一种循环模式。固然除了这种新增的循环模式以外,咱们仍然还可使用原有的普通循环模式,只不过它们之间是有效率区别的,咱们来看下面一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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时就仍是使用传统的循环方式吧。
Java语言当中其实给咱们提供了很是丰富的API接口,咱们在编写程序时若是可使用系统提供的API就应该尽可能使用,系统提供的API完成不了咱们须要的功能时才应该本身去写,由于使用系统的API在不少时候比咱们本身写的代码要快得多,它们的不少功能都是经过底层的汇编模式执行的。
好比说String类当中提供的好多API都是拥有极高的效率的,像indexOf()方法和一些其它相关的API,虽然说咱们经过本身编写算法也可以完成一样的功能,可是效率方面会和这些方法差的比较远。这里举个例子,若是咱们要实现一个数组拷贝的功能,使用循环的方式来对数组中的每个元素一一进行赋值固然是可行的,可是若是咱们直接使用系统中提供的System.arraycopy()方法将会让执行效率快9倍以上。
咱们平时写代码时都被告知,必定要使用面向对象的思惟去写代码,而面向对象的三大特性咱们都知道,封装、多态和继承。其中封装的基本思想就是不要把类内部的字段暴漏给外部,而是提供特定的方法来容许外部操做相应类的内部字段,从而在Java语言当中就出现了Getters/Setters这种封装技巧。
然而在Android上这个技巧就再也不是那么的受推崇了,由于字段搜寻要比方法调用效率高得多,咱们直接访问某个字段可能要比经过getters方法来去访问这个字段快3到7倍。不过咱们确定不能仅仅由于效率的缘由就将封装这个技巧给抛弃了,编写代码仍是要按照面向对象思惟的,可是咱们能够在能优化的地方进行优化,好比说避免在内部调用getters/setters方法。
那什么叫作在内部调用getters/setters方法呢?这里我举一个很是简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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上是不推崇的,咱们能够进行以下修改:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
Calculate {
private
int
one =
1
;
private
int
two =
2
;
......
public
int
getSum() {
return
one + two;
}
}
|
改为这种写法以后,咱们就避免了在内部调用getters/setters方法,而对于外部而言Calculate类仍然是具备很好的封装性的。