为什么 Dialog 不能用 Application 的 Context

10 篇文章 0 订阅
1 篇文章 0 订阅

抛出的异常:

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 有一个 mParentWindow 成员。
  • 一个 Window 有一个 mWindowManager 成员。
  • 当 Window 通过 setWindowManger 方法生成 mWindowManager 时,这个 mWindowManager 会同时将 Window 作为 mParentWindow。
  • 所以,一个 WindowManger 的 token 如果不手动指定,则取决于它的 mParentWindow 对象(即生成它的 Window 对象)的 mAppToken。
// 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。

总结一下:

  • Dialog 在构造时生成 WindowManger
    • 如果是 Activity,则获取到的是 Activity 的 WindowManger,它的 WindowManger.LayoutParams 的 token 是 Activity 的 token。
    • 如果是其他 Context,则获取到的是一个新的 WindowManger,它的 WindowManger.LayoutParams 的 token 是 null。
  • Dialog show 时
    • 如果 WindowManger.LayoutParams 的 token 不是 Activity 的 token,则抛出异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值