为什么 Dialog 不能用 Application 的 Context
抛出的异常:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationat android.view.ViewRootImpl.setView(ViewRootImpl.java:685)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)at android.app.Dialog.show(Dialog.java:316)
抛出的地方:
Res 来自 windowSession,即来自 WindowManagerService:
可以看到,异常说 attr.token
不是一个 app 的 token,attr 是 setView 方法的参数,是一个 WindowManager.LayoutParams 对象,WindowManger.LayoutParams 继承了 ViewGroup.LayoutParams。
// WindowManagerGlobalpublic void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ...}
setView 被 addView 调用,传递了 wparams
给 attrs:
// WindowManagerGlobalpublic void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... root.setView(view, wparams, panelParentView); ...}
如果 parentWindow 不为空,wparams 会使用 parentWindow
来赋值:
// WindowManagerGlobalpublic void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } ... root.setView(view, wparams, panelParentView); ...}
parentWindow 中会根据 mContainer
的值来决定 wp.token 的值:
// Windowvoid adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { ... if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } ...}
mContainer 只会在 Activity
中赋值:
// Windowpublic void setContainer(Window container) { mContainer = container; ...}// Activityfinal void attach(...) { ... if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } ...}
Activity 的 mParent
是 ActivityGroup,已经被废弃了,所以一定为 null,则 mContainer 一定为 null,所以 Window#adjustLayoutParamsForSubWindow
方法中 mp.token 为 mAppToken,即 WindowManagerGlobal#addView
方法中 parentWindow 参数的 mAppToken。
WindowManagerGlobal#addView 方法会被 WindowManagerImpl#addView
方法调用,parentWindow 的值为 mParentWindow。
// WindowManagerImplpublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}
mParentWindow 的赋值来自 createLocalWindowManger
,createLocalWindowManger 会被 Window 的 setWindowManger
调用。
setWindowManger 调用时,会将自己作为 parentWindow,也就是调用 setWindowManger 的 Window 对象会被作为 parentWindow。
总结一下:
- 一个 WindowMangerImpl 有一个 mParentWindow 成员。
- 一个 Window 有一个 mWindowManager 成员。
- 当 Window 通过 setWindowManger 方法生成 mWindowManager 时,这个 mWindowManager 会同时将 Window 作为 mParentWindow。
- 所以,一个 WindowManger 的 token 如果不手动指定,则取决于它的 mParentWindow 对象(即生成它的 Window 对象)的 mAppToken。
// WindowMangerImplprivate WindowManagerImpl(Context context, Window parentWindow) { mContext = context; mParentWindow = parentWindow;}public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow);}// Windowpublic void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}
Dialog 的构造函数中会生成 WindowManger:
// DialogDialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { ... mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); ...}
对 Activity 来说,返回的是自己的 mWindowManager
:
// Activitypublic Object getSystemService(@ServiceName @NonNull String name) { ... if (WINDOW_SERVICE.equals(name)) { return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name);}
Activity 的 mWindowManger 来自它 PhoneWindow 的 WindowManger:
// Activityfinal void attach(...) { ... mWindowManager = mWindow.getWindowManager(); ...}
PhoneWindow 的 WindowManger 来自 setWindowManager 方法。
// Activityfinal void attach(...) { ... mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ... mWindowManager = mWindow.getWindowManager(); ...}
根据上面的结论,PhoneWindow 的 WindowManager 的 token 取决于 PhoneWindow 的 mAppToken。
在上面的 setWindowManager 方法参数中可以看到,PhoneWindow 的 mAppToken 来自 Activity 的 mToken,所以它的 WindowManger 的 token 也就是 Activity 的 mToken。
如果是其他 Context,则没有这个 mToken。
总结一下:
- Dialog 在构造时生成 WindowManger
- 如果是 Activity,则获取到的是 Activity 的 WindowManger,它的 WindowManger.LayoutParams 的 token 是 Activity 的 token。
- 如果是其他 Context,则获取到的是一个新的 WindowManger,它的 WindowManger.LayoutParams 的 token 是 null。
- Dialog show 时
- 如果 WindowManger.LayoutParams 的 token 不是 Activity 的 token,则抛出异常。
更多相关文章
- Android(安卓)TV listView焦点平滑移动
- Android(安卓)onMeasure、Measure、measureChild、measureChildr
- android inputreader 部分对event数据的处理
- 在Android(安卓)7.0上PopupWindow.showAsDropDown不起作用的解决
- android获取设备屏幕大小的方法
- Android实现手机震动抖动效果的方法
- Android级联菜单的实现方法
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用