【记】Dialog dismiss无法正常关闭问题
问题描述
弹出框正常show后在界面正常展示,但调用dismiss操作无法进行关闭。
并且设置 setCancelable(true) 也无法点击弹出框外部进行关闭,也就是弹出框显示后无论如何操作均无法正常关闭。
问题复现
首先创建弹出框
mDialog = new Dialog(context);
mDialog.setContentView(rootLayout, rootLP);
mDialog.setCancelable(false);
mDialog.show();
相关关闭调用
mContentView.findViewById(R.id.dialog_teach_rate_up_layout).setOnClickListener(v -> {
dismiss();
});
dismiss 内部则是调用的mDialog.dismiss(),调试代码可以触发路径正常,也正常进入系统dialog内部dismiss方法中。
问题猜测
触发路径异常
这个经过调试,整个链路正常,也触发了dismiss操作,但手机界面无反应。
mDialog变量被覆盖
这个猜测是觉得mDialog变量被反复创建,当我们调用dismiss时不是调用的正在显示的dialog;有时候可能会因为历史逻辑或粗心导致该情况。
也是一样调试发现创建过程仅触发一次,其hash与dismiss时hash一致;排除该情况。
在子线程中创建
怀疑过该情况,但实际运行中并未触发crash。在弹出框内部有持有Handler。
其内部有判断不合法则会Crash
在子线程中dismiss
该情况也有怀疑,但实际代码排查不存在该问题;排除。
到此时问题似乎难解了,基本上该排查的都排查了,甚至还怀疑是不是刚刚dismiss又立刻进行了show显示的情况,但均不是。
问题解决
问题没找到那就肯定是排查的还不够仔细,就在我进一步调试dismiss时,发现了这些信息:
图中我加上了是否是UI-Thread的调试判断,以及当前的Looper输出。
从图中可以看出:
- 是UI-Thread中调用的dismiss
- mDialog内部的Handler所持有的Looper与当前的Main Looper 并不相同
那么到这里,答案似乎呼之欲出了。
首先弹出框内部dismiss会进行looper判断,那么按我们的情况会进入到handler post message 的流程。
其链路为:
Handler.post -> Handler.sendMessageDelayed -> Handler.sendMessageAtTime -> Handler.enqueueMessage -> MessageQueue.enqueueMessage
看起来似乎与Looper没有关系,但Handler中的mQueue = mLooper.mQueue;
猜测:当Looper状态不正常时,其对应的mQueue消息将得不到处理。
其不正常状态一般为:
- 对应的线程处于阻塞状态,也就无法处理消息
- Looper本身quit了
而前面怀疑 “在子线程创建” 这一步放过了,因其判断依据仅仅为是否有looper存在,而没细考虑到子线程也会有Looper的情况。
最后验证猜想
在创建时刻加上断点,并查看当前链路与looper判断;可以看见其调度流程其实是播放器线程调度过来的,并且此时也的确不是主线程的Looper,而在本次调用后,播放器其实阻断停止了;所以也就导致了后续的dismiss的消息无法正常消费的问题。
最后
本次算是一次踩坑经历,遂记录之。