AlertDialog的构造方法一共有4种,分别是:
protected AlertDialog(Context context)
创建一个默认配置的AlertDialog对象
protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener)
创建一个AlertDialog对象并设置是否可取消和取消监听
protected AlertDialog(Context context, @StyleRes int themeResId)
创建一个AlertDialog对象并设置主题
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper)
创建一个AlertDialog对象并设置主题和是否创建主题包装
我们通常却很少这样写,我们通常使用Builer创建:
AlertDialog alertDialog=new AlertDialog.Builder(this).create();
我们使用 setNegativeButton,setButton,setView来为AlertDialog设置样式。
如果我们在AlertDialog上设置的话,方法是这样的:
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
private AlertController mAlert;
都是在给AlertController对象赋值
如果使用Builer来设置,那么他们的方法时这样的:
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
return this;
}
public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}
public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
return this;
}
我们可以看到,无论设置什么,都是在给P的字段赋值,P是什么呢?
private final AlertController.AlertParams P;
P是一个AlertController.AlertParams对象,装载着对话框的各种设置,它有以下字段
public static class AlertParams {
public final Context mContext;
public final LayoutInflater mInflater;
public int mIconId = 0;
public Drawable mIcon;
public int mIconAttrId = 0;
public CharSequence mTitle;
public View mCustomTitleView;
public CharSequence mMessage;
public CharSequence mPositiveButtonText;
public DialogInterface.OnClickListener mPositiveButtonListener;
public CharSequence mNegativeButtonText;
public DialogInterface.OnClickListener mNegativeButtonListener;
public CharSequence mNeutralButtonText;
public DialogInterface.OnClickListener mNeutralButtonListener;
public boolean mCancelable;
public DialogInterface.OnCancelListener mOnCancelListener;
public DialogInterface.OnDismissListener mOnDismissListener;
public DialogInterface.OnKeyListener mOnKeyListener;
public CharSequence[] mItems;
public ListAdapter mAdapter;
public DialogInterface.OnClickListener mOnClickListener;
public int mViewLayoutResId;
public View mView;
public int mViewSpacingLeft;
public int mViewSpacingTop;
public int mViewSpacingRight;
public int mViewSpacingBottom;
public boolean mViewSpacingSpecified = false;
public boolean[] mCheckedItems;
public boolean mIsMultiChoice;
public boolean mIsSingleChoice;
public int mCheckedItem = -1;
public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
public Cursor mCursor;
public String mLabelColumn;
public String mIsCheckedColumn;
public boolean mForceInverseBackground;
public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
public OnPrepareListViewListener mOnPrepareListViewListener;
public boolean mRecycleOnMeasure = true;
在使用Builder设置完后必须调用creat方法来创建AlertDialog对象
/**
* Creates an {@link AlertDialog} with the arguments supplied to this
* builder.
* <p>
* Calling this method does not display the dialog. If no additional
* processing is needed, {@link #show()} may be called instead to both
* create and display the dialog.
*/
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
if (mOnKeyListener != null) {
dialog.setOnKeyListener(mOnKeyListener);
}
*/
}
可以看到两种方法都是一样的,最后都是在给AlertController对象赋值
调用AlertDialog的show方法其实也会调用creat方法
/**
* Creates an {@link AlertDialog} with the arguments supplied to this
* builder and immediately displays the dialog.
* <p>
* Calling this method is functionally identical to:
* <pre>
* AlertDialog dialog = builder.create();
* dialog.show();
* </pre>
*/
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
我们再来看看setButton方法,它有4种不同参数的方法,不过有两种被弃用了
/**
* Set a message to be sent when a button is pressed.
*
* @param whichButton Which button to set the message for, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
* {@link DialogInterface#BUTTON_NEGATIVE}, or
* {@link DialogInterface#BUTTON_NEUTRAL}
* @param text The text to display in positive button.
* @param msg The {@link Message} to be sent when clicked.
*/
public void setButton(int whichButton, CharSequence text, Message msg) {
mAlert.setButton(whichButton, text, null, msg);
}
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
*
* @param whichButton Which button to set the listener on, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
* {@link DialogInterface#BUTTON_NEGATIVE}, or
* {@link DialogInterface#BUTTON_NEUTRAL}
* @param text The text to display in positive button.
* @param listener The {@link DialogInterface.OnClickListener} to use.
*/
public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
mAlert.setButton(whichButton, text, listener, null);
}
可能我们用的多是第二种方法,第一种很少见,但其实第一种可以说是 第二种方法的内部实现,看代码
/**
* Sets a click listener or a message to be sent when the button is clicked.
* You only need to pass one of {@code listener} or {@code msg}.
*
* @param whichButton Which button, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
* {@link DialogInterface#BUTTON_NEGATIVE}, or
* {@link DialogInterface#BUTTON_NEUTRAL}
* @param text The text to display in positive button.
* @param listener The {@link DialogInterface.OnClickListener} to use.
* @param msg The {@link Message} to be sent when clicked.
*/
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");
}
}
如果设置了监听,监听就会被封装到message中,最终都会被赋值给对应按钮的message中,如果视图被点击就会取对应的信息,使用handler发送:
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();
}
};
如果是设置的监听,会使用AlertController中的mHandler发送,它在AlertController的构造方法中被初始化,也就是说调用了creat方法或者show方法,mHandler都会被初始化
protected AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
final TypedArray a = context.obtainStyledAttributes(null,
R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
mButtonPanelSideLayout = a.getResourceId(
R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(
R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
mMultiChoiceItemLayout = a.getResourceId(
R.styleable.AlertDialog_multiChoiceItemLayout,
R.layout.select_dialog_multichoice);
mSingleChoiceItemLayout = a.getResourceId(
R.styleable.AlertDialog_singleChoiceItemLayout,
R.layout.select_dialog_singlechoice);
mListItemLayout = a.getResourceId(
R.styleable.AlertDialog_listItemLayout,
R.layout.select_dialog_item);
mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
a.recycle();
/* We use a custom title so never request a window title */
window.requestFeature(Window.FEATURE_NO_TITLE);
}
如果是设置的Message,那么就会使用Message对象的target Handler
/*package*/ Handler target;
它可以使用public void setTarget(Handler target)来赋值
看个例子:
AlertDialog alertDialog=new AlertDialog.Builder(this).create();
Message message=new Message();
message.setTarget(new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
showToast("哈哈");
}
});
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE,"显示",message);
alertDialog.show();
所以明白了吧,AlertDialog的按钮监听其实是使用handler实现的,那么就会有内存泄漏问题
在AlertDialog构建过程中传入的参数 int whichButton, OnClickListener listener都包装成了msg来处理,这样子就造成了msg对listener的引用 msg→ OnClickListener
有两种情况可能会发生内存泄漏:
(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关闭时避免了内存泄露.
参考网址:https://blog.csdn.net/awangyunke/article/details/78933854