Dialog的Window创建过程
- Dialog的Window的创建过程和Activity类似
- 首先看一下dialog构造方法里面代码
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == ResourceId.ID_NULL) {
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.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
从上面的代码可以看出,通过final Window w = new PhoneWindow(mContext);创建window对象,接下来设置各种回调
- 接下来要做的就是初始化DecorView并将Dialog的视图添加到DecorView中,接下来看一下Dialog的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
直接调用的window的setContentView方法。和Activity的逻辑基本是一样的了,最后在dialog的show()方法中将DecorView添加到Window中代码如下
public void show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
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();
}
Dialog的Window创建和Activity的Window创建过程很类似,二者几乎没有什么区别。当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)。
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
Dialog有个特殊之处,如果用applicationContext来启动的话会报错报错如下:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:809)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
at android.app.Dialog.show(Dialog.java:330)
at com.demo.myapplication.MainActivity.showMyDialog(MainActivity.kt:26)
at com.demo.myapplication.MainActivity.access$showMyDialog(MainActivity.kt:9)
at com.demo.myapplication.MainActivity$onCreate$1.onClick(MainActivity.kt:19)
at android.view.View.performClick(View.java:6311)
at android.view.View$PerformClick.run(View.java:24833)
at android.os.Handler.handleCallback(Handler.java:794)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:173)
at android.app.ActivityThread.main(ActivityThread.java:6653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:821)
错误的原因就是找不到token这个标识,而应用token一般只有Activity才有所以只要用Activity作为Context来显示Dialog即可。
另外系统Window比较特殊,它可以不需要token。WindowManager.LayoutParams中的type表示Window的类型,而系统Window的层级范围是2000~2999,这些层级范围就对应着type参数。系统Window的层级有很多值,对于本例来说,可以选用TYPE_SYSTEM_OVERLAY,TYPE_SYSTEM_ERROR 来指定对话框的Window类型为系统Window,并在AndroidManifest文件中声明权限从而可以使用系统Window
dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)
//或者 dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY)
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
上一篇: NO.11 对象是怎样炼成的 | Java敲黑板系列
下一篇: 获取文件修改时间