抛出的异常:

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)

抛出的地方:
为什么 Dialog 不能用 Application 的 Context_第1张图片

Res 来自 windowSession,即来自 WindowManagerService:
为什么 Dialog 不能用 Application 的 Context_第2张图片

可以看到,异常说 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,则抛出异常。

更多相关文章

  1. Android实现手机震动抖动效果的方法
  2. android获取设备屏幕大小的方法
  3. Android中MediaPlayer的setDataSource方法的使用
  4. android 监听方法
  5. android sdk 自带的非空非空串判断方法
  6. android中的handler的使用方法
  7. Android下调整多媒体音量方法
  8. Android Studio出现Failed to open zip file问题的解决方法

随机推荐

  1. Android多进程总结一:生成多进程(android
  2. android样式和主题(style&theme)
  3. android 移至pc
  4. Android(安卓)上实现水波特效
  5. 努力向前,年轻人
  6. Android中JNI的使用方法
  7. android:AsyncTask实现异步处理任务
  8. Android(安卓)socket通信 readline方法阻
  9. (转)Android从服务器端获取数据的几种方
  10. Android的多媒体框架OpenCore(PacketVide