在ValueEventListener的onDataChange中调用AlertDialog触发BadTokenException
WindowManager$BadTokenException的问题 这个坑我踩过好多次!你遇到的android.view.WindowManager$BadTokenException本质原因很简单:当Firebase的ValueEventListener回调触发时,你的Activity已经被销毁或者不在活跃状态了,这时候AlertDialog找不到有效的窗口Token来依附,自然就报错了。
下面给你几个靠谱的解决方案,按优先级排序:
1. 弹Dialog前先检查Activity状态
在创建AlertDialog之前,先判断当前Activity是否还处于活跃状态,避免在Activity销毁后执行弹窗操作:
@Override public void onDataChange(DataSnapshot dataSnapshot) { String token = (String) dataSnapshot.getValue(); if (token != null) { // 先检查Activity是否存活 if (!YourActivity.this.isFinishing() && !YourActivity.this.isDestroyed()) { AlertDialog.Builder builder = new AlertDialog.Builder(YourActivity.this); // 这里添加Dialog的标题、内容等设置 builder.setTitle("提示") .setMessage("收到新的Token:" + token) .setPositiveButton("确定", null) .show(); } } }
2. 及时移除Firebase监听器
Activity销毁后,一定要移除对应的ValueEventListener,避免后续异步回调触发无用操作,还能防止内存泄漏。在Activity的onDestroy()方法里做这件事:
private DatabaseReference mDatabaseRef; // 要持有你的Firebase数据库引用实例 @Override protected void onDestroy() { super.onDestroy(); // 移除监听器 if (mDatabaseRef != null && mInviteListener != null) { mDatabaseRef.removeEventListener(mInviteListener); } }
注意:一定要保留DatabaseReference的实例引用,不然没法调用removeEventListener方法。
3. 用弱引用避免Activity内存泄漏+状态检查
如果你的监听器是匿名内部类,它会持有Activity的强引用,可能导致Activity无法被GC回收(内存泄漏),同时也会在Activity销毁后依然触发回调。这时候可以用WeakReference来包装Activity:
// 把监听器改成静态内部类,避免持有外部类强引用 private static class InviteValueEventListener implements ValueEventListener { private final WeakReference<YourActivity> mActivityWeakRef; public InviteValueEventListener(YourActivity activity) { mActivityWeakRef = new WeakReference<>(activity); } @Override public void onDataChange(DataSnapshot dataSnapshot) { YourActivity activity = mActivityWeakRef.get(); // 先判断Activity是否还存活 if (activity == null || activity.isFinishing() || activity.isDestroyed()) { return; } String token = (String) dataSnapshot.getValue(); if (token != null) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); // 设置Dialog内容 builder.show(); } } @Override public void onCancelled(DatabaseError databaseError) { // 这里可以处理回调取消的情况,比如打印日志 Log.e("Firebase", "监听器取消:" + databaseError.getMessage()); } }
然后初始化监听器的时候:
mInviteListener = new InviteValueEventListener(this); mDatabaseRef.addValueEventListener(mInviteListener);
为什么会出现这个错误?
AlertDialog是依赖于Activity的窗口存在的,每个Activity都有一个对应的Window Token。当Activity被finish()或者系统销毁后,这个Token就失效了。而Firebase的ValueEventListener是异步回调,可能在你退出Activity之后,数据才更新触发回调,这时候用失效的Token去创建Dialog,就会抛出这个异常。
内容的提问来源于stack exchange,提问作者ShiningChoi

