You need to enable JavaScript to run this app. Android问答APP:QuizActivity倒计时结束弹窗音效跳转后仍异常播放
优惠活动
大模型
产品
解决方案
定价
更多

Android问答APP:QuizActivity倒计时结束弹窗音效跳转后仍异常播放

异常原因定位
  • 定时器启动逻辑存在缺陷:setNextQestions方法中,无论是否已经遍历完所有题目,都会在方法开头执行timer.start()。当用户完成全部题目准备跳转结果页时,代码依然会启动一个完整30秒的后台倒计时,这个定时器实例没有和Activity生命周期绑定,哪怕页面跳转到其他界面,定时器依然会在后台持续运行。
  • 缺少状态拦截:定时器的onFinish回调没有做前置校验,只要定时时间到就会直接执行弹窗、播放音效的逻辑,哪怕页面已经跳转、不再处于测验状态。
  • 资源回收缺失:Activity销毁时没有主动终止定时器、释放MediaPlayer资源,导致后台泄漏的定时器依然可以触发后续的音效播放逻辑。
  • 流程疏漏:正常完成测验跳转ResultActivity的分支没有调用finish()销毁当前QuizActivity,让页面一直留在后台栈中,给后台定时器的回调执行提供了上下文支持。
可落地修复方案
  1. 调整定时器启动时机,仅在还有未完成题目时才启动倒计时,在题目遍历完成的分支不再启动新的计时任务
  2. 新增全局页面状态标记,在页面退出、跳转其他页面时标记为非活跃,拦截定时器的无效回调
  3. 重写onDestroy方法,在页面销毁时彻底终止定时器、释放所有媒体资源,避免后台泄漏
  4. 在所有跳转其他页面的分支增加finish()调用,及时销毁QuizActivity
  5. 在所有主动退出、跳转其他页面的逻辑分支中提前将状态标记置为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.26 05:57:04