Android问答APP:QuizActivity倒计时结束弹窗音效跳转后仍异常播放
异常原因定位
- 定时器启动逻辑存在缺陷:
setNextQestions方法中,无论是否已经遍历完所有题目,都会在方法开头执行timer.start()。当用户完成全部题目准备跳转结果页时,代码依然会启动一个完整30秒的后台倒计时,这个定时器实例没有和Activity生命周期绑定,哪怕页面跳转到其他界面,定时器依然会在后台持续运行。 - 缺少状态拦截:定时器的
onFinish回调没有做前置校验,只要定时时间到就会直接执行弹窗、播放音效的逻辑,哪怕页面已经跳转、不再处于测验状态。 - 资源回收缺失:Activity销毁时没有主动终止定时器、释放MediaPlayer资源,导致后台泄漏的定时器依然可以触发后续的音效播放逻辑。
- 流程疏漏:正常完成测验跳转
ResultActivity的分支没有调用finish()销毁当前QuizActivity,让页面一直留在后台栈中,给后台定时器的回调执行提供了上下文支持。
可落地修复方案
- 调整定时器启动时机,仅在还有未完成题目时才启动倒计时,在题目遍历完成的分支不再启动新的计时任务
- 新增全局页面状态标记,在页面退出、跳转其他页面时标记为非活跃,拦截定时器的无效回调
- 重写
onDestroy方法,在页面销毁时彻底终止定时器、释放所有媒体资源,避免后台泄漏 - 在所有跳转其他页面的分支增加
finish()调用,及时销毁QuizActivity - 在所有主动退出、跳转其他页面的逻辑分支中提前将状态标记置为false,避免残留定时器触发无效回调
关键修正代码如下:
首先新增全局状态标记:
// 标记当前测验页面是否处于活跃可交互状态 private boolean isGameActive = false;
调整setNextQestions方法逻辑,将定时器启动逻辑挪到有效题目分支内:
public void setNextQestions() { // 先取消上一个未执行完的定时器 if (timer != null) { timer.cancel(); } if (index < qestions.size()) { // 仅在存在未展示题目时启动倒计时 timer.start(); // 标记页面为活跃状态 isGameActive = true; // 原有题目展示逻辑保持不变 binding.qestioncounter.setText(String.format("%d/%d", (index + 1), qestions.size())); question = qestions.get(index); binding.qestions.setText(question.getQestion()); binding.option1.setText(question.getOption1()); binding.option2.setText(question.getOption2()); binding.option3.setText(question.getOption3()); binding.option4.setText(question.getOption4()); index++; } else { // 题目全部遍历完成,标记为非活跃状态 isGameActive = false; reset(); Toast.makeText(this, "Quiz Finsished", Toast.LENGTH_SHORT).show(); Intent in = new Intent(QuizActivity.this, ResultActivity.class); in.putExtra("correct_answer", correctAnswer); in.putExtra("total", qestions.size()); startActivity(in); // 及时销毁当前页面,避免后台栈残留 finish(); } }
重写onDestroy方法完成资源回收:
@Override protected void onDestroy() { super.onDestroy(); // 彻底终止定时器,解绑回调 if (timer != null) { timer.cancel(); timer = null; } // 释放MediaPlayer资源 if (mMediaPlayer != null) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.stop(); } mMediaPlayer.release(); mMediaPlayer = null; } isGameActive = false; }
给定时器的onFinish回调增加状态拦截:
@Override public void onFinish() { // 非活跃状态下直接返回,不执行弹窗和音效 if (!isGameActive) { return; } // 原有弹窗逻辑 new AlertDialog.Builder(QuizActivity.this) .setIcon(R.drawable.sad) .setTitle("Oops Game Over!") .setCancelable(false) .setMessage(" Please Try Again ") .setPositiveButton("Retry", (dialogInterface, i) -> { dialogInterface.dismiss(); isGameActive = false; Intent in = new Intent(QuizActivity.this, MainActivity.class); startActivity(in); finish(); }).create().show(); // 原有音效播放逻辑 mMediaPlayer = MediaPlayer.create(QuizActivity.this, R.raw.wrong); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setLooping(false); mMediaPlayer.start(); }
在onBackPressed的退出分支中同步增加状态标记重置:
@Override public void onClick(DialogInterface dialogInterface, int i) { isGameActive = false; Intent in = new Intent(QuizActivity.this, MainActivity.class); startActivity(in); finish(); }
内容的提问来源于stack exchange,提问作者vikram
相关产品推荐
相关产品推荐

