3-1 Dialog窗口源码分析
写过APP都知道,Dialog是一系列XXXDialog的基类,我们可以new任意Dialog或者通过Activity提供的 onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法来管理我们的Dialog,但是究 其实质都是来源于Dialog基类,所以我们对于各种XXXDialog来说只用分析Dialog的窗口加载就可以了。
如下从Dialog的构造函数开始分析:
可以看到,Dialog构造函数首先把外部传入的参数context对象赋值给了当前类的成员(我们的Dialog一般都是在Activity中启动的, 所以这个context一般是个Activity),然后调用 context.getSystemService(Context.WINDOW_SERVICE)获取WindowManager,这个 WindowManager是哪来的呢?先按照上面说的context一般是个Activity来看待,可以发现这句实质就是Activity的 getSystemService方法,我们看下源码,如下:
看见没有,Dialog中的WindowManager成员实质和Activity里面是一样的,也就是共用了一个WindowManager。
回到Dialog的构造函数继续分析,在得到了WindowManager之后,程序又新建了一个Window对象(类型是PhoneWindow 类型,和Activity的Window新建过程类似);接着通过w.setCallback(this)设置Dialog为当前window的回调接 口,这样Dialog就能够接收事件处理了;接着把从Activity拿到的WindowManager对象关联到新创建的Window中。
至此Dialog的创建过程Window处理已经完毕,很简单,所以接下来我们继续看看Dialog的show与cancel方法,如下:
可以看见Dialog的新Window与Activity的Window的type同样都为TYPE_APPLICATION,上面介绍 WindowManager.LayoutParams时TYPE_APPLICATION的注释明确说过,普通应用程序窗口 TYPE_APPLICATION的token必须设置为Activity的token来指定窗口属于谁。所以可以看见,既然Dialog和 Activity共享同一个WindowManager(也就是上面分析的WindowManagerImpl),而WindowManagerImpl 里面有个Window类型的mParentWindow变量,这个变量在Activity的attach中创建WindowManagerImpl时传入 的为当前Activity的Window,而当前Activity的Window里面的mAppToken值又为当前Activity的token,所以 Activity与Dialog共享了同一个mAppToken值,只是Dialog和Activity的Window对象不同。
3-2 Dialog窗口加载总结
通过上面分析Dialog的窗口加载原理,我们总结如下图:
从图中可以看出,Activity和Dialog共用了一个Token对象,Dialog必须依赖于Activity而显示(通过别的 context搞完之后token都为null,最终会在ViewRootImpl的setView方法中加载时因为token为null抛出异常),所 以Dialog的Context传入参数一般是一个存在的Activity,如果Dialog弹出来之前Activity已经被销毁了,则这个 Dialog在弹出的时候就会抛出异常,因为token不可用了。在Dialog的构造函数中我们关联了新Window的callback事件监听处理, 所以当Dialog显示时Activity无法消费当前的事件。
到此Dialog的窗口加载机制就分析完毕了,接下来我们说说应用开发中常见的一个诡异问题。
3-3 从Dialog窗口加载分析引出的应用开发问题
有了上面的分析我们接下来看下平时开发App初学者容易犯的几个错误。
实现在一个Activity中显示一个Dialog,如下代码:
分析:使用了Activity为context,也即和Activity共用token,符合上面的分析,所以不会报错,正常执行。
实现在一个Activity中显示一个Dialog,如下代码:
分析:传入的是Application的Context,导致TYPE_APPLICATION类型Dialog的token为null,所以抛出如下异常,无法显示对话框。
实现在一个Service中显示一个Dialog,如下代码:
分析:传入的Context是一个Service,类似上面传入ApplicationContext一样的后果,一样的原因,抛出如下异常:
至此通过我们平时使用最多的Dialog也验证了Dialog成功显示的必要条件,同时也让大家避免了再次使用Dialog不当出现异常的情况,或者出现类似异常后知道真实的背后原因是什么的问题。
可以看见,Dialog的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。