You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用单例模式实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:08:22