单Activity含13个RecyclerView及SearchView仅在API25模拟器报BadTokenException的原因?
问题场景
- 单个Activity中集成13个RecyclerView及对应的SearchView
- 仅在4.65英寸720p(Galaxy Nexus)API 25 Nougat模拟器出现崩溃
- 其他设备/模拟器(API 22/23/26、Pixel系列API 27)运行正常
- 崩溃日志:
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@a515134 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:679)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.widget.Toast$TN.handleShow(Toast.java:459)
at android.widget.Toast$TN$2.handleMessage(Toast.java:342)
原因分析
这个崩溃的核心是Toast尝试在Activity已销毁或处于不可用状态时显示。API 25的Toast实现逻辑和其他版本存在差异:
- 早期Android版本中,Toast会自动处理Activity状态,即便Activity已销毁,最多只是不显示Toast,不会崩溃
- 但API 25中,如果Activity的WindowToken已失效,Toast会直接抛出
BadTokenException
另外,13个RecyclerView+SearchView的场景大概率存在大量异步操作(比如搜索结果回调、列表加载完成后弹Toast),当Activity发生生命周期变化(比如屏幕旋转、切后台),API 25下的WindowToken可能提前失效,而异步任务仍在执行,最终触发崩溃。
解决方案
1. 显示Toast前先校验Activity状态
在调用Toast显示方法前,先判断Activity是否处于活跃状态,避免在销毁后触发显示:
if (!isFinishing() && !isDestroyed()) { Toast.makeText(this, "你的提示内容", Toast.LENGTH_SHORT).show(); }
如果需要兼容API 17以下版本,可以用getWindow().getDecorView().isAttachedToWindow()替代isDestroyed()。
2. 统一管理Toast实例
频繁创建Toast可能在API 25下引发Token冲突,建议全局维护一个Toast实例,更新内容后再显示:
private Toast mToast; public void showToast(String message) { if (mToast == null) { mToast = Toast.makeText(this, message, Toast.LENGTH_SHORT); } else { mToast.setText(message); } if (!isFinishing() && !isDestroyed()) { mToast.show(); } }
3. 绑定异步任务与Activity生命周期
13个RecyclerView和SearchView带来的异步任务(搜索、列表加载),必须在Activity销毁时及时取消,避免后续回调触发Toast:
- 用
AsyncTask的话,在onDestroy()中调用cancel(true) - 用Coroutines的话,在
onDestroy()中取消对应的Job - 用RxJava的话,在
onDestroy()中调用dispose()解除订阅
4. 针对API 25捕获异常
直接捕获API 25下的BadTokenException,避免崩溃:
public void safeShowToast(Context context, String message) { try { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } catch (WindowManager.BadTokenException e) { // 仅在API 25下可能触发,捕获后不做额外处理或记录日志 e.printStackTrace(); } }
内容的提问来源于stack exchange,提问作者Learner

