明白原理,轻松应对Android内存泄漏

相信“内存泄漏”问题,是一个挺让开发者头疼的事情,笔者在回顾以为代码时,惊讶发现:初学Android时,许多不修边幅的代码习惯,导致了许多内存泄漏问题,因此特来分析一下,把自己挖过的坑补一下,也希望奔跑在Android开发道路上的你能够优雅避免~

内存泄漏本质原因:忘记释放分配的内存;应用不需要某对象时候,该对象仍然保持被引用状态(当对象拥有强引用,GC无法回收),而具体的GC机制请见:Java进阶 - JVM 内存管理机制探秘中的“GC机制与内存分配策略”部分。

概述:Context - 最容易引发内存泄漏

Activity、Service、Applictaion;(BroadcastReceiver、ContentProvider虽然不在Context继承树,但其内部会持有Context)
原理:很可能Activity作为Context传递给某些类,Activity生命周期结束之后,某些类仍然存活并保持着该Activity的引用,Activity是重量级对象,却保持引用无法被回收。

不熟悉Context?请见: Android Context 上下文 你必须知道的一切

主要的两种情况:

  • 全局进程持有Activity强引用(某个单例类一直持有引用)
  • 与Activity生命周期无关的的线程,在Activity生命周期结束时,没有清空Activity引用

static 变量

例如:

  • static Activity activity = this;(听说C++开发中常喜欢通过这样的方式管理类,而放到Java上,似乎并不合适。)
    原理:static变量贯穿应用生命周期,泄漏的Activity会一直存在。
  • static view,Activity销毁时候没有设为null;
    原理:将View设置为static,不建议这么做,LayoutInflater需要使用context来加载layout,那么View最终会持有Activity引用,生命周期结束没有设置null,Activity也会一直存在。

解决:

  1. 尽量避免使用static变量;
  2. 如果逻辑上允许,则使用弱引用,可能会有NullPointerException风险(无论内存是否足够,只能生存到下一次GC之前。使用WeakReference 类实现);
  3. 继续使用static变量,记得在Activity被销毁的时候,释放static变量引用。

非静态内部类、匿名内部类持有外部类的引用

原理:简单来说,编译的时候,编译器会自动为内部类构造方法中加上外部类的引用。详情请见:深入理解Java中为什么内部类可以访问外部类的成员
如果外部还对这样的内部类持有一个static引用,那么很有可能导致内存泄漏。

例如:

  • 匿名的AsyncTask被execute,尽管Activity被销毁,也需要等异步任务全部完成才可销毁,匿名的Thread,也是类似的道理;
  • Handler:内部类、匿名类创建Handler,会使得Handler持有外部Activity引用,而Handler与Android消息机制密切相关,当Handler发出Message,Message进入MessageQueue,等待Looper取出来交给Handler进行handleMessage,这漫长的消息链,会让Activity一直引用,生命周期结束了,也不可被销毁;

解决:

  1. 使用静态内部类,静态内部类内部使用弱引用来引外外部类,因为静态内部类并不会持有外部类引用,这样就有效打破了引用链(Handler内存泄漏问题推荐,其他类型也可使用该方式避免);
  2. 当Activity被销毁或者不需要AsyncTask、Thread时,停止异步任务、线程任务;

使用Context获取系统服务

原理:当使用Context.getSystemService(int name)方法,获取系统服务时候,某些服务处于系统进程中一直存在,如果传入Activity引用,也会导致内存泄漏问题。
解决:记得Activity被销毁时候,断开与系统服务的联系(如:registerListener、unregisterListener)

属性动画导致的内存泄漏

原理:Android 3.0 后,属性动画出现,其中有一类无限循环的动画,如果Activity没有在onDestroy方法中停止动画,尽管用户看不见,动画会一直播放下去,执行属性动画的View被动画持有引用,而Activity又被View持有引用,导致Activity也无法释放。
解决:在Activity销毁时,调用animator.cancel()来停止动画即可

学习资源推荐

感谢您的阅读,希望文章对您有所帮助