这个问题其实不算一个太好的问题,但是也能考察事件的分发流程,搞清楚 Window,Activity,DecorView 在事件分发环节的调用流程。
更多问答 >>
-
每日一问 据说很多 app 在 2019 年最后一周都出现了日期上的 bug ?
2020-01-14 20:33 -
每日一问 系统帮我们做了网络安全校验,那么还需要我们手动去做吗?
2020-01-14 20:33 -
每日一问 “别慌,你 post 一个 Runnable ,在下一帧就可以拿到了。”这种说法对吗?
2020-01-20 01:05 -
每日一问 “你这个布局可以在进入这个页面之前预加载,这样页面渲染就快了”可能有什么潜在的问题吗?
2020-01-20 01:05 -
2020-02-02 17:17
-
每日一问 很多书籍上写:“事件分发只有一次 ACTION_DOWN,一次 ACTION_UP”严谨吗?
2020-01-07 00:08 -
每日一问 ViewPager 嵌套,“老子”怎么就没拦住你?
2019-12-29 23:52 -
每日一问 Activity 都重建了,你 Fragment凭什么活着?
2019-12-23 23:19 -
2019-12-20 00:08
-
2019-12-15 23:55

这个问题很简单,这个是因为Dialog是Focusable的。如果不是Focusable的,那么Dialog 弹出后 Activity 还是可以响应用户事件的。
首先要明确一个概念是窗口是事件分发的基本单位,而不是activity或者应用。系统的事件总是要先分给窗口,决定要分给哪个窗口是在系统的InputDispatcher.cpp这个类中。
对于touch事件,还要再由窗口交给DecorView在控件树中进行分发,最终到达某个View。但在此之前,会交由activity进行处理一下。可以用以下demo测试一下:
getWindowManager().addView 这里的添加view其实是添加一个子窗口。添加完MyDialog以后,我们的activity一共就有两个窗口了,另外一个是默认生成的子窗口。这一点可以通过adb shell dumpsys window visible来查看:
主窗口的名字一般就是activity的ComponentName。子窗口的名字如果不调用params.setTitle()进行设置,那么默认也是所属activity的ComponentName,只是前面的地址不一样而已。
可以看到,子窗口是在主窗口上面,即子窗口的z序是小于主窗口的。我们的MyDialog 没有加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 这句话的时候,子窗口的touch区域其实是整个Frame,也就是主窗口完完全全被子窗口挡住了,自然无法响应用户事件。这个说法有什么证据吗?可以通过adb shell dumpsys input 来查看一下:
在Input Dispatcher State:一栏中,有以下信息:
可以看到虽然子窗口的看起来只有 frame=[590,1127][1080,1287] 这么大,但其touchableRegion=[0,0][1080,2340]却是全屏的,和主窗口一样大,又在主窗口上面,于是挡住了主窗口;所以主窗口,即大家经常说的acitivity,无法接收到touch事件。只有当子窗口接受到事件dismiss以后,才能接收到事件。
当MyDialog 加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 这句话的时候 ,它的touch区域发生了变化。
现在子窗口看起来多大,能接受touch事件的区域也就多大了,其余区域不会在阻拦主窗口的事件。而且现在子窗口无法接受key事件,canReceiveKeys=false。
最后,为什么谷歌要这么做?原因不是很清楚,可以猜测一下:如果一个窗口是Focusable的,那么其是要响应全局事件的,因为Focusable对应着key事件,意味可以接受key事件,而key事件是全局的(没有一个具体的区域,touch事件是有局域的,就是touchableRegion),所以为了统一,也把touchableRegion改成了全屏。如果你调反过来,如果一个子窗口是不能接受key事件的,那么它实际上多大,它的touchableRegion也就是多大。
总结:
因为Android中特意淡化了Window的概念,所以可能很多同学只知道activity而不知道Window。如果弄清楚了Window在事件分发中的作用,这个问题还是容易理解的。结论就是子窗口弹出了以后,因为它在窗口的上面,而且他的touchableRegion是全屏,挡住了主窗口,所以系统把touch事件都发给子窗口了,主窗口无法响应。demo代码:https://github.com/CQULittleMing/AndroidDemo/blob/master/demo/src/main/java/com/example/xcm/demo/wms/TestWmsActivity.java
这个只是个人学习的一个demo,大家看看就好哈哈~运行以后在Window - window - TestWmsActivity中感谢分享!
Dialog 弹出后 Activity 就无法响应用户事件?是事件分发的原因吗?
我一直认为是后面的activity进入了onPause(),Activity在onResume()中才能与界面进行交互。然后最近做了一个TV的自定义dialog,这个dialog本身就是一个activity,只是改了theme样式,我弹出dialog的判定本身就是使用ok键,如果弹出后还能响应原activity的用户事件那岂不是乱套了,我在本dialog中无法使用ok键,而且无限弹框了。然后题目中说了“搞清楚 Window,Activity,DecorView 在事件分发环节的调用流程。”,我就有点头绪了,因为android的事件分发是U形的,也就是在前端中的事件捕获(从上到下)与冒泡事件(从下到上)的组合。当时我有个大胆的理解是,你在网页中,你使用的是鼠标,鼠标是在屏幕里面的,就可以把它想成一个最底层的组件,只是z-index最高就好;而在手机中,点击的一般是我们手指,你只能点到屏幕,他最开始收到信号的只能是屏幕,然后只能根据Android视图间的层级关系往下传递,最后再冒泡回来。
链接:https://juejin.im/post/5da210ccf265da5ba0778541就像上面链接中说到android的事件分发的时候很多都会略过PhoneWindow这一层,因为dialog的目的就是一种对话框的存在,几乎是一次性的存在(对于一次对话来说)。甚至是提示框,如果你有那么多交互的东西,你会像我一样去自定义一个dialog,其实就是一个非全屏的activity。
注意下这个问题有些歧义,其实Dialog并不是一定会挡住Activity的所有touch事件。
所以一般我们理解的事件分发是从activity往下的u形分发。
实际上是window,只不过淡化了window的概念而dialog实际上也是装载有window的,所以理解上可以理解成dialog就是一个非全屏窗口型Activity,挡住了下层的activity导致下方无法响应事件