AlertDialog引起的内存泄露

一. 追踪Dialog泄露根源
在我们使用AlertDialog时,标准的写法如下

AlertDialog.Builder builder = new AlertDialog.Builder(this)
        .setPositiveButton("confirm", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                login();
            }
        });
builder.create().show();

但这样子的写法是存在内存泄露的,具体分析如下:
1.以上代码在非静态内部类OnClickListener方法中引用着Activity(activity.this.login()) OnClickListener→ Activiity

public void setButton(int whichButton, CharSequence text,
        DialogInterface.OnClickListener listener, Message msg) {

    if (msg == null && listener != null) {
        msg = mHandler.obtainMessage(whichButton, listener);
    }

    switch (whichButton) {

        case DialogInterface.BUTTON_POSITIVE:
            mButtonPositiveText = text;
            mButtonPositiveMessage = msg;
            break;

        case DialogInterface.BUTTON_NEGATIVE:
            mButtonNegativeText = text;
            mButtonNegativeMessage = msg;
            break;

        case DialogInterface.BUTTON_NEUTRAL:
            mButtonNeutralText = text;
            mButtonNeutralMessage = msg;
            break;

        default:
            throw new IllegalArgumentException("Button does not exist");
    }
}

2.从以上源码中可以看到,在AlertDialog构建过程中传入的参数 int whichButton, OnClickListener listener都包装成了msg来处理,这样子就造成了msg对listener的引用 msg→ OnClickListener

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

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

        // Post a message so we dismiss after the above handlers are executed
        mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
                .sendToTarget();
    }
};

3.以上源码中,在处理事件响应时,Dialog从消息队列中再次obtain一个Message实例,复制给m进行发送,Message m也会在Dialog销毁时跟着销毁,并没有发现产生内存泄露的时机.

4.那什么情况下会产生内存泄露呢?
(1)Message是任何线程共用的,HandlerThread中,Looper会不停的从阻塞队列MessageQueue中取Message进行处理.当没有可消费Message对象时,就会开始阻塞,而此时最后一个被取出的Message就会被本地变量引用,一直不会释放引用,除非有新的message
(2)Dialog从消息队列中可能会恰巧取到一个“仍然被某个阻塞中的HandlerThread本地变量引用的Message实例”,代码msg = mHandler.obtainMessage(whichButton, listener),把listener赋给Message的obj,并一直保存在Dialog实例中
如此产生引用: Thread → Mesage → Listener → Dialog → Activity. 当Activity关闭时,Thread仍然引用着Activity, 这样内存泄漏就发生了.

二. 解决方法

public class DetachClickListener implements DialogInterface.OnClickListener {

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

    private DialogInterface.OnClickListener mDelegate;

    private DetachClickListener(DialogInterface.OnClickListener delegate) {
        this.mDelegate = delegate;
    }

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

    public void clearOnDetach(Dialog dialog) {
        dialog.getWindow()
                .getDecorView()
                .getViewTreeObserver()
                .addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
                    @Override
                    public void onWindowAttached() {
                    }

                    @Override
                    public void onWindowDetached() {
                        mDelegate = null;
                    }
                });
    }

}


DetachClickListener clickListener = DetachClickListener.wrap(new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {

    }
});

AlertDialog dialog = new AlertDialog.Builder(context)
        .setPositiveButton("confirm", clickListener)
        .create();
dialog.show();
clickListener.clearOnDetach(dialog);

以上写法在Dialog退出后,清除了对DialogInterface.OnClickListener的引用,在中间层截断, 故在Activity关闭时避免了内存泄露.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值