本文中涉及的代码基于Android API 25.
本文主要探索Dialog的本质以及它是如何被显示和隐藏的。
构造Dialog最终会调用其参数最多的重载函数
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
在方法中获取系统WindowsManager,初始化mWindows为PhoneWindow。
在Dialog构造完毕完毕之后,调用show()在屏幕上显示dialog。展示的window处于application层,并且是不透明状态。不能通过重写这个方法来初始化,而应该在onStart()方法中进行。
public void show() {
if (mShowing) {//判断是否是显示状态
if (mDecor != null) {//判断mDecor是否已经初始化
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {//还没创建的话最终会调用onCreate()方法
dispatchOnCreate(null);
} else {
//当dialog从windowmanager中移除之后,如果系统configuration
//已经发生变化,再次展示dialog的时候需要将config传入
//mWindow的decorview中以做出相应配置改变
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
//actionBar的一些处理,忽略不表
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
mWindowManager.addView(mDecor, l); //最关键的其实在这
mShowing = true;
sendShowMessage();
}
dispatchOnCreate()方法会调用onCreate方法:
protected void onCreate(Bundle savedInstanceState) {
}
在dialog方法中onCreate()方法为空,该方法和activity中的onCreate类似,需要用户在该方法中实现dialog初始化,包括调用setContent()方法。
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
setContentView()方法最终调用window的setContent()方法来初始化view。
之后最关键的地方就是调用mWindowManager的addView方法将mWindow的mDecor添加进来,也就是将dialog展示在界面上。
private void sendShowMessage() {
if (mShowMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mShowMessage).sendToTarget();
}
}
如果mShowMessage已经被初始化,在绑定的handler中处理该message。
那么mShowMessage的是在哪被初始化的呢?
public void setOnShowListener(@Nullable OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
mShowMessage = null;
}
}
mShowMessage是被mListenerHandler初始化。
mListenrHandler的实现如下:
private static final class ListenersHandler extends Handler {
private final WeakReference mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference<>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
mListenerHandler内部持有一个WeakReference类型的当前diaolog对象,防止内存泄露。所以onDismiss/onCancelo/nShow的参数有可能为null,使用的时候需要注意下。
调用hide()方法可以隐藏Dialog,注意这里是隐藏不是dimiss。
public void hide() {
if (mDecor != null) {
mDecor.setVisibility(View.GONE);
}
}
实现方式只是将window的mDecor隐藏掉,也就是说将view设置为gone。
调用dismiss()方法使Dialog消失。该方法在是线程安全的,意思是说在非UI线程也可调用。注意不能在重写此方法来清理内存对象,而应该在onStop()方法中。
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {//调用者在UI线程
dismissDialog();
} else {//在非UI线程,在与主线程绑定的的mHandler中调用mDismissAction
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
if (mDecor == null || !mShowing) {//如果mDecor还没有初始化或者dialog还没有显示直接return。
return;
}
if (mWindow.isDestroyed()) {//mWindow已经被destroyed
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);//最重要的事
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
该方法中主要做的事情就是从mWindowManager中移除mDecor。
sendDismissMessage()与上述的sendShowMessage()方法类似,不再赘述。
从此可以看出Dialog的本质其实是一个管理以及封装类。dialog的显示以及隐藏都是通过WindowManager中添加或移除view来实现,dialog只是做了相应的封装。