一切从Android的Handler讲起(八):Handler的内存泄露

一切从Android的Handler讲起(八):Handler的内存泄露

  前面肥柴从浅入深,以Handler的基本工作机制为导入,进一步解析Handler机制的内部底层原理、Android触摸事件原理以及Android Framework层对消息机制的应用。这一篇章作为Handler的最后一个篇章,我们依旧从Handler入手,来谈谈内存泄漏的那些事。

一、内存泄露

  内存泄漏是一个老生常谈的问题,也是面试容易问到的问题,那到底什么是内存泄漏呢?

  内存泄漏是指动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束

一切从Android的Handler讲起(八):Handler的内存泄露_第1张图片

  java的优势之一就是内置了垃圾回收器GC,它帮助我们实现了自动化内存管理。但是GC再好,也有老马失前蹄的时候,它不能保证提供一个解决内存泄漏的万无一失的解决方案。而内存泄漏通俗的讲就是一部分内存空间明明已经使用了,却没有引用指向这部分空间,造成这片已经使用的空间无法处理的情况。

二、Handler的内存泄露

  当我们尝试写一个Handler的匿名对象的时候,Android Studio会有如下报错,提示我们此写法存在内存泄漏。

一切从Android的Handler讲起(八):Handler的内存泄露_第2张图片

  那么为何Android Studio会有这种提示?

一切从Android的Handler讲起(八):Handler的内存泄露_第3张图片

  答案很简单:因为匿名内部类默认会持有外部类Activity的引用,这样当Activity被销毁时,由于被匿名handler对象所持有而不能被释放,Activity所占用的内存就会泄露

  这是我们都知道的答案,那么问题又来了,为啥匿名内部类会持有外部类引用?

一切从Android的Handler讲起(八):Handler的内存泄露_第4张图片

  这就得从匿名内部类的java编译后的.class文件说起,下面是肥柴把上面的匿名内部Handler类编译后的kotlin字节码代码。我们重点看注释1的地方,可以发现编译器为匿名内部类也单独生成了一份.class文件,而且其类名为Outer$1,并为其构造函数添加了一个参数,这个参数就是Outer类的实例,这就是为什么说匿名内部类默认会持有外部类的引用

// ================com/happyfatdoge/learningdemo/MainActivity$handler$1.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/happyfatdoge/learningdemo/MainActivity$handler$1 extends android/os/Handler {

    /** 注释1 外部Activity引用变量 */
  OUTERCLASS com/happyfatdoge/learningdemo/MainActivity <init> ()V

  // access flags 0x1
  public handleMessage(Landroid/os/Message;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "msg"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 20 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/happyfatdoge/learningdemo/MainActivity$handler$1; L0 L2 0
    LOCALVARIABLE msg Landroid/os/Message; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x0
  <init>()V
   L0
    LINENUMBER 17 L0
    ALOAD 0
   L1
    LINENUMBER 17 L1
    INVOKESPECIAL android/os/Handler.<init> ()V
    RETURN
   L2
    LOCALVARIABLE this Lcom/happyfatdoge/learningdemo/MainActivity$handler$1; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  @Lkotlin/Metadata;(mv={1, 5, 1}, k=1, d1={"\u0000\u0017\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000*\u0001\u0000\u0008\n\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0016\u00a8\u0006\u0006"}, d2={"com/happyfatdoge/learningdemo/MainActivity$handler$1", "Landroid/os/Handler;", "handleMessage", "", "msg", "Landroid/os/Message;", "LearningDemo.app.main"})
  // access flags 0x19
  public final static INNERCLASS com/happyfatdoge/learningdemo/MainActivity$handler$1 null null
  // compiled from: MainActivity.kt
}

  同理,非静态内部类也是默认持有了外部类引用,而容易造成内存泄漏

一切从Android的Handler讲起(八):Handler的内存泄露_第5张图片

  现在我们回到正题,因为Handler这个匿名内部类持有外部Activity的引用,导致Activity销毁时无法释放其内存,那为何Activity被引用了就无法释放Activity的内存呢?

  这里就涉及到了JVM的垃圾回收机制:如果GC Root到这个对象是引用链可达的话,那么此时就不能被GC垃圾回收掉此对象的内存

  而JVM内能充当GC Root对象的可以分为几种:1). 虚拟机栈/本地方法栈中JNI中的引用的对象。2). System Class Loader/Boot Class Loader加载的类对象。3). 激活状态的Thread 线程。4). 方法区中的常量引用的对象。5). 方法区里的类静态属性所引用的对象,等等。

  肥柴利用内存泄漏工具分析Handler泄漏的真实引用链:Activity -> handler -> message -> queue -> UI线程。用最容易理解的一句话来回答为何会导致无法释放Activity的内存:由于激活状态的Thread线程是GC Root对象,此处对应了UI线程,那么由于UI线程一直处于激活状态,导致了这条引用链上的所有对象都不能被GC内存回收掉

一切从Android的Handler讲起(八):Handler的内存泄露_第6张图片

  那根据这个理论,如果我们现在的handler关联的looper是子线程而非UI线程的话,因为随着子线程运行完毕,子线程的Looper和MessageQueue、Handler对象也随之消亡,这条引用链也就断裂了,Activity销毁后就可以被GC回收掉!

三、Handler内存泄漏的处理

  既然我们知道了内存泄漏的真实原因,如何处理Handler引发的内存泄漏呢?

  最常见的答案便是:将Handler声明为静态内部类,同时为了能够使用到Activity的引用,可以使用弱引用处理Activity引用,避免GC无法释放Activity

  那有更简单更好的方法吗?

一切从Android的Handler讲起(八):Handler的内存泄露_第7张图片

  其实是有的,这是肥柴也是很多读者最容易忽视的方法,同时也是最简单的方法:就是在Activity onDestroy()时调用handler.removeCallbacksAndMessages(null),这样就把Queue里所有的Message都remove掉了,之前说过Message被Message pool回收掉会reset,因此不会再引用Handler,这条引用链就断掉了

一切从Android的Handler讲起(八):Handler的内存泄露_第8张图片

 - - - - - Handler的内存泄露完 - - - - -

结束

  至此关于Handler的相关内容也就结束了,后续如有补充在继续扩展本篇章。

  一、一切从Android的Handler讲起(一):Handler工作机制

  二、一切从Android的Handler讲起(二):Message

  三、一切从Android的Handler讲起(三):Looper的唯一性——ThreadLocal

  四、一切从Android的Handler讲起(四):Looper消息获取

  五、一切从Android的Handler讲起(五):延迟消息实现原理与消息机制的基本原理

  六、一切从Android的Handler讲起(六):Android触摸事件基本原理

  七、一切从Android的Handler讲起(七):Handler在Android系统框架层的应用

  八、一切从Android的Handler讲起(八):Handler的内存泄露

  篇章系列参考资料:https://www.zhihu.com/people/jing-shen-ling-xiu-68-79/posts

一切从Android的Handler讲起(八):Handler的内存泄露_第9张图片

 - - - - - 一切从Android的Handler讲起篇章完 - - - - -

你可能感兴趣的:(android,Handler内存泄漏)