使用单例模式实现AlertDialog导致Android应用崩溃问题排查
单例实现LoadingDialog导致崩溃的原因与修复方案
核心问题根源
单例的全局唯一性特性,和AlertDialog依赖Activity生命周期、Window Token的特性完全冲突,主要触发两类问题:
1. 错误的Context类型导致Token无效
AlertDialog必须依附于Activity的Window才能显示,它需要Activity的Window Token完成绑定。如果单例实现中:
- 传入了
Application Context(比如调用context.getApplicationContext()初始化),Application Context没有对应的Window,调用show()时会直接抛出WindowManager$BadTokenException。 - 长期持有已销毁的Activity Context,当Activity被回收后,对应的Window Token已经失效,再次调用Dialog的
show()也会触发该异常。
2. 单例实例与Activity生命周期不匹配
单例是全局唯一实例,会一直持有Context引用:
- 如果传入的是Activity Context,当Activity销毁后,单例里的Dialog实例仍然持有这个已失效的Context引用,后续调用
show()时,系统检测到Window Token无效,直接崩溃。 - 即使调用了
dismiss(),单例的Dialog实例不会被销毁,再次显示时复用旧的、绑定了无效Context的实例,同样触发错误。
常见错误代码示例(模拟)
你的LoadingDialog单例大概率是类似这样的写法(存在问题):
public class LoadingDialog { private static LoadingDialog instance; private final AlertDialog dialog; private LoadingDialog(Context context) { // 错误:用Application Context初始化Dialog Context appContext = context.getApplicationContext(); dialog = new AlertDialog.Builder(appContext) .setMessage("加载中") .setCancelable(false) .create(); } public static LoadingDialog getInstance(Context context) { if (instance == null) { instance = new LoadingDialog(context); } return instance; } public void show() { if (!dialog.isShowing()) { dialog.show(); // 此处触发BadTokenException } } public void dismiss() { if (dialog.isShowing()) { dialog.dismiss(); } } }
修复方案
方案1:移除单例,每次创建新实例(推荐)
这是你已经验证有效的方式,让LoadingDialog和当前Activity的生命周期绑定:
public class LoadingDialog { private final AlertDialog dialog; public LoadingDialog(Context context) { // 直接传入当前Activity的Context dialog = new AlertDialog.Builder(context) .setMessage("加载中") .setCancelable(false) .create(); } public void show() { if (!dialog.isShowing()) { dialog.show(); } } public void dismiss() { if (dialog.isShowing()) { dialog.dismiss(); } } } // 在Activity中调用 LoadingDialog loadingDialog = new LoadingDialog(this); loadingDialog.show(); // 用完后调用dismiss() loadingDialog.dismiss();
方案2:保留单例但做生命周期兼容处理
如果一定要用单例,需要解决Context持有和有效性检查的问题:
public class LoadingDialog { private static LoadingDialog instance; private AlertDialog dialog; private WeakReference<Context> contextRef; // 用弱引用避免内存泄漏 private LoadingDialog(Context context) { contextRef = new WeakReference<>(context); } public static LoadingDialog getInstance(Context context) { if (instance == null) { instance = new LoadingDialog(context); } else { // 更新为当前有效的Activity Context instance.contextRef = new WeakReference<>(context); } return instance; } public void show() { Context context = contextRef.get(); // 检查Context是否有效:必须是活跃的Activity if (context == null || !(context instanceof Activity) || ((Activity) context).isFinishing()) { return; } // 每次显示前重新创建Dialog,避免复用旧实例 if (dialog != null && dialog.isShowing()) { dialog.dismiss(); } dialog = new AlertDialog.Builder(context) .setMessage("加载中") .setCancelable(false) .create(); dialog.show(); } public void dismiss() { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); dialog = null; // 释放实例 } } }
错误日志解释
WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
这个异常明确说明:你试图将Dialog附加到一个不存在的Window上,要么是用了没有Window的Application Context,要么是对应的Activity已经销毁,Window Token已失效。
内容的提问来源于stack exchange,提问作者Mohamed Hossam
相关产品推荐
相关产品推荐

