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

Android CountDownTimer 結束顯示 Dialog 崩潰:Context 錯誤與 Activity 生命週期

     編輯:程式設計綜合問答

問題現象

CountDownTimer.onFinish() 中建立 AlertDialog 崩潰:BadTokenException: Unable to add window -- token ... is not valid; is your activity running?。

根本原因

  1. CountDownTimer 內部持有外部 Context 參考:建構子傳入 this (Activity),但計時器在背景執行緒運行,Activity 可能已 onDestroy。
  2. Dialog 需要有效的 Window Token:只有處於 Resumed 狀態的 Activity 才能顯示 Dialog。
  3. 建構子未傳遞 Context 給 AlertDialog.Builder:原碼 new AlertDialog.Builder(this) 中的 this 指向 CountDownTimer 實例而非 Activity。

正確修正

修正一:建構子接收並保存 Activity Context


public class MyCountDownTimer extends CountDownTimer {
    private final WeakReference activityRef;  // 弱參考防洩漏
    
    public MyCountDownTimer(Activity activity, long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
        this.activityRef = new WeakReference<>(activity);
    }
    
    @Override
    public void onFinish() {
        Activity activity = activityRef.get();
        if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
            showRestartDialog(activity);
        }
    }
    
    private void showRestartDialog(Activity activity) {
        new AlertDialog.Builder(activity)  // 使用 Activity Context
            .setTitle("遊戲結束")
            .setMessage("是否重新開始?")
            .setPositiveButton("是", (dialog, which) -> {
                activity.startActivity(new Intent(activity, LevelActivity.class));
                activity.finish();
            })
            .setNegativeButton("否", (dialog, which) -> activity.finish())
            .show();
    }
}

修正二:onTick 中更新 UI 同樣需檢查 Activity 狀態


@Override
public void onTick(long millisUntilFinished) {
    Activity activity = activityRef.get();
    if (activity != null && !activity.isFinishing()) {
        activity.runOnUiThread(() -> 
            counterTextView.setText("剩餘: " + millisUntilFinished/1000 + " 秒"));
    }
}

現代替代方案:Lifecycle + Coroutine


// ViewModel 或 LifecycleObserver
class GameTimer(private val lifecycle: Lifecycle) {
    fun start(duration: Long) {
        lifecycleScope.launchWhenStarted {
            val startTime = System.currentTimeMillis()
            while (isActive) {
                val elapsed = System.currentTimeMillis() - startTime
                val remaining = duration - elapsed
                if (remaining <= 0) {
                    showDialog()
                    break
                }
                _remainingTime.postValue(remaining / 1000)
                delay(1000)
            }
        }
    }
}

優勢:自動綁定生命週期、取消協程即停止、無記憶體洩漏風險、程式碼線性易讀。

核心原則

  • 背景任務持有 Activity 參考必須用 WeakReference,並在使用前檢查 isFinishing()/isDestroyed()。
  • Dialog 必須用 Activity Context 建立,不可用 Application/Service Context。
  • CountDownTimer 已過時 (API 30),新專案請用協程/Flow/Lifecycle。

理解 Android 組件生命週期與 Context 類型差異,是避免 BadTokenException、記憶體洩漏、ANR 的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved