在咱们使用AlertDialog时,标准的写法以下:bash
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setPositiveButton("confirm", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
login();
}
});
builder.create().show();
复制代码
但这样子的写法是存在内存泄露的,具体分析以下:ide
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");
}
}
复制代码
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();
}
};
复制代码
三、以上源码中,在处理事件响应时,Dialog从消息队列中再次obtain一个Message实例,复制给m进行发送,Message m也会在Dialog销毁时跟着销毁,并无发现产生内存泄露的时机.oop
四、那什么状况下会产生内存泄露呢?ui
(1)Message是任何线程共用的,HandlerThread中,Looper会不停的从阻塞队列MessageQueue中取Message进行处理.当没有可消费Message对象时,就会开始阻塞,而此时最后一个被取出的Message就会被本地变量引用,一直不会释放引用,除非有新的messagethis
(2)Dialog从消息队列中可能会恰巧取到一个“仍然被某个阻塞中的HandlerThread本地变量引用的Message实例”,代码msg = mHandler.obtainMessage(whichButton, listener),把listener赋给Message的obj,并一直保存在Dialog实例中 如此产生引用: Thread → Mesage → Listener → Dialog → Activity. 当Activity关闭时,Thread仍然引用着Activity, 这样内存泄漏就发生了.spa
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) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
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 alertDialog = new AlertDialog.Builder(this)
.setPositiveButton("confirm", clickListener).create();
alertDialog.show();
// 在适当的时机调用该方法,防止内存泄漏
clickListener.clearOnDetach(alertDialog);
复制代码
以上写法在Dialog退出后,清除了对DialogInterface.OnClickListener的引用,在中间层截断, 故在Activity关闭时避免了内存泄露.线程