安卓性能优化—内存优化

1,968 阅读6分钟
原文链接: blog.csdn.net

Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,本篇博客,我将介绍内存优化的相关知识。

java的内存区域

大致分为堆(Heap),栈(Stacks)、方法区(MethodArea);

  1. 堆(Heap):主要存放new出来的对象以及对象中所有的成员变量和数组,在堆中分配的内存,由Java虚拟机自动垃圾回收器来管理,在对中产生一个数组或对象,就会在栈中产生一个变量来指向堆中的实体对象,这个变量就是数组或者对象的引用变量,以后就可以在程序中使用栈中的这个引用变量来访问堆中的对象,堆是不连续的内存区域,系统采用链表来存储空闲内存地址;在堆中频繁的new、delete会产生大量的内存碎片,使程序效率降低;

  2. 栈(Stacks):在执行函数时,函数中的局部变量存储在栈中,函数执行结束后,这些局部变量自动释放,栈内存分配运算内置于处理器的指令集中,效率高,但是分配的内存容量有限(windows下栈大小是2M),对于栈,它是连续的内存区域,先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。

  3. 方法区(MethodArea):内存在程序编译时就分配好了的,这块区域在整个程序运行期间都存在,主要存放静态数据和常量;

程序中的内存泄露主要就是针对堆中的对象没有及时释放而言的。

内存泄露产生的原因及场景

这里先了解一下Java的垃圾回收机制,内存泄漏指当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而就导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏;

内存泄露的场景有很多

  • 非静态内部类的静态实例
    由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。
  • 资源对象未关闭
    资源性对象如Cursor、Stream、Socket,Bitmap,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。
  • 注册对象未反注册
    我们常常写很多的Listener,未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。

  • 单例模式导致的内存泄露
    单例模式的特点是其生命周期和application保持一致,当activity对象被单例模式所引用之后,这个activity就无法被回收。

  • 属性动画导致的内存泄露
    属性动画持有view的引用,动画结束后要调用animator.cancel()来停止动画;

bitmap优化

对bitmap做缩放,对bitmap做缩放的意义很明显,提示显示性能,避免分配不必要的内存。Android提供了现成的bitmap缩放的API,叫做createScaledBitmap(),使用这个方法可以获取到一张经过缩放的图片
这里写图片描述
上面的方法能够快速的得到一张经过缩放的图片,可是这个方法能够执行的前提是,原图片需要事先加载到内存中,如果原图片过大,很可能导致OOM。
inSampleSize能够等比的缩放显示图片,同时还避免了需要先把原图加载进内存的缺点。我们会使用类似像下面一样的方法来缩放bitmap:
这里写图片描述
这里写图片描述
还有一个经常使用到的技巧是inJustDecodeBounds,inJustDecodeBounds设为true时,bitmapfactory只会解析图片的原始大小不至于占用什么内存最后加载压缩过后的图片之前记得将inJustDecodeBounds设为false。如下图所示:
这里写图片描述
使用inBitmap属性提升bitmap的循环效率,使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。
这里写图片描述
达到的效果是这样的:
这里写图片描述
使用inBitmap需要注意几个限制条件:

  • 在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。
  • 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。

我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。如下图所示:
这里写图片描述
线程优化
线程优化的思想就是采用线程池,避免程序中存在大量的thread,线程池可以重用内部的线程,从而避免线程的创建和销毁带来的性能开销,线程池可以有效的控制最大并发数,避免大量的线程因抢占系统资源而导致阻塞现象的发生;

一些性能优化的建议

  • 避免城建过多的对象
  • 不要过多使用枚举,枚举占用的内存空间比整型大;
  • 常量用static final修饰
  • 使用一些安卓特有的数据结构,比如sparsearray
  • 适当使用软引用和弱引用
  • 采用内存缓存和磁盘缓存
  • 尽量采用静态内部类,这样可以避免潜在的由于内部类导致的内存泄露。