探索AlertDialog.getButton为null的问题

来源:互联网 发布:网络直播 经济效益 编辑:程序博客网 时间:2024/06/17 12:47

今日科技快讯

昨天我的个人域名guolin.tech经历了长达数个小时无法访问的情况,不光是我的博客使用的是这个域名,《第二行代码》中的天气预报接口使用的也是这个域名,很多朋友都到公众号后台问我怎么回事,我也是忙得不可开交。其实原因就是7月1号之后的不知什么新政,对域名备案情况又进行了一波严查,而我这个域名是没有备案的,于是惨遭了强制关闭。但问题在于并不是我不想备案,而是工信部还不给.tech域名备案,既不给备案,不备案还不准上线,真是让人哭笑不得。

而我使用的guolin.tech这个域名已经印在《第二行代码》几万本发行的书上了,因此现在让我再换个能备案的域名显然是不切实际的。最终我选择的解决方案是将主机移到大陆以外的地区,这样就不需要备案了。于是我昨天又在阿里云上买了一台香港的主机,重新部署服务器程序和数据库,并将域名进行了重新解析和定向,经过几个小时抢修之后,现在guolin.tech域名又恢复正常访问了。为了保证所有《第二行代码》的读者都能长期稳定地使用书中的接口服务,我也是操碎了心呀。

作者简介

本篇来自 yannecer 的投稿,通过源码分析了在 AlertDialog 调用 show 之前 getButton 为 null 的问题,希望对大家有所帮助。

yannecer 的博客地址:

http://blog.csdn.net/y12345654321

正文

我们在使用 AlertDialog 的时候,如果想改变 POSITIVE_BUTTON 或者 NEGATIVE_BUTTON 的字体颜色、大小时,可能会注意到 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 这个方法,但是当我们在创建了AlertDialog,想要拿到这 Button 的时候会发现返回的结果是个 null

尝试之后会发现,在 AlertDialog 的 show() 执行过之后,这个 Button 返回的就不再是 null 了,此时就可以对 Button 的属性进行设置:

或者在 onShow(DialogInterface dialog) 的回调中设置:

接下来我们就看一下为什么 AlertDialog.Builder.create() 已经构建了一个 AlertDialog 对象,再去 getButton(int which) 会是 null

有了 AlertDialog 实体而 getButton(int which) 为 null,那我们就从 getButton(int which) 入手看一下源码的处理。点进去 getButton(int which) 我们会发现返回的是 AlertController 类中的成员变量 Button mButtonPositive:

AlertController,这个类几乎包含了 AlertDialog 所有的属性。返回的 button 是 null,说明这个 button 还没有初始化,我们来看一下这个 button 是在哪里被初始化的:

button 在 AlertController类 的 setupButtons(ViewGroup buttonPanel)方法 中被初始化,通过追踪,setupButtons(ViewGroup buttonPanel)方法 在 setupView() 中被执行,setupView() 在 installContent() 中被执行:

这个 installContent()方法 是公有的且在 AlertController 中没有被执行,那必定是在其它类中被 AlertController 的实例执行了,AlertController类 是 AlertDialog 的一个辅助类且 AlertDialog 也持有一个 AlertController类 的实例,所以我们在 AlertController类 中找这个 installContent()方法:

通过查找,在 AlertDialog 中的 onCreate(Bundle savedInstanceState)方法 中找到了这个方法。onCreate(Bundle savedInstanceState)方法 是重写了 父类Dialog 的方法,那具体的执行过程就要去 Dialog类 中找了。

在 Dialog类 中能看到 onCreate(Bundle savedInstanceState) 在 dispatchOnCreate(Bundle savedInstanceState) 中执行:

dispatchOnCreate(Bundle savedInstanceState)方法 在 Dialog类 中被多次执行,而与 AlertDialog 直接相关的则是 show()方法:

简化之后的 show()方法,mCreated 是一个布尔变量,标记是否被创建,首次调用 show() 时,mCreated 为 false,会执行 dispatchOnCreate(null),就会执行 onCreate(Bundle savedInstanceState)。到此,为什么 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为 null,就很清楚了。AlertDialog 的逻辑是 show()方法 中初始化了 AlertDialog 上面的控件,然后展示出来,所以在 show()方法 执行之后才能得到 button。

那我们也来看一下 AlertDialog.Builder.create() 中都干了什么:

这个方法里面做了实例化一个 AlertDialog 和一些参数的传递,在 AlertDialog 的构造方法中初始化了 AlertController mAlert

在 AlertController 的构造方法中也只是传参和初始化布局id,并没有做初始化控件的操作。

所以在 AlertDialog.Builder.create() 之后是得不到具体的控件实例的。

在 show() 之后我们能得到 Button,那我们能不能得到 dialog 上面其他的控件呢?

我们看 AlertController 的源码会发现,有多处使用 findViewById() 这个方法,使用的是 mWindow,比如:

这个 mWindow 变量是通过构造方法传进来的,我们在看下 AlertDialog 的构造方法:

mWindow 是 Dialog 的 getWindow() 返回的对象,查看 Dialog 的构造方法,会发现这个 mWindow 是一个 PhoneWindow 实例:

源码使用 Window.findViewById(int id),那我们应该也能通过获取 Dialog 的 Window 来获取其他控件。利用这个方法得到 AlertDialog 上面的控件来设置各种属性,包括title、message都可以设置颜色、大小等属性,可以看下我的上篇博:

Builder模式设置AlertDialog字体大小、颜色等属性

http://blog.csdn.net/y12345654321/article/details/72639106

回顾一下AlertDialog的创建流程:

首先,通过 AlertDialog 的内部类 Builder,设置了各种属性,然后通过 create()方法 实例化了 AlertDialog,在这个过程中创建了 Dialog 的 PhoneWindow,再然后是执行 show()方法,在这个方法中,执行了 onCreate(Bundle savedInstanceState)方法,接着是执行 AlertController.installContent(),在这个方法中初始化了各种控件,并设置属性。最后才是 show()方法 中的 WindowManager.addView(PhoneWindow.getDecorView, WindowManager.LayoutParams) 展示出来。

看一下 AlertController.installContent() 这个方法:

Dialog类 中的 setContentView(contentView)方法:

所以在 mDialog.setContentView(contentView) 这句话的执行之后才能在后面使用 mWindow.findViewById(int id),也就是这样我们才能通过 Dialog.getWindow().findViewById(int id) 得到具体的控件。

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

原创粉丝点击