使用Dialog可能引起的内存泄漏

Excluded by rule matching field android.os.Message#obj because Prior to ART , a thread waiting on a blocking queue will leak the last dequeued object as a stack local reference .

So when a HandlerThread becomes idle, it keeps a local reference to the last message it received. that message then gets recycled and can be used again. as long as all message are recycled after beingused, this won’t be a problem , because these references are cleared when beingrecycled. However, dialogs create when amessage needs to be sent.

These Message templates holds references to the dialoglisteners , which most likely leads to holding a reference onto the activity in someway.Dialogs never recycle their template Message, assuming these Message instances will get GCed when the dialog is GCed. the combination of the these two things creates a hingh potential for memory leaks as soon as you use dialogs. these memory leaks might be temporary, but some handler threads sleep for a long time.

To fix this, you could post empty messages to the idle handler threads from time to time. this won’t be easy because you cannot access all handler threads, but a librarythat is widely used should consider doing this for its own handler threads.

排除规则匹配字段android.os.Message#obj因为在ART之前,等待阻塞队列的线程将泄漏最后一个出队对象作为堆栈本地引用。

所以当HandlerThread变得空闲时,它会保留对它接收到的最后一个消息的本地引用。该消息然后被再次使用,可以再次使用。只要所有消息在被使用后被回收,这不会是一个问题,因为这些引用在被重新循环时被清除。但是,当需要发送消息时会创建对话框。

这些Message模板保存对dialoglisteners的引用,这最有可能导致在某个操作中保留对该活动的引用.Dialogs从不回收其模板Message,假设当对话框为GCed时,这些Message实例将被GCed。一旦你使用对话框,这两个东西的组合就会产生一个内存泄漏的潜力。这些内存泄漏可能是暂时的,但是一些处理程序线程可以长时间睡眠。

要解决这个问题,您可以不时向空闲的处理程序线程发送空消息。这不容易,因为您无法访问所有处理程序线程,但是一个被广泛使用的库应该考虑为自己的处理程序线程执行此操作。 

 

-------------------------------------------------------------------------------------------------------------------------------

泄漏过程

首先,Message的实例分2种,直接new Message() 或者Message.obtain(),区别在于后者是通过复用来提升效率。

然后,Handler会持有最后进栈的Message实例,因为Message实例不能及时清除(只能等待下一次复用),又因为Message持有Dialog的引用(可能是Dialog的Interface或Dialog本身),导致Handler也持有Dialog的引用。

其次,Handler虽然持有的是Dialog的弱引用,但是Dialog本身可能会持有外部强引用(比如Dialog的Context,View,Interface里包含外部强引用),因此Handler对Dialog的弱引用失效,最终Handler一直持有Dialog的引用。

最后,因为Dialog不能被GC导致它持有的引用对象也不能被GC,因此造成了内存泄漏。

源码分析

1.AlertDialog创建时,如果有Interface,那么就会把信息复用到Message

if (this.mPositiveButtonText != null || this.mPositiveButtonIcon != null) {
    dialog.setButton(-1, this.mPositiveButtonText, this.mPositiveButtonListener, (Message)null, this.mPositiveButtonIcon);
}

if (this.mNegativeButtonText != null || this.mNegativeButtonIcon != null) {
    dialog.setButton(-2, this.mNegativeButtonText, this.mNegativeButtonListener, (Message)null, this.mNegativeButtonIcon);
}

if (this.mNeutralButtonText != null || this.mNeutralButtonIcon != null) {
    dialog.setButton(-3, this.mNeutralButtonText, this.mNeutralButtonListener, (Message)null, this.mNeutralButtonIcon);
}
public void setButton(int whichButton, CharSequence text, android.content.DialogInterface.OnClickListener listener, Message msg, Drawable icon) {
        if (msg == null && listener != null) {
            msg = this.mHandler.obtainMessage(whichButton, listener);
        }

        switch(whichButton) {
        case -3:
            this.mButtonNeutralText = text;
            this.mButtonNeutralMessage = msg;
            this.mButtonNeutralIcon = icon;
            break;
        case -2:
            this.mButtonNegativeText = text;
            this.mButtonNegativeMessage = msg;
            this.mButtonNegativeIcon = icon;
            break;
        case -1:
            this.mButtonPositiveText = text;
            this.mButtonPositiveMessage = msg;
            this.mButtonPositiveIcon = icon;
            break;
        default:
            throw new IllegalArgumentException("Button does not exist");
        }

    }

2.点击AlertDialog时,先发送按钮的Message,再发送关闭对话框的Message

    private final OnClickListener mButtonHandler = new OnClickListener() {
        public void onClick(View v) {
            Message m;
            if (v == AlertController.this.mButtonPositive && AlertController.this.mButtonPositiveMessage != null) {
                m = Message.obtain(AlertController.this.mButtonPositiveMessage);
            } else if (v == AlertController.this.mButtonNegative && AlertController.this.mButtonNegativeMessage != null) {
                m = Message.obtain(AlertController.this.mButtonNegativeMessage);
            } else if (v == AlertController.this.mButtonNeutral && AlertController.this.mButtonNeutralMessage != null) {
                m = Message.obtain(AlertController.this.mButtonNeutralMessage);
            } else {
                m = null;
            }

            if (m != null) {
                m.sendToTarget();
            }

            AlertController.this.mHandler.obtainMessage(1, AlertController.this.mDialog).sendToTarget();
        }
    };

3.AlertDialog的Handler的处理

    private static final class ButtonHandler extends Handler {
        private static final int MSG_DISMISS_DIALOG = 1;
        private WeakReference<DialogInterface> mDialog;

        public ButtonHandler(DialogInterface dialog) {
            this.mDialog = new WeakReference(dialog);
        }

        public void handleMessage(Message msg) {
            switch(msg.what) {
            case -3:
            case -2:
            case -1:
                ((android.content.DialogInterface.OnClickListener)msg.obj).onClick((DialogInterface)this.mDialog.get(), msg.what);
            case 0:
            default:
                break;
            case 1:
                ((DialogInterface)msg.obj).dismiss();
            }

        }
    }

如果AlertDialog没有设置OnCancelListener或者OnDismissListener,那么ButtonHandler将持有最后一个进栈的Message。

4.设置OnCancelListener或者OnDismissListener

    public void setOnCancelListener(@Nullable OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }

    public void setOnDismissListener(@Nullable OnDismissListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnDismissListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
        } else {
            mDismissMessage = null;
        }
    }

5.Dialog的Handler的处理

    private static final class ListenersHandler extends Handler {
        private final WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

如果设置OnCancelListener或者OnDismissListener,那么ListenersHandler将持有最后一个进栈的Message。

问题解决

内存泄漏是由于Dialog持有外部强引用(比如Dialog的Context、View、Interface),导致Handler不能释放Dialog,最终导致Dialog持有的对象也不能被释放,造成了泄漏。

那么从这三点入手应该就能解决泄漏问题。

1.关于Context

Dialog创建时,mContext = new ContextThemeWrapper (context, themeResId),传入的context必须含有Theme。

我的理解是Dialog持有的Context已经从外部强引用Activity中取消了关联,它在内部创建了新的Context并持有它。

2.关于View

Dialog中的视图View都是由Window去创建的。Window有自己的管理办法去销毁View,因此不会造成泄漏。

3.关于Interface

很大的可能性是由于Interface造成了泄漏,比如DialogInterface.OnClickListener它持有了外部强引用对象。

解决Interface泄漏

1.Interface持有弱引用对象,比如WeakReference<BaseActivity> activityWeakReference

2.在Dialog退出后清除对Interface的引用

自定义包裹类

public final class DetachableClickListener implements DialogInterface.OnClickListener {

  public static DetachableClickListener wrap(DialogInterface.OnClickListener delegate) {
    return new DetachableClickListener(delegate);
  }

  private DialogInterface.OnClickListener delegateOrNull;

  private DetachableClickListener(DialogInterface.OnClickListener delegate) {
    this.delegateOrNull = delegate;
  }

  @Override public void onClick(DialogInterface dialog, int which) {
    if (delegateOrNull != null) {
      delegateOrNull.onClick(dialog, which);
    }
  }

  public void clearOnDetach(Dialog dialog) {
    dialog.getWindow()
        .getDecorView()
        .getViewTreeObserver()
        .addOnWindowAttachListener(new OnWindowAttachListener() {
          @Override public void onWindowAttached() { }
          @Override public void onWindowDetached() {
            delegateOrNull = null;
          }
        });
  }
}

使用方法

DetachableClickListener clickListener = wrap(new DialogInterface.OnClickListener() {
  @Override public void onClick(DialogInterface dialog, int which) {
    MyActivity.this.abc();
  }
});

AlertDialog dialog = new AlertDialog.Builder(this)
    .setPositiveButton("确定", clickListener)
    .create();
clickListener.clearOnDetach(dialog);
dialog.show();

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值