性能优化系列——代码优化

概述

代码优化是每个程序员应有的一项能力,要在日常的开发中不断地提升自我的编码能力,在一点一滴中进步,不断地积累不断地完善自己。对于代码的优化,Android官网的建议是:

  • 不要做冗余的工作。
  • 尽量避免次数过多的内存分配操作。

剔除冗余的代码

当程序中多处出现相同代码时(包括相同的代码及常量),应进行整合,提取出公共的方法,公共的工具类。

避免创建非必要的对象

对象的创建创建需要分配内存,对象的销毁需要垃圾回收,这些操作都是有成本的,会或多或少的影响应用的性能。当一个对象可以复用时要尽量的去复用之前的对象。尤其是一些比较大的对象,像bitmap对象,频繁的创建与销毁,对系统来说是一个不小的符合,过于频繁的创建与回收,会造成内存的抖动,严重时可能会导致应用的崩溃。

在使用String对象时,出现字符串连接操作时应尽量使用StringBuilder/StringBuffer来代替,由于String的拼接是生成新的对象,而旧的对象就会被回收,这就涉及到了对象的创建(内存的分配)及垃圾回收的操作,如果这种情况较多的话是很影响程序性能的。

注意Getters/Setters的写法

我们在开发中使用get/set方法来屏蔽外界对我们所定义对象的感知,从而达到封装的效果,相信我们大多数人都是这样使用的。这样使用会产生性能问题。根据Android官网的介绍,在没有JIT编译器时,直接访问的速度时调用get方法的3倍;在JIT编译时,直接访问是调用get方法的7倍。从这里可以看出直接调用一个类的变量速度是最快的,而且快了好几倍。好在当项目中使用ProGuard的话,它会对get/set方法进行内联操作,从而达到直接访问的效果。

熟练使用java中的四种引用方式

对java四种引用方式的介绍请参考这篇文章:垃圾回收器及内存分配策略

熟练使用这四种引用你就可以处理大多数的你存泄露问题了。如一个静态的dialog引用了一个activity的context,这就造成了内存的泄露,这时就可以使用弱引用来引用这个context,而不是使用强引用。使用弱引用之后当activity被销毁后,在GC是dialog持有的context对象就回被释放,就不会造成内存的泄露了。

Context的使用

相信在开发中我们经常与context打交道,正确的使用context是非常必要的,能够避免一些内存泄露,避免一些程序报错。

种类

context可以分为以下几种:

  • Application:Android程序种全局唯一的一个context实例。
  • Activity/Service:这两个都是ContextWrapper的子类,每个Activity和Service都是一个context。因此每添加一个Activity或者Service就回增加一个context
  • ContentProvider:它不是context的子类,当时它创建时系统会传入一个context实例。如果ContentProvider和调用者处于同一个进程中,getContext()返回应用全局唯一的Context实例。如果是其他进程调用的ContentProvider,那么ContentProvider将持有自身所在进程的Context实例。
  • Broadcast Receiver:和ContentProvider类似,它也不是context的子类,是由系统传入的context,但是这个context是经过功能剪裁的,它不能调用registerReceiver()和宾得Service()方法。

不同context的功能不同,总结如下:

功能 Application Activity Service BroadcastReceiver ContentProvider
显示Dialog NO YES NO NO NO
启动Activity NO【1】 YES NO【1】 NO【1】 NO【1】
实现Layout Inflation NO【2】 YES NO【2】 NO【2】 NO【2】
启动Service YES YES YES YES YES
绑定Service YES YES YES YES NO
发送Broadcast YES YES YES YES YES
注册Broadcast YES YES YES YES NO【3】
加载资源Resource YES YES YES YES YES

NO【1】:是可以启动Activity,但是不建议这么做,因为如果这样启动,会在新的Task中创建Activity,而不是在原先的Task中创建Activity。
NO【2】:也是不建议这样做,在非Activity中进行Layout Inflation,会使用系统默认的主题,而不是使用应用中设置的主题。
NO【3】:表示在Android4.2及以上的系统上,如果注册的BroadcastReceiver是null时是可以的,用来获取sticky广播的当前值。

选择合适的数据结构

对于不同的数据选择合适的数据结构是非常必要的,我们要对java中的ArrayList,Linked List,HashSet和HashSet等要非常的了解,只有深入了解了,才能灵活的去运用。通常情况下我们使用Android中特有的稀疏数组SparseArray来代替HashMap。特定场合能提高应用的性能。他的核心是二分查找算法。目前SparseArray有以下四类:

  1. SparseIntArray 用来代替HashMap<Integer,Integer>
  2. SparseBooleanArray 用来代替HashMap<Integer,Boolean>
  3. SparseLongArray 用来代替HashMap<Integer,Long>
  4. SparseArray 用来代替HashMap<Integer,String>

由于SparseArray采用的是二分查找算法,因此在插入数据时不可避免的会按照Key值的大小进行插入;SparseArray对删除操作做了优化,它并不会立即删除这个元素,而是通过设置标志位(DELETED)的方式,后面尝试重用。

正确的使用内部类

非静态内部类可能会引起内存的泄露,究其原因是非静态内部类持有外部类的引用,同时可能存在非静态内部类与外部类生命周期不同步的情况,当非静态内部类的生命期比外部类要长时,就回产生内存的泄露(因为非静态内部类持有外部类的引用,导致外部类无法释放),最常见的就是Handler的使用,一不注意就回产生内存泄露。

解决方法:

  1. 当预料会有生命周期不同步的情况时,尽量避免使用非静态内部类。
  2. 将有可能产生内存泄露的非静态内部类声明为静态内部类(静态内部类不持有外部类的引用),可以避免外部类的泄露。

尽可能地使用局部变量

局部变量是保存在栈中的,访问速度较快,而其他变量如:实例变量,静态变量等是创建在堆里的,其访问速度是很慢的。所以在方法中传过来的变量尽量变为局部变量再使用,此外栈中创建的变量,随着方法的结束而回收,不需要额外的垃圾回收。

再循环内或者频繁调用的方法内避免重复创建对象的引用

如:

for (int i = 0; i < count; i++)
{
    Object obj = new Object();    
}

这样会创建count份Object对象的引用,当count值很大时,这也是一笔不小的内存开销。

最后不断的重构代码

重构代码是一项长期的工作,随着阅历的不断成长,你就会发现之前的代码还会存在着不少的提升空间。

最后确保自己永远走在前进的路上,不断学习,不断突破自我。

其他后续补充

猜你喜欢

转载自blog.csdn.net/u013049016/article/details/89382494