Android 为什么这个节拍器应用程序会崩溃?(安卓)

Android 为什么这个节拍器应用程序会崩溃?(安卓),android,memory,stream,buffer,audiotrack,Android,Memory,Stream,Buffer,Audiotrack,我正在做一个非常小的Android项目,它使用 但是,当我(或你)间歇地按下启动/停止按钮时。。。该应用程序最终崩溃。不幸的是,这可能需要一段时间来复制。。。但它会发生的 哦,我忘了预期的结果 预期结果是不会发生此崩溃。:) 有人知道为什么会发生这种崩溃吗?自2013年3月以来,这段代码的作者在Github上有一个公开的bug/问题。。。所以我很确定这不是一个特别愚蠢的问题。。。如果你知道这个问题的答案,毫无疑问,你将是一个被誉为弓箭手的人 几天来,我一直在剖析代码、打印调试、研究异步任务、处理

我正在做一个非常小的Android项目,它使用

但是,当我(或你)间歇地按下启动/停止按钮时。。。该应用程序最终崩溃。不幸的是,这可能需要一段时间来复制。。。但它会发生的

哦,我忘了预期的结果

预期结果是不会发生此崩溃。:)

有人知道为什么会发生这种崩溃吗?自2013年3月以来,这段代码的作者在Github上有一个公开的bug/问题。。。所以我很确定这不是一个特别愚蠢的问题。。。如果你知道这个问题的答案,毫无疑问,你将是一个被誉为弓箭手的人

几天来,我一直在剖析代码、打印调试、研究异步任务、处理程序和音频跟踪,但我搞不清楚。。。如果没有其他人比我强的话,我会的

这是堆栈跟踪:

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #4
                  Process: com.example.boober.beatkeeper, PID: 15664
                  java.lang.RuntimeException: An error occurred while executing doInBackground()
                      at android.os.AsyncTask$3.done(AsyncTask.java:309)
                      at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
                      at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
                      at java.util.concurrent.FutureTask.run(FutureTask.java:242)
                      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
                      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
                      at java.lang.Thread.run(Thread.java:818)
                   Caused by: java.lang.IllegalStateException: Unable to retrieve AudioTrack pointer for write()
                      at android.media.AudioTrack.native_write_byte(Native Method)
                      at android.media.AudioTrack.write(AudioTrack.java:1761)
                      at android.media.AudioTrack.write(AudioTrack.java:1704)
                      at com.example.boober.beatkeeper.AudioGenerator.writeSound(AudioGenerator.java:55)
                      at com.example.boober.beatkeeper.Metronome.play(Metronome.java:60)
                      at com.example.boober.beatkeeper.MainActivity$MetronomeAsyncTask.doInBackground(MainActivity.java:298)
                      at com.example.boober.beatkeeper.MainActivity$MetronomeAsyncTask.doInBackground(MainActivity.java:283)
                      at android.os.AsyncTask$2.call(AsyncTask.java:295)
                      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
                      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
                      at java.lang.Thread.run(Thread.java:818) 
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    String TAG = "AAA";

    Button playStopButton;
    TextView currentBeat;

    // important objects
    MetronomeAsyncTask aSync;
    Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        currentBeat = findViewById(R.id.currentBeatTextView);
        playStopButton = findViewById(R.id.playStopButton);

        // important objcts
        aSync = new MetronomeAsyncTask();
    }


    // only called from within playStopPressed()
    private void stopPressed() {
        aSync.stop();
        aSync = new MetronomeAsyncTask();
    }
    // only called from within playStopPressed()
    private void playPressed() {
        //aSync.execute();
        aSync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
    }
    public synchronized void playStopButtonPressed(View v) {
        boolean wasPlayingWhenPressed = playStopButton.isSelected();
        playStopButton.setSelected(!playStopButton.isSelected());
        if (wasPlayingWhenPressed) {
            stopPressed();
        } else {
            playPressed();
        }
    }

    // METRONOME BRAIN STUFF ------------------------------------------

    private Handler getHandler() {
        return new Handler() {
            @Override
            public void handleMessage(Message msg) {
                String message = (String) msg.obj;
                if (message.equals("1")) {
                    currentBeat.setTextColor(Color.GREEN);
                    }
                else {
                    currentBeat.setTextColor(Color.BLUE);
                }

                currentBeat.setText(message);
            }
        };
    }


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

        MetronomeAsyncTask() {
            mHandler = getHandler();
            metronome = new MetronomeBrain(mHandler);
            Runtime.getRuntime().gc();    // <---- don't know if this line is necessary or not.
        }

        protected String doInBackground(Void... params) {
            metronome.setBeat(4);
            metronome.setNoteValue(4);
            metronome.setBpm(100);
            metronome.setBeatSound(2440);
            metronome.setSound(6440);
            metronome.play();
            return null;
        }

        public void stop() {
            metronome.stop();
            metronome = null;
        }

        public void setBpm(short bpm) {
            metronome.setBpm(bpm);
            metronome.calcSilence();
        }

        public void setBeat(short beat) {
            if (metronome != null)
                metronome.setBeat(beat);
        }

    }

}
import android.os.Handler;
import android.os.Message;

public class MetronomeBrain {

    private double bpm;
    private int beat;
    private int noteValue;
    private int silence;

    private double beatSound;
    private double sound;
    private final int tick = 1000; // samples of tick

    private boolean play = true;

    private AudioGenerator audioGenerator = new AudioGenerator(8000);
    private Handler mHandler;
    private double[] soundTickArray;
    private double[] soundTockArray;
    private double[] silenceSoundArray;
    private Message msg;
    private int currentBeat = 1;

    public MetronomeBrain(Handler handler) {
        audioGenerator.createPlayer();
        this.mHandler = handler;
    }

    public void calcSilence() {
        silence = (int) (((60 / bpm) * 8000) - tick);
        soundTickArray = new double[this.tick];
        soundTockArray = new double[this.tick];
        silenceSoundArray = new double[this.silence];
        msg = new Message();
        msg.obj = "" + currentBeat;
        double[] tick = audioGenerator.getSineWave(this.tick, 8000, beatSound);
        double[] tock = audioGenerator.getSineWave(this.tick, 8000, sound);
        for (int i = 0; i < this.tick; i++) {
            soundTickArray[i] = tick[i];
            soundTockArray[i] = tock[i];
        }
        for (int i = 0; i < silence; i++)
            silenceSoundArray[i] = 0;
    }

    public void play() {
        calcSilence();
        do {
            msg = new Message();
            msg.obj = "" + currentBeat;
            if (currentBeat == 1)
                audioGenerator.writeSound(soundTockArray);
            else
                audioGenerator.writeSound(soundTickArray);
            if (bpm <= 120)
                mHandler.sendMessage(msg);
            audioGenerator.writeSound(silenceSoundArray);
            if (bpm > 120)
                mHandler.sendMessage(msg);
            currentBeat++;
            if (currentBeat > beat)
                currentBeat = 1;
        } while (play);
    }

    public void stop() {
        play = false;
        audioGenerator.destroyAudioTrack();
    }

    public double getBpm() {
        return bpm;
    }

    public void setBpm(int bpm) {
        this.bpm = bpm;
    }

    public int getNoteValue() {
        return noteValue;
    }

    public void setNoteValue(int bpmetre) {
        this.noteValue = bpmetre;
    }

    public int getBeat() {
        return beat;
    }

    public void setBeat(int beat) {
        this.beat = beat;
    }

    public double getBeatSound() {
        return beatSound;
    }

    public void setBeatSound(double sound1) {
        this.beatSound = sound1;
    }

    public double getSound() {
        return sound;
    }

    public void setSound(double sound2) {
        this.sound = sound2;
    }

}
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

public class AudioGenerator {

    private int sampleRate;
    private AudioTrack audioTrack;

    public AudioGenerator(int sampleRate) {
        this.sampleRate = sampleRate;
    }

    public double[] getSineWave(int samples,int sampleRate,double frequencyOfTone){
        double[] sample = new double[samples];
        for (int i = 0; i < samples; i++) {
            sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/frequencyOfTone));
        }
        return sample;
    }

    public byte[] get16BitPcm(double[] samples) {
        byte[] generatedSound = new byte[2 * samples.length];
        int index = 0;
        for (double sample : samples) {
            // scale to maximum amplitude
            short maxSample = (short) ((sample * Short.MAX_VALUE));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSound[index++] = (byte) (maxSample & 0x00ff);
            generatedSound[index++] = (byte) ((maxSample & 0xff00) >>> 8);

        }
        return generatedSound;
    }

    public void createPlayer(){
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT, sampleRate,
                AudioTrack.MODE_STREAM);
        audioTrack.play();
    }

    public void writeSound(double[] samples) {
        byte[] generatedSnd = get16BitPcm(samples);
        audioTrack.write(generatedSnd, 0, generatedSnd.length);
    }

    public void destroyAudioTrack() {
        audioTrack.stop();

        // This line seems to be a most likely culprit of the start/stop crash.
        // Is this line even necessary?
        audioTrack.release();
    }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.boober.android_metronome.MainActivity">

    <Button
        android:id="@+id/playStopButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:onClick="playStopButtonPressed"
        android:text="Play"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/currentBeatTextView"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="32dp"
        android:text="TextView"
        android:gravity="center_vertical"
        android:textAlignment="center"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/playStopButton" />

</android.support.constraint.ConstraintLayout>
你可以去github下载原始代码,但为了满足stackoverflow的要求,我还提供了更简洁的“最小工作示例”,如果你愿意,你可以单独剪切并粘贴到Android Studio中

main活动:

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #4
                  Process: com.example.boober.beatkeeper, PID: 15664
                  java.lang.RuntimeException: An error occurred while executing doInBackground()
                      at android.os.AsyncTask$3.done(AsyncTask.java:309)
                      at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
                      at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
                      at java.util.concurrent.FutureTask.run(FutureTask.java:242)
                      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
                      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
                      at java.lang.Thread.run(Thread.java:818)
                   Caused by: java.lang.IllegalStateException: Unable to retrieve AudioTrack pointer for write()
                      at android.media.AudioTrack.native_write_byte(Native Method)
                      at android.media.AudioTrack.write(AudioTrack.java:1761)
                      at android.media.AudioTrack.write(AudioTrack.java:1704)
                      at com.example.boober.beatkeeper.AudioGenerator.writeSound(AudioGenerator.java:55)
                      at com.example.boober.beatkeeper.Metronome.play(Metronome.java:60)
                      at com.example.boober.beatkeeper.MainActivity$MetronomeAsyncTask.doInBackground(MainActivity.java:298)
                      at com.example.boober.beatkeeper.MainActivity$MetronomeAsyncTask.doInBackground(MainActivity.java:283)
                      at android.os.AsyncTask$2.call(AsyncTask.java:295)
                      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
                      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
                      at java.lang.Thread.run(Thread.java:818) 
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    String TAG = "AAA";

    Button playStopButton;
    TextView currentBeat;

    // important objects
    MetronomeAsyncTask aSync;
    Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        currentBeat = findViewById(R.id.currentBeatTextView);
        playStopButton = findViewById(R.id.playStopButton);

        // important objcts
        aSync = new MetronomeAsyncTask();
    }


    // only called from within playStopPressed()
    private void stopPressed() {
        aSync.stop();
        aSync = new MetronomeAsyncTask();
    }
    // only called from within playStopPressed()
    private void playPressed() {
        //aSync.execute();
        aSync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
    }
    public synchronized void playStopButtonPressed(View v) {
        boolean wasPlayingWhenPressed = playStopButton.isSelected();
        playStopButton.setSelected(!playStopButton.isSelected());
        if (wasPlayingWhenPressed) {
            stopPressed();
        } else {
            playPressed();
        }
    }

    // METRONOME BRAIN STUFF ------------------------------------------

    private Handler getHandler() {
        return new Handler() {
            @Override
            public void handleMessage(Message msg) {
                String message = (String) msg.obj;
                if (message.equals("1")) {
                    currentBeat.setTextColor(Color.GREEN);
                    }
                else {
                    currentBeat.setTextColor(Color.BLUE);
                }

                currentBeat.setText(message);
            }
        };
    }


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

        MetronomeAsyncTask() {
            mHandler = getHandler();
            metronome = new MetronomeBrain(mHandler);
            Runtime.getRuntime().gc();    // <---- don't know if this line is necessary or not.
        }

        protected String doInBackground(Void... params) {
            metronome.setBeat(4);
            metronome.setNoteValue(4);
            metronome.setBpm(100);
            metronome.setBeatSound(2440);
            metronome.setSound(6440);
            metronome.play();
            return null;
        }

        public void stop() {
            metronome.stop();
            metronome = null;
        }

        public void setBpm(short bpm) {
            metronome.setBpm(bpm);
            metronome.calcSilence();
        }

        public void setBeat(short beat) {
            if (metronome != null)
                metronome.setBeat(beat);
        }

    }

}
import android.os.Handler;
import android.os.Message;

public class MetronomeBrain {

    private double bpm;
    private int beat;
    private int noteValue;
    private int silence;

    private double beatSound;
    private double sound;
    private final int tick = 1000; // samples of tick

    private boolean play = true;

    private AudioGenerator audioGenerator = new AudioGenerator(8000);
    private Handler mHandler;
    private double[] soundTickArray;
    private double[] soundTockArray;
    private double[] silenceSoundArray;
    private Message msg;
    private int currentBeat = 1;

    public MetronomeBrain(Handler handler) {
        audioGenerator.createPlayer();
        this.mHandler = handler;
    }

    public void calcSilence() {
        silence = (int) (((60 / bpm) * 8000) - tick);
        soundTickArray = new double[this.tick];
        soundTockArray = new double[this.tick];
        silenceSoundArray = new double[this.silence];
        msg = new Message();
        msg.obj = "" + currentBeat;
        double[] tick = audioGenerator.getSineWave(this.tick, 8000, beatSound);
        double[] tock = audioGenerator.getSineWave(this.tick, 8000, sound);
        for (int i = 0; i < this.tick; i++) {
            soundTickArray[i] = tick[i];
            soundTockArray[i] = tock[i];
        }
        for (int i = 0; i < silence; i++)
            silenceSoundArray[i] = 0;
    }

    public void play() {
        calcSilence();
        do {
            msg = new Message();
            msg.obj = "" + currentBeat;
            if (currentBeat == 1)
                audioGenerator.writeSound(soundTockArray);
            else
                audioGenerator.writeSound(soundTickArray);
            if (bpm <= 120)
                mHandler.sendMessage(msg);
            audioGenerator.writeSound(silenceSoundArray);
            if (bpm > 120)
                mHandler.sendMessage(msg);
            currentBeat++;
            if (currentBeat > beat)
                currentBeat = 1;
        } while (play);
    }

    public void stop() {
        play = false;
        audioGenerator.destroyAudioTrack();
    }

    public double getBpm() {
        return bpm;
    }

    public void setBpm(int bpm) {
        this.bpm = bpm;
    }

    public int getNoteValue() {
        return noteValue;
    }

    public void setNoteValue(int bpmetre) {
        this.noteValue = bpmetre;
    }

    public int getBeat() {
        return beat;
    }

    public void setBeat(int beat) {
        this.beat = beat;
    }

    public double getBeatSound() {
        return beatSound;
    }

    public void setBeatSound(double sound1) {
        this.beatSound = sound1;
    }

    public double getSound() {
        return sound;
    }

    public void setSound(double sound2) {
        this.sound = sound2;
    }

}
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

public class AudioGenerator {

    private int sampleRate;
    private AudioTrack audioTrack;

    public AudioGenerator(int sampleRate) {
        this.sampleRate = sampleRate;
    }

    public double[] getSineWave(int samples,int sampleRate,double frequencyOfTone){
        double[] sample = new double[samples];
        for (int i = 0; i < samples; i++) {
            sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/frequencyOfTone));
        }
        return sample;
    }

    public byte[] get16BitPcm(double[] samples) {
        byte[] generatedSound = new byte[2 * samples.length];
        int index = 0;
        for (double sample : samples) {
            // scale to maximum amplitude
            short maxSample = (short) ((sample * Short.MAX_VALUE));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSound[index++] = (byte) (maxSample & 0x00ff);
            generatedSound[index++] = (byte) ((maxSample & 0xff00) >>> 8);

        }
        return generatedSound;
    }

    public void createPlayer(){
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT, sampleRate,
                AudioTrack.MODE_STREAM);
        audioTrack.play();
    }

    public void writeSound(double[] samples) {
        byte[] generatedSnd = get16BitPcm(samples);
        audioTrack.write(generatedSnd, 0, generatedSnd.length);
    }

    public void destroyAudioTrack() {
        audioTrack.stop();

        // This line seems to be a most likely culprit of the start/stop crash.
        // Is this line even necessary?
        audioTrack.release();
    }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.boober.android_metronome.MainActivity">

    <Button
        android:id="@+id/playStopButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:onClick="playStopButtonPressed"
        android:text="Play"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/currentBeatTextView"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="32dp"
        android:text="TextView"
        android:gravity="center_vertical"
        android:textAlignment="center"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/playStopButton" />

</android.support.constraint.ConstraintLayout>
导入android.graphics.Color;
导入android.os.AsyncTask;
导入android.os.Handler;
导入android.os.Message;
导入android.support.v7.app.AppActivity;
导入android.os.Bundle;
导入android.util.Log;
导入android.view.view;
导入android.widget.Button;
导入android.widget.TextView;
公共类MainActivity扩展了AppCompatActivity{
String TAG=“AAA”;
按钮播放停止按钮;
文本视图;
//重要对象
节拍器同步任务异步;
汉德勒;
@凌驾
创建时受保护的void(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
currentBeat=findViewById(R.id.currentBeatTextView);
playStopButton=findViewById(R.id.playStopButton);
//重要目标
aSync=新MetronomeAsyncTask();
}
//仅从PlayStoppersted()中调用
私人禁制令(){
aSync.stop();
aSync=新MetronomeAsyncTask();
}
//仅从PlayStoppersted()中调用
私人空间{
//execute();
aSync.executeOnExecutor(AsyncTask.THREAD\u POOL\u EXECUTOR,(Void[])null;
}
已按下公共同步作废播放停止按钮(视图v){
布尔值在播放时按下=播放停止按钮。isSelected();
playStopButton.setSelected(!playStopButton.isSelected());
如果(按下时正在播放){
塞住();
}否则{
playPressed();
}
}
//节拍器脑内容------------------------------------------
私有处理程序getHandler(){
返回新处理程序(){
@凌驾
公共无效handleMessage(消息消息消息){
字符串消息=(字符串)msg.obj;
if(message.equals(“1”)){
currentBeat.setTextColor(Color.GREEN);
}
否则{
currentBeat.setTextColor(Color.BLUE);
}
currentBeat.setText(消息);
}
};
}
私有类MetronomeAsyncTask扩展了AsyncTask{
节拍器脑节拍器;
节拍器同步任务(){
mHandler=getHandler();
节拍器=新节拍器脑(mHandler);
Runtime.getRuntime().gc()/在考虑了作者的评论并阅读了代码之后,我得出了回答您问题的结论。这是一个竞争条件,也是一个未初始化对象的访问。因此,简短的解决方案是:如果访问的数据已初始化,则代码需要检查。
AudioTrack
对象可以检查,我f为
null
getState()
等于“已初始化”。不幸的是,我的设置(Android Studio 3.1.2,Android SDK构建工具28-rc2)并没有消除该问题

在代码分析之后,您可能会注意到创建了异步任务和音频曲目。因此,为了最小化这些,请在
onCreate
-函数中只创建一次异步任务,并将
AudioTrack
对象设置为
static

main活动

音频发生器

public class AudioGenerator {
    /*changed to static*/
    private static AudioTrack audioTrack;
    ...
}
我承认仅仅将其更改为静态并不是一个很好的解决方案。但是,由于我只需要一个连接音频服务的管道,这就可以了。
创建音频管道、停止播放音频并释放资源将如下所示:

public void createPlayer(){
    if (audioTrack == null  || ! isInitialized())
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
            sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_16BIT, sampleRate,
            AudioTrack.MODE_STREAM);
    if (isInitialized()){
        audioTrack.play();
    }
}
public void destroyAudioTrack() {
    if (isInitialized()) {
        audioTrack.stop();
    }
}
public void stopRelease() {
    if (isInitialized()) {
        audioTrack.stop();
        audioTrack.release();
    }
}
布尔值
play
由我重新调整用途。此外,当按下播放按钮时,名为
currentBeat
的节拍计数器被重置。对于从
main活动访问
:将这些变量从
private
更改为
public
,不是最佳解决方案

// only called from within playStopPressed()
private void stopPressed() {
    aSync.metronome.play = false;
}
// only called from within playStopPressed()
private void playPressed() {
    aSync.metronome.play = true;
    aSync.metronome.currentBeat = 1;
}
MetronomeBrain
play()
中,循环变成了一个无休止的循环。这个问题很快就会得到解决。这就是为什么
play
布尔值可能会被重新调整用途的原因。音调的播放需要设置为不同的条件,这取决于
play

public void play() {
    calcSilence();
/*a change for the do-while loop: It runs forever and needs
  to be killed externally of the loop.
  Also the play decides, if audio is being played.*/
    do {
        msg = new Message();
        msg.obj = "" + currentBeat;
        if (currentBeat == 1 && play)
            audioGenerator.writeSound(soundTockArray);
        else if (play)
            audioGenerator.writeSound(soundTickArray);
        if (bpm <= 120)
            mHandler.sendMessage(msg);
        audioGenerator.writeSound(silenceSoundArray);
        if (bpm > 120)
            mHandler.sendMessage(msg);
        currentBeat++;
        if (currentBeat > beat)
            currentBeat = 1;
    } while (true);
}
正如我所说的,代码可以进一步改进,但它提供了一个合理的提示和足够的材料来思考/了解异步任务、音频服务和活动生命周期等服务


参考资料
-
-
-
-



TL;DR:确保在访问对象之前对其进行了初始化,只需创建一次所有对象,并在不需要它们时销毁它们,例如在活动结束时。

看起来您正在工作线程中调用play,而在主线程中停止,因此我认为您遇到了一个正在执行play的竞争条件在停车后停车。顺便说一句,你应该