代码优化
概述
代码优化是每个程序员应有的一项能力,要在日常的开发中不断地提升自我的编码能力,在一点一滴中进步,不断地积累不断地完善自己。对于代码的优化,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有以下四类:
- SparseIntArray 用来代替HashMap<Integer,Integer>
- SparseBooleanArray 用来代替HashMap<Integer,Boolean>
- SparseLongArray 用来代替HashMap<Integer,Long>
- SparseArray 用来代替HashMap<Integer,String>
由于SparseArray采用的是二分查找算法,因此在插入数据时不可避免的会按照Key值的大小进行插入;SparseArray对删除操作做了优化,它并不会立即删除这个元素,而是通过设置标志位(DELETED)的方式,后面尝试重用。
正确的使用内部类
非静态内部类可能会引起内存的泄露,究其原因是非静态内部类持有外部类的引用,同时可能存在非静态内部类与外部类生命周期不同步的情况,当非静态内部类的生命期比外部类要长时,就回产生内存的泄露(因为非静态内部类持有外部类的引用,导致外部类无法释放),最常见的就是Handler的使用,一不注意就回产生内存泄露。
解决方法:
- 当预料会有生命周期不同步的情况时,尽量避免使用非静态内部类。
- 将有可能产生内存泄露的非静态内部类声明为静态内部类(静态内部类不持有外部类的引用),可以避免外部类的泄露。
尽可能地使用局部变量
局部变量是保存在栈中的,访问速度较快,而其他变量如:实例变量,静态变量等是创建在堆里的,其访问速度是很慢的。所以在方法中传过来的变量尽量变为局部变量再使用,此外栈中创建的变量,随着方法的结束而回收,不需要额外的垃圾回收。
再循环内或者频繁调用的方法内避免重复创建对象的引用
如:
for (int i = 0; i < count; i++)
{
Object obj = new Object();
}
这样会创建count份Object对象的引用,当count值很大时,这也是一笔不小的内存开销。
最后不断的重构代码
重构代码是一项长期的工作,随着阅历的不断成长,你就会发现之前的代码还会存在着不少的提升空间。
最后确保自己永远走在前进的路上,不断学习,不断突破自我。
其他后续补充