AlertDialog源码浅析
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 cursorif ((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() {@Overridepublic 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 executedmHandler.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(){@Overridepublic 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;}@Overridepublic 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() {@Overridepublic void onWindowAttached() {}@Overridepublic void onWindowDetached() {mDelegate = null;}});}}DetachClickListener clickListener = DetachClickListener.wrap(new DialogInterface.OnClickListener() {@Overridepublic 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
AlertDialog源码浅析相关推荐
- Android开发之Theme、Style探索及源码浅析
1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...
- hashmap允许null键和值吗_hashMap底层源码浅析
来源:https://blog.csdn.net/qq_35824590/article/details/111769203 hashmap是我们经常使用的一个工具类.那么知道它的一些原理和特性吗? ...
- Android Loader机制全面详解及源码浅析
原文出处:csdn@工匠若水,http://blog.csdn.net/yanbober/article/details/48861457 一.概述 在Android中任何耗时的操作都不能放在UI主线 ...
- 内核启动流程分析(四)源码浅析
目录 kernel(四)源码浅析 建立工程 启动简析 head.s 入口点 查询处理器 查询机器ID 启动MMU 其他操作 start_kernel 处理命令行 分区 kernel(四)源码浅析 建立 ...
- harbor登录验证_Harbor 源码浅析
Harbor 源码浅析www.qikqiak.com Harbor 是一个CNCF基金会托管的开源的可信的云原生docker registry项目,可以用于存储.签名.扫描镜像内容,Harbor 通 ...
- fetch first mysql_MySQL多版本并发控制机制(MVCC)源码浅析
MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<>诚然讲的非常透彻,但只能提纲挈领,不能让 ...
- 【flink】Flink 1.12.2 源码浅析 : Task数据输入
1.概述 转载:Flink 1.12.2 源码浅析 : Task数据输入 在 Task 中,InputGate 是对输入的封装,InputGate 是和 JobGraph 中 JobEdge 一一对应 ...
- 【flink】Flink 1.12.2 源码浅析 :Task数据输出
1.概述 转载:Flink 1.12.2 源码浅析 :Task数据输出 Stream的计算模型采用的是PUSH模式, 上游主动向下游推送数据, 上下游之间采用生产者-消费者模式, 下游收到数据触发计算 ...
- 【flink】Flink 1.12.2 源码浅析 : StreamTask 浅析
1.概述 转载:Flink 1.12.2 源码浅析 : StreamTask 浅析 在Task类的doRun方法中, 首先会构建一个运行环境变量RuntimeEnvironment . 然后会调用lo ...
最新文章
- 6个快速优化回归测试套件的方法,你都知道吗?
- 拖拽插入Table的列(I.E. ONLY)
- DevExpress 使用 XtraTabbedMdiManager 控件以 Tab样式加载 Mdi窗体并合并 RibbonControl 解决方案
- Spring全局异常处理
- JAVA代码实现多级树结构封装对象
- 为什么说一次一密加是密抗窃听无条件安全的?
- SQL Server数据文件迁移
- android call require api level
- 软件架构设计案例_透过现象看本质:常见的前端架构风格和案例
- oracle的in集合,oracle中in与not in集合中有空值问题
- socket和http间的区别
- mysql8.0卡cpu_MySQL 8.0资源组有效解决慢SQL引发CPU告警
- Android SDK Manager 中如果没有相应的镜像ARM XX Image
- atitit.web原理 理论attilax总结
- linux appium 安装教程,Ubuntu 系统安装 Appium 及样例运行教程
- matlab两矩阵相似性,两个矩阵同时相似对角化MATLAB程序.docx
- python 实现软件激活码验证
- H5标签input标签上传文件(一)
- ztree树与列表名字获取
- 小米机顶盒显示网络无法连接服务器,小米盒子无线网络连接不上怎么回事 - 卡饭网...
热门文章
- 中小企业如何差异化“生意表达”,成为最了不起的小企业?
- 软件测试--基础知识1--测试简介、软件质量等
- 计算机更新失败变的很卡,win 8.1 运行慢,更新kb2919355失败频繁重启等问题
- Portal技术介绍
- css阴影设置透明度,css3圆角 阴影 透明度
- Ubuntu学习(六)Linux安装压缩包版的软件
- Android 使用自带的MediaCodec 框架进行本地视频压缩
- php学生成绩管理系统,数据库使用MySQL,包括源代码和数据库SQL文件,具有学生和教师登录管理功能
- 技术人如何通过了解业务,获取晋升机会
- 谷粒商城项目笔记之分布式基础(三)