前言
android 中使用 dialog 时一般情况下涉及到三个方法:new,show 与 dismiss。三个方法都会影响到对线程的使用有影响。
构造函数
在构造一个 dialog 对象时会初始化一个 Handler 对象。因此,dialog 在哪个线程初始化该 handler 就是哪个线程的对象。如果相关的线程没有 Looper::prepare 就会报错。
private final Handler mHandler = new Handler();
如下代码就会报错
// Can't create handler inside thread Thread[xxx] that has not called Looper.prepare()
thread {
mDialog = Dialog(this)
}
show
show 方法内部会调用 WindowManager::addView(),所以最终会初始化 ViewRootImpl 对象,初始化时也会初始化一些变量。所以如果 show 在子线程中调用,那么 ViewRootImpl 中记录的也是子线程。
// 初始化一个 Handler,所以如果 ViewRootImpl 是在子线程初始化的,Handler 就会是子线程的
final ViewRootHandler mHandler = new ViewRootHandler();
// 记录当前线程,也就是 show 方法所在的线程
mThread = Thread.currentThread();
dismiss
dismiss 会进行线程切换
dismiss 源码中会进行线程切换,它保证 dismiss 与构造函数执行在同一个线程,而不是与 show 执行在同一个线程,而且 dismiss 最终可以执行到 ViewRootImpl::checkThread 方法,该方法中又会进行线程判断,它要求当前线程必须和 ViewRootImpl 创建线程是同一个线程。
public void dismiss() {
// mHandler 在是 new dialog 实例的时候创建的
// 所以此处会将线程切换至 dialog 创建时的线程
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
dismiss 最终肯定会执行到 ViewRootImpl 中,将 View 从 window 中移除。在操作之前 ViewRootImpl 会调用 ViewRootImpl::checkThread 判断线程:
// ViewRootImpl 中
void checkThread() {
// mThread 是在 ViewRootImpl 中被赋值,也就是说,它记录的是 show 所在的线程
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that crea...");
}
}
总结
下面的总结只适合 android.app 包下的 Dialog,不适用于 androidx,后者会有一些主线程逻辑判断。
-
dialog 的创建与 show 必须处于同一线程,dismiss 会自动切换至该线程 -
整个代码中并没有对主线程的判断,因此
dialog 的 show 可以执行在任何线程中,只要这个线程调用可以使用 Handler 即可 -
android 允许在子线程中操作 UI,只要保证操作线程也 ViewRootImpl 创建线程是同一个即可。如下代码是可以执行的