弹窗内Spinner模拟器运行正常,真机触发BadTokenException异常
这种真机专属的崩溃真的让人头大!我之前开发带Spinner的弹窗时也遇到过一模一样的情况——模拟器上顺风顺水,一到真机就报android.view.WindowManager$BadTokenException: Unable to add window -- token android.view.ViewRootImpl$W@b3b2e85 is not valid; is your activity running?,给你捋捋原因和解决方案:
问题根源
本质是Spinner弹出下拉框时,依赖的Activity已经处于非活跃状态(被销毁/后台回收/finish),导致它拿不到有效的Window Token。模拟器资源充足,Activity生命周期变化宽松,所以没触发;而真机内存紧张,系统会优先回收后台Activity,就暴露了问题。
具体解决方案
1. 显示弹窗前先校验Activity状态
在弹出弹窗的代码前,加个判断确保Activity还活着:
if (!isFinishing() && !isDestroyed()) { // 显示你的弹窗 dialog.show(); }
- Android 11及以上用
isDestroyed(),低版本只用isFinishing()就行,避免Activity已经销毁还去弹窗口。
2. 给Spinner用对Context
绝对不要用getApplicationContext()初始化Spinner或弹窗!必须用当前Activity的上下文(比如YourActivity.this),因为Application Context没有自己的Window,Spinner下拉需要绑定Activity的Window Token才能正常显示。
3. 确保弹窗的Window Token正确
如果是自定义弹窗(不是系统Dialog),手动指定Window Token为Activity的根View Token:
WindowManager.LayoutParams params = customDialog.getWindow().getAttributes(); // 绑定当前Activity的Window Token params.token = getWindow().getDecorView().getRootView().getWindowToken(); customDialog.getWindow().setAttributes(params);
系统Dialog如果用Activity Context创建的话,默认会自动绑定正确的Token,这一步可以跳过,但自定义弹窗一定要注意。
4. 生命周期中及时清理弹窗
在Activity进入后台或销毁时,关闭弹窗,避免残留的弹窗触发Spinner操作:
@Override protected void onPause() { super.onPause(); if (dialog != null && dialog.isShowing()) { dialog.dismiss(); } } @Override protected void onDestroy() { super.onDestroy(); dialog = null; // 置空避免内存泄漏 }
额外提醒
如果你的弹窗是在异步任务(比如网络请求回调)中显示的,一定要再加一层Activity状态校验——因为网络请求完成时,Activity可能已经被用户关掉了,这时候弹窗口必崩!
内容的提问来源于stack exchange,提问作者El_Loco

