Android 性能优化之内存泄露LeakCannary

110 阅读4分钟

内存泄露检测 LeakCanary

LeakCanary 是 Square 公司的开元哭, 通过它可以在App 运行的过程中检测内存泄露, 当内存泄露发生时会生产发生泄露对象的引用链, 并通知开发人员

使用

debugImplementation 'com.squareup.leakcannary:leakcannary-android:2.7'

执行流程

heap dump

  • 检测保留的对象
  • 生成堆 dump 文件
  • 分析堆 dump 文件
  • 堆泄露进行分类

image.png

检测对象类型

  • 已销毁的 Activity 实例
  • 销毁的 Fragment 实例
  • 已清除的 ViewModel 实例

四种引用

强引用

被强引用引用或者关联的对象, 在GC 之后不会被回收掉被引用的对象

软引用 SoftReference

  • 一些有用但是非必须,用软引用关联的对象,系统将发生内存溢出OOM之前,这些对象会被回收(如果这次回收之后还没有足够空间,才会抛出内存溢出)

弱引用 WeakReference

  • 一些有用但是并必需,用弱应用关联对象,在 GC 的时候进行回收;
  • 前提这个弱引用引用的一个强引用对象未空的, 没有被

虚引用 PhantomReference

原理

  • 通过 hook Android 的生命周期来自动检测 Activity 和 Fragment 被销毁,这些被 destroy 的对象会被传递给 ObjectWatcher, ObjectWatcher 持有对他们的弱引用。

  • 弱引用和引用队列

ReferenceQueue 与 WeakReference 弱引用和引用队列

  • 弱应用(WeakReference) 和一个引用队列(ReferenceQueue)联合使用, 如果弱引用所引起的对象被垃圾回收器回收, 虚拟机就会把这个弱应用加入到与之管理的引用队列中, 我们可以用此特性来检测一个对象是否被来及回收器回收成功。

image.png

核心流程

  • 监控列表 、 保留列表

image.png

  • 弱引用的流转过程

image.png

源码

  • ActivityWatcher

image.png

  • FragmentAndViewModelWatcher
  • ViewModelClearedWatcher
  • ServiceWatcher

常见内存泄漏场景

静态实例持有

单例持有上下文

因为单例的静态特性,如果他应用了Activity 的上下文,那么在 Activity 生命周期结束后,activity 销毁,但是这个引用不能被GC 回收,导致内存泄漏

image.png

静态变量持有上下文

和单例原理一样

非静态内部类

自定义handler 非静态内部类

  • 内部类不管匿名还是有名,一定持有外部类的引用;

  • 定义一个 handler 匿名的内部类, 当时Activity销毁之后,handler 中还有消息的存在,那么 Message 持有Handler , handler 持有外部类 Activity , 造成内存泄露;

  • 并且会有 activity is running 的异常抛出的可能;

image.png

改写方法
  • 写成静态类的方式,
  • 持有Activity 的弱引用

image.png

  • 在Activity 生命周期结束之后, 全部移除消息队列中的消息;

image.png

未取消注册或毁掉导致

  • 广播 ,在Activity 销毁时要注销监听,因为广播注册持有Activity ,原理非静态内部类持有外部类引用,
  • 观察者模式的注册 ,在销毁时也要注销
  • Retrofit+RxJava 注册的网络请求毁掉,同样是非静态内部类持有外部类,在销毁时也要注销

Timer和TimerTask

image.png

集合中的对象未清理

  • 集合中对象,当不再使用的时候,应该从集合中溢出,
  • 如果集合被静态引用的话, 集合里面那些没有用的对象更会造成内存泄漏, 所以在使用集合时要及时将不用的对象从集合中溢出。

资源未关闭或未释放

使用IO、File 或者Sqlite、Cursor 等资源时要及时关闭, 这些资源在进行读写操作时通常都使用了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用而得不到释放,

因此我们在不需要使用他们的时候及时关闭,从缓冲期及时能释放,

属性动画

属性动画是一个定时任务,在销毁时没有关闭动画,那么多动画会不断的播放下去,动画引用的控件, 控件引用Activity,所以造成Activity无法释放, 内存泄露;

webview

  • 因为 webview 在加载网页之后会长期占用内存而不能释放, 因为我们在Activity 销毁后要调用它的 destory 方法来销毁它释放内存。
  • 另外 webview 中的 callback 持有Activity ,及时调用destory 也无法解决,
  • 最终的方法从中移除掉 callback 之后在销毁 webview。