首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android Dialog顯示崩潰「Unable to add window」根因深度解析與最佳實踐

     編輯:程式設計疑難解答

異常現場還原

應用退出後重新進入,網絡異常彈窗導致 Crash,日誌拋出:

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@... is not valid; is your activity running?

核心原因:Dialog 依附的 Context 已失效

Dialog 必須依附於一個存活的 Activity/Window。當 Activity 已 `finish()`、被系統回收、或處於非運行狀態(如 `onStop` 後)時,其 Window Token 失效,此時調用 `dialog.show()` 即拋出該異常。

常見觸發場景

  1. 異步回調延遲: 網絡請求回調時 Activity 已銷毀(用戶按 Back 鍵退出)。
  2. 靜態 Context 引用: 使用 `ApplicationContext` 或舊 Activity 的靜態引用創建 Dialog。
  3. 生命周期競態: `onSaveInstanceState` 後異步任務嘗試彈窗(State Loss)。

錯誤代碼剖析

// 錯誤示範:直接用 MainActivity.this,若 Activity 已銷毀則崩潰
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.create().show();

四大解決方案(按推薦度排序)

方案一:檢查 Activity 存活狀態(最通用)

private void showNetErrorDialog() {
    if (isFinishing() || isDestroyed()) return; // API 17+ 用 isDestroyed()
    
    new AlertDialog.Builder(this)
        .setTitle("提示")
        .setMessage("網絡異常,請重試")
        .setPositiveButton("重試", (d, w) -> retry())
        .setNegativeButton("退出", (d, w) -> finish())
        .show();
}

方案二:使用 DialogFragment(官方推薦)

DialogFragment 由 FragmentManager 管理,能自動處理配置變更、生命周期,避免 Window Leak。

public class NetErrorDialog extends DialogFragment {
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(requireActivity())
            .setMessage("網絡異常")
            .setPositiveButton("重試", (d, w) -> ((Retryable)requireActivity()).onRetry())
            .create();
    }
}

// 調用
new NetErrorDialog().show(getSupportFragmentManager(), "net_error");

方案三:弱引用/生命周期感知組件


// ViewModel + LiveData 模式,UI 組件自動感知生命周期
viewModel.getNetworkError().observe(this, error -> {
    if (error != null) showDialog();
});

方案四:Application Context 只能用於 Toast,不可用於 Dialog

❌ `new AlertDialog.Builder(getApplicationContext())` — 無法添加 Window Token。
✅ `Toast.makeText(getApplicationContext(), ...)` — Toast 不依附 Window。

最佳實踐總結

場景推薦方案
簡單彈窗,邏輯輕量方案一:`isFinishing()/isDestroyed()` 守衛
複雜交互、需保持狀態、配置變更方案二:DialogFragment
MVVM 架構、解耦 UI 與邏輯方案三:LiveData + ViewModel

避坑指南

  • 🚫 絕不在非 UI 線程直接 `show()`,必須 `runOnUiThread` 或 Handler。
  • 🚫 絕不持有 Activity 的靜態強引用(內存洩漏 + 崩潰雙重打擊)。
  • 🚫 絕不在 `onSaveInstanceState` 後異步彈窗,使用 `commitAllowingStateLoss()` 或 DialogFragment。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved