求助:多次触发android.view.WindowManager$BadTokenException崩溃
我来拆解这个你遇到的高频Android崩溃问题,先看一下你提供的崩溃日志:
Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@42383ff8 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:536)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.ActivityThread.handleResumeActivity(Act...
这个BadTokenException本质上是你尝试在已经失效的Activity上下文上添加窗口类组件(比如Dialog、PopupWindow)。这里的BinderProxy是Activity窗口的唯一令牌,当Activity被销毁(比如用户返回、系统回收、配置重建)后,这个令牌就会失效,系统会拒绝任何基于这个失效令牌的窗口添加请求,直接抛出异常。
常见的触发场景有这几种:
- 异步任务(网络请求、子线程耗时操作)完成后,在回调里弹出窗口,但此时用户已经退出了当前Activity
- 错误地使用
ApplicationContext创建Dialog/PopupWindow(ApplicationContext没有关联的窗口令牌,无法承载这类组件) - 屏幕旋转、语言切换等配置变化导致Activity重建,旧Activity实例的回调触发,尝试弹窗口
针对这些场景,你可以逐个排查并应用对应的解决方案:
弹出窗口前先检查Activity状态
在调用弹出窗口的代码前,先判断Activity是否还处于活跃状态,用isFinishing()(兼容所有API)和isDestroyed()(API 17+)双重校验:if (!this.isFinishing() && !this.isDestroyed()) { // 这里写弹出Dialog/PopupWindow的逻辑 AlertDialog dialog = new AlertDialog.Builder(this) .setTitle("提示") .setMessage("内容") .create(); dialog.show(); }用弱引用持有Activity(针对异步场景)
如果是在异步任务(比如AsyncTask、OkHttp回调)中触发窗口弹出,不要直接持有Activity的强引用,改用WeakReference避免内存泄漏同时,在回调里先检查引用是否有效:// 异步任务示例 private static class MyNetworkTask extends AsyncTask<Void, Void, String> { private final WeakReference<YourActivity> mActivityRef; public MyNetworkTask(YourActivity activity) { mActivityRef = new WeakReference<>(activity); } @Override protected String doInBackground(Void... params) { // 模拟网络请求 return "请求结果"; } @Override protected void onPostExecute(String result) { YourActivity activity = mActivityRef.get(); if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { // 弹出窗口 } } }禁止用ApplicationContext创建窗口组件
Dialog、PopupWindow必须依附于Activity上下文,所以创建时一定要传入当前Activity的this,而不是getApplicationContext()或者getApplication(),后者没有窗口令牌,必然会触发这个异常。在Activity生命周期内取消异步任务
在Activity的onDestroy()或者onPause()方法中,取消所有未完成的异步任务、RxJava订阅、Coroutine Job等,从源头避免无效回调触发窗口弹出:@Override protected void onDestroy() { super.onDestroy(); // 取消AsyncTask if (mMyNetworkTask != null && mMyNetworkTask.getStatus() != AsyncTask.Status.FINISHED) { mMyNetworkTask.cancel(true); } // 取消RxJava订阅 if (mDisposable != null && !mDisposable.isDisposed()) { mDisposable.dispose(); } // 取消Coroutine Job if (mJob != null && mJob.isActive()) { mJob.cancel(); } }处理配置变化场景
如果是屏幕旋转等配置变化导致的崩溃,可以在Manifest的Activity标签中添加android:configChanges="orientation|screenSize"(不推荐滥用,仅当你不需要系统重建Activity时使用),或者在onSaveInstanceState中保存任务状态,重建后恢复并判断是否需要弹出窗口。
内容的提问来源于stack exchange,提问作者vidha

