Piasy · 更新于 2018-11-28 11:00:42

Memory leak专题

  • 神器:LeakCanary,memory leak检测工具;

Hanlder、Runnable、Thread的非静态内部类、匿名类,都会持有外部类的强引用,都可能造成内存泄漏;

  • Java的非静态内部类、匿名类,会持有外部类的强引用,静态的不会持有;对于Activity/Fragment内定义的Handler/Runnable,是最容易因此导致内存泄漏的,因为它们可能会postDelayed,从而导致Activity/Fragment及其内部的资源无法GC;推荐做法是定义静态内部类/静态匿名成员,访问外部类的成员和方法通过WeakRefrence实现;
  • WeakRefrence需要在内部类内创建才符合其语义?

Prior to Android Lollipop, alert dialogs may cause memory leaks in your Android apps.

  • 考虑以下代码
    while (true) {
      MyMessage msg = queue.take(); // might block
      System.out.println("Received: " + msg);
    }

    msg对象是栈上的局部变量,每次循环都将会重写,一旦被重写,上一次循环的msg引用指向的对象将不再被其引用;但是在Dalvik虚拟机的实现中,如果queue.take()阻塞了,那么本次循环的msg未被赋值,则上次的msg的引用将不会被清除,

  • HandlerThread
    for (;;) {
      Message msg = queue.next(); // might block
      if (msg == null) {
        return;
      }
      msg.target.dispatchMessage(msg);
      msg.recycleUnchecked();
    }

    msg每次循环的后面都被recycle了(清空了内容),所以泄漏的仅仅是一个空的msg对象,影响不大(LeakCanary将默认忽略Message对象的泄漏)。

  • 遇上AlertDialog
    new AlertDialog.Builder(this)
      .setPositiveButton("Baguette", new DialogInterface.OnClickListener() {
        @Override public void onClick(DialogInterface dialog, int which) {
          MyActivity.this.makeBread();
        }
      })
      .show();

    DialogInterface.OnClickListener的匿名实现类持有了MainActivity的强引用;而在AlertDialog的实现中,OnClickListener类将被包装在一个Message对象中,而且这个Message会在其内部被复制一份,两份Message中只有一个被recycle,另一个(OnClickListener的成员变量引用的Message对象)将会leak!

  • 解决办法
    • ART VM(>=5.0),JVM不存在此问题
    • Message对象的泄漏无法避免,但是如果仅仅是一个空的Message对象,而且将被放入对象池作为后用,是没有问题的
    • DialogInterface.OnClickListener对象不持有外部类的强引用:static类实现;DetachableClickListener(监听窗口解除事件,手动释放引用);
    • 当worker thread空闲后,向HandlerThread发送一个空的消息,解除上一个Message的泄漏