CountDownTimer.onFinish() 中建立 AlertDialog 崩潰:BadTokenException: Unable to add window -- token ... is not valid; is your activity running?。
this (Activity),但計時器在背景執行緒運行,Activity 可能已 onDestroy。Resumed 狀態的 Activity 才能顯示 Dialog。new AlertDialog.Builder(this) 中的 this 指向 CountDownTimer 實例而非 Activity。
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();
}
}
@Override
public void onTick(long millisUntilFinished) {
Activity activity = activityRef.get();
if (activity != null && !activity.isFinishing()) {
activity.runOnUiThread(() ->
counterTextView.setText("剩餘: " + millisUntilFinished/1000 + " 秒"));
}
}
// 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)
}
}
}
}
優勢:自動綁定生命週期、取消協程即停止、無記憶體洩漏風險、程式碼線性易讀。
isFinishing()/isDestroyed()。理解 Android 組件生命週期與 Context 類型差異,是避免 BadTokenException、記憶體洩漏、ANR 的關鍵。