Android性能优化(一)代码优化

S代码级别的优化是最基本的能力,每个开发人员都应该不断努力提高自己的编码能力,从而写出高效的代码。根据Android 官方的建议,编写高效代码的两个基本准则如下:

1 不要做冗余的工作

2 尽量避免次数过多的内存分配操作

数据结构的选择


正确的选择合适的数据结构是很重要的,对java中常见的数据结构例如ArrayList和LinkedList,HashMap和HashSet等,需要做到对他们的联系与区别有较深入的理解,这样在编写代码中面临选择时才能做出正确的选择,下面我们一在Android 开发中使用SparseArray代替HashMap为例进行说明,SparseArray是Android 平台特有的稀疏数组的实现,它是Integer到Object的一个映射,在特定场合可用于代替HashMpa>提高性能,它的核心实现是二分查找算法

SparseArray家族目前有以下四类:

Android性能优化(一)代码优化_第1张图片

需要注意以下几点:

1 SparseArray不是线程安全的

2 由于要进行二分查找,因此SparseArray会对插入的数据按照key值大小顺序插入

3 SparseArray对删除操作做了优化,它并不是立即删除这个元素,而是通过设置标志位的方式,后面尝试重用

在Android 工程中运行Lint进行静态代码分析,会有一个名为AndroidLintSpaseArrays的检查项,如果违规,它会提示

这样可以很轻松的找到工程中可优化的地方

handler和内部类的正确用法


Android 代码中设计线程间通信的地方经常会使用Handler,典型的代码结构如下:

Android性能优化(一)代码优化_第2张图片

使用Android Lint分析这段代码,会违反检查项AndroidLintHandlerLeak,得到如下提示

          那么产生内存泄漏的原因可能是什么呢,我们知道,Handler是和Looper以及MessageQueue一起工作的,在Android中,一个应用启动后,系统默认会创建一个为主线程服务的Looper对象,该Looper对象用于处理主线程的所有Message对象,它的生命周期贯穿于整个应用的生命周期,在主线程中使用的Handler 都会默认绑定到这个Looper对象。在主线程中创建Handler对象时,它会立即关联主线程Looper对象的MessageQueue,这时发送到MessageQueue中的Message对象都会持有这个Handler对象的引用,这样在Looper处理消息时才能回调到Handler的handlerMessage方法,因此,如果Message还没有被处理完成,那么Handler对象也不会被垃圾回收。

        在上面的代码中,将Handler 的实例声明为HandlerActivity类的内部类,而在java语言中,非静态内部匿名类会持有外部类的一个隐式的引用,这样就可能会导致外部类无法被垃圾回收。因此,最终由于MessageQueue中的Message还没处理完成,就会持有Handler对象的引用,而非静态的Handler对象会持有外部类HandlerActivity的引用,这个Activity无法被垃圾回收,从而导致内存泄漏。

一个明显会引入内存泄漏的例子如下:

Android性能优化(一)代码优化_第3张图片

由于消息延迟5分钟发送,因此,当用户进入这个Activity并退出后,在消息发生并处理完成之前,这个Activity是不会被系统回收的(系统内存不够用的情况除外)。

如何解决?

1 在子线程中使用Handler ,这时需要开发者自己创建一个Looper对象,这个Looper对象的生命周期同一般的java对象,因此这种用法没有问题

2 将Handler声明为静态的内部类,静态内部类不会持有外部类的引用,因此,也不会引起内存泄漏,经典用点的代码如下。

Android性能优化(一)代码优化_第4张图片
Android性能优化(一)代码优化_第5张图片

正确使用Context


Context 应该是每个入门Android 开发的程序员第一个接触到的概念,它代表当前的上下文环境,可以用来实现很多功能的调用

Android性能优化(一)代码优化_第6张图片

可见。正确理解Context的概念是很重要的,虽然应用开发中随处可见Context的使用,但并不是所有的Context实例都具备相同的功能,在使用上需要区别对待,否则可能会引入问题。

Context的种类

根据Context依托的组件以及用途不同,我们可以将Context分为如下几种:

1 Application:Android应用中的默认单例类,在Activity或者Service中通过getApplication可以获取到这个单例,通过context.getApplicationContext()可以获取到应用全局唯一的Context实例。

2Activity/Service:这两个类都是ContextWrapper的子类,在这两个类可以通过getBaseContext()获取到它们的Context实例,不同的Activity或者Service实例,他妈的Context都是独立的,不会复用。

3 BroadcastReceiver:和Activity以及Service不同,BroadcastReceiver本身并不是Context的子类,而是在回调函数onReceiver()中由Android 框架传入一个Context的实例。系统传入的这个Context实例是经过功能裁剪的,他不能调用registerReceiver()以及bindService()这两个函数。

4 ContentProvider:同样的,ContentProvider也不是Context的子类,但是创建时系统会传入一个Context实例,这样在ContentProvider中可以通过调用getContext函数获取,如果ContentProvider和调用者处于相同的应用进程中,那么getContext()将返回应用全局唯一的Context实例,如果是其他进程调用的ContentProvider,那么ContentProvider将持有自身所在进程的Context实例。

错误使用Context导致的内存泄漏

典型的例子是在实现单例模式时使用Context

Android性能优化(一)代码优化_第7张图片

如果使用者调用getInstance时传入的是一个Activity或者Service 的实例,那么在应用退出之前,由于单例一直存在,会导致对应的Activity或者Service被单例引用,从而不会被垃圾回收,Activity或者Service中关联的其他View或者数据结构对象也不会被释放,从而导致内存泄漏,正确的做法是使用Application Context,因为它是应用唯一的,而且生命周期是和应用一致的。

Android性能优化(一)代码优化_第8张图片

不同Context的对比

Android性能优化(一)代码优化_第9张图片

其中NO[1]标记表示对应的组件并不是真的不可以启动Activity,而是建议不要这么做,因为这些组件会在新的Task中创建Activity,而不是原来的Task中。

NO[2]标记也是表示不建议这么做,因为在非Activity中进行Layout Inflation,会使用系统默认的主题,而不是应用中设置的主题

NO[3]标记表示在Android4.2以上的系统上,如果注册的BroadcaseReceiver是null时时可以的,用来获取sticky广播的当前值。

掌握Java的四种引用方式


掌握Java的四种引用类型对于写出内存使用良好的应用时很关键的。

强引用:Java里面最广泛使用的一种,也是对象默认的引用类型,如果一个对象具有强引用,那么垃圾回收器是不会对它进行回收操作的,当内存空间不足时,Java虚拟机将会抛出OutOfMemoryError错误,这时应用将会终止运行

软引用:一个对象如果只有软引用,那么当内存空间充足时,垃圾回收器不会对它进行回收操作,只有当内存空间不足时,这个对象才会被回收,软引用可以用来实现内存敏感的高速缓存,如果配合引用队列(ReferenceQueue)使用,当软引用指向的对象被垃圾回收器回收后,Java虚拟机将会把这个软引用加入到与之关联的引用队列中

弱引用:从名字上可以看出,弱引用是比软引用更弱的一种引用类型,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有弱引用的对象中,不论当前内存空间是否不足,都会对弱引用对象进行回收,弱引用也可以和一个引用队列配合使用,当弱引用指向的对象被回收后,Java虚拟机会将这个弱引用加入到与之关联的引用队列中

虚引用:和软引用和弱引用不同,虚引用并不会对所指向的对象生命周期产生任何影响,也就是对象还是会按照它原来的方式被垃圾回收器回收,虚引用本质上只是一个标记作用,主要用来跟踪对象被垃圾回收的活动,虚引用必须和引用队列配合使用,当对象被垃圾回收时,如果存在虚引用,那么java虚拟机会将这个虚引用加入到与之关联的引用队列中。

其他代码微优化


1 避免创建非必要的对象:

对象的创建需要内存分配,对象的销毁需要垃圾回收,这些都会一定程序上影响到应用的性能,因此一般来说,最好是重用对象而不是在每次需要的时候去创建一个功能相同的新对象,特别是注意不要在循环中重复创建相同的对象。

2 对常量使用static final修饰

对于基本数据类型和String类型的常量,建议使用static final修饰,因为final类型的常量会在进入静态dex文件的域初始化部分,这时对基本数据类型和String类型常量的调用不会涉及类的初始化,而是直接调用字面量。

3 避免内部的Getters/Setters

在面向对象编程中,Getters/Settrers的作用主要是对外屏幕具体的变量定义,从而达到更好的封装性,但如果是在类内部还使用Getters/Setters函数访问变量的话,会降低访问的速度,根据Android 官方文档,在没有JIT(Just in Time)编译器时,直接访问变量的速度是调用Getter方法的三倍,在JIT编译时,直接访问变量的速度是调用Getter方法的7倍,当然,如果你的应用中使用了ProGuard的话,那么ProGuard会对Getters/Setters进行内联操作,从而达到直接访问的效果

4 代码的重构

代码的重构是一项长期的持之以恒的工作,需要依靠团队中的每一个成员来维护代码库的高质量,如何有效的进行代码重构,除了需要对你所在的项目有较深的理解之外,你还需要一定的方法指导,强烈推荐《代码整洁之道》

你可能感兴趣的:(Android性能优化(一)代码优化)