You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android节拍器APP反复点击启停按钮崩溃原因排查

分析与解决方案:Android节拍器快速启停崩溃问题

从你的崩溃栈和代码来看,根本原因是快速点击启停按钮时,AsyncTask实例和AudioTrack资源的生命周期没有正确同步:每次停止你都会创建新的MetronomeAsyncTask,但旧的任务可能还在后台运行,对应的AudioTrack可能已经被尝试停止/释放,却还在被线程调用write()方法,导致抛出IllegalStateException: Unable to retrieve AudioTrack pointer for write()。

具体问题点拆解

  1. AsyncTask未正确取消与资源清理:你的stop()方法只是创建了新的AsyncTask,但旧的任务的doInBackground可能还在循环播放,没有被中断,也没有释放AudioTrack资源。
  2. AudioTrack状态非法:当旧线程还在尝试写入AudioTrack时,该AudioTrack可能已经被停止或释放,此时调用write()就会触发系统层的指针获取失败异常。
  3. 并发状态混乱:快速点击导致多个AsyncTask实例同时存在,多个线程操作音频资源,引发状态冲突。

分步解决方案

1. 给MetronomeBrain添加安全的停止与资源释放机制

在你的MetronomeBrain类中,添加一个volatile标志位控制播放循环,同时实现资源释放方法:

public class MetronomeBrain {
    private AudioGenerator audioGenerator;
    private volatile boolean isPlaying = false; // 用volatile保证线程可见性
    private Handler handler;

    public MetronomeBrain(Handler handler) {
        this.handler = handler;
        audioGenerator = new AudioGenerator();
        audioGenerator.createPlayer(); // 初始化AudioTrack
    }

    public void startPlaying() {
        isPlaying = true;
    }

    public void playBeat() {
        if (!isPlaying) return; // 播放前先检查状态
        // 发送节拍更新消息到UI线程
        Message msg = handler.obtainMessage();
        msg.obj = /* 当前节拍字符串 */;
        handler.sendMessage(msg);
        // 仅在播放状态有效时写入音频
        if (audioGenerator != null && isPlaying) {
            audioGenerator.writeSound();
        }
    }

    public void stopPlaying() {
        isPlaying = false;
    }

    public void releaseResources() {
        stopPlaying();
        if (audioGenerator != null) {
            audioGenerator.releaseAudioTrack();
            audioGenerator = null;
        }
    }
}

2. 修复AsyncTask的取消逻辑

修改MetronomeAsyncTask,让它正确响应取消请求,并在任务结束时释放资源:

private class MetronomeAsyncTask extends AsyncTask<Void, Void, String> {
    private MetronomeBrain metronome;

    MetronomeAsyncTask() {
        mHandler = getHandler();
        metronome = new MetronomeBrain(mHandler);
        metronome.startPlaying();
    }

    public void stop() {
        if (metronome != null) {
            metronome.stopPlaying();
        }
        cancel(true); // 触发AsyncTask取消
    }

    @Override
    protected String doInBackground(Void... params) {
        metronome.setBeat(4);
        long bpmInterval = 60000 / /* 你的BPM值 */; // 计算节拍间隔
        while (!isCancelled()) { // 循环中检查是否被取消
            metronome.playBeat();
            try {
                Thread.sleep(bpmInterval);
            } catch (InterruptedException e) {
                // 被中断后恢复中断标志,退出循环
                Thread.currentThread().interrupt();
                break;
            }
        }
        // 无论正常结束还是取消,都释放资源
        if (metronome != null) {
            metronome.releaseResources();
        }
        return "Task completed";
    }
}

3. 优化启停按钮的状态管理

不要每次停止就立即创建新的AsyncTask,而是确保旧任务完全结束后再创建新实例,同时同步按钮点击逻辑避免并发:

public synchronized void playStopButtonPressed(View v) {
    boolean wasPlayingWhenPressed = playStopButton.isSelected();
    playStopButton.setSelected(!wasPlayingWhenPressed);

    if (wasPlayingWhenPressed) {
        stopPressed();
    } else {
        // 只有当旧任务已经完成/取消时,才创建新任务
        if (aSync == null || aSync.getStatus() == AsyncTask.Status.FINISHED) {
            aSync = new MetronomeAsyncTask();
        }
        playPressed();
    }
}

private void stopPressed() {
    if (aSync != null && aSync.getStatus() != AsyncTask.Status.FINISHED) {
        aSync.stop();
    }
}

private void playPressed() {
    if (aSync.getStatus() == AsyncTask.Status.PENDING) {
        aSync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }
}

4. 完善AudioGenerator的资源释放

在AudioGenerator中添加releaseAudioTrack方法,确保AudioTrack被正确停止和释放:

public void releaseAudioTrack() {
    if (audioTrack != null) {
        if (audioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
            audioTrack.stop();
        }
        audioTrack.release();
        audioTrack = null;
    }
}

关键注意点

  • volatile关键字:用来保证isPlaying标志位在多线程中的可见性,避免线程读取到过期状态。
  • AsyncTask的isCancelled()检查:在doInBackground的循环中定期检查,确保任务能及时响应取消请求。
  • 资源释放时机:无论任务正常结束还是被取消,都必须释放AudioTrack资源,避免内存泄漏和状态冲突。

内容的提问来源于stack exchange,提问作者Nerdy Bunz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:28:16