抛出的异常:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at 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。
// WindowManagerGlobal
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
}
setView 被 addView 调用,传递了 wparams
给 attrs:
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
root.setView(view, wparams, panelParentView);
...
}
如果 parentWindow 不为空,wparams 会使用 parentWindow
来赋值:
// WindowManagerGlobal
public 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 的值:
// Window
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
...
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
...
}
mContainer 只会在 Activity
中赋值:
// Window
public void setContainer(Window container) {
mContainer = container;
...
}
// Activity
final 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。
// WindowManagerImpl
public 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
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
// Window
public 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:
// Dialog
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
...
}
对 Activity 来说,返回的是自己的 mWindowManager
:
// Activity
public 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:
// Activity
final void attach(...) {
...
mWindowManager = mWindow.getWindowManager();
...
}
PhoneWindow 的 WindowManger 来自 setWindowManager 方法。
// Activity
final 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。
总结一下: