Подумав о комментарии dmarin и прочитав код, я пришел к выводу, что dmarin ответил на ваш вопрос.Это условие гонки, а также доступ к объекту, который не инициализирован.Таким образом, решение short состоит в следующем: код должен проверить, инициализированы ли полученные данные.Объекты AudioTrack
можно проверить, если это null
или если getState()
равно «инициализировано».К сожалению, проблема не исчезает с моей настройкой (Android Studio 3.1.2, Android SDK Build-Tools 28-rc2).
private boolean isInitialized() {
return audioTrack.getState() == AudioTrack.STATE_INITIALIZED;
}
После анализа кода можно было заметить создание AsyncTasks и AudioTracks.Таким образом, чтобы минимизировать их, создайте AsyncTask только один раз в функции onCreate
- и установите для объекта AudioTrack
значение static
.
MainActivity
@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();
aSync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
}
AudioGenerator
public class AudioGenerator {
/*changed to static*/
private static AudioTrack audioTrack;
...
}
Я признаю, что просто поменять его на статическое не является прекрасным решением.Но так как мне нужен только один канал для AudioService, это подойдет.
Создание аудио-канала, остановка воспроизведения аудио и освобождение ресурса будут выглядеть так:
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
, сбрасывается при нажатии кнопки воспроизведения.Для доступа из MainActivity
: изменение этих переменных с 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;
}
В play()
из MetronomeBrain
цикл становится бесконечным циклом,Эта проблема будет исправлена в ближайшее время.Вот почему 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);
}
Теперь цикл выполняется вечно, но он может воспроизводиться только в том случае, если для play
установлено значение true
.Если очистка необходима, это может быть сделано в конце жизненного цикла Activity
, например, в MainActivity
:
@Override
protected void onDestroy() {
aSync.metronome.stopReleaseAudio(); //calls the stopRelease()
aSync.cancel(true);
super.onDestroy();
}
Как я уже говорил, код можно было бы улучшить, ноон дает справедливую подсказку и достаточно материала, чтобы подумать / узнать об AsyncTasks, таких сервисах, как Audio Service и Activity - Lifecycles.
Список литературы
- https://developer.android.com/reference/android/os/AsyncTask
- https://developer.android.com/reference/android/media/AudioManager
- https://developer.android.com/reference/android/media/AudioTrack
- https://developer.android.com/reference/android/app/Activity#activity-lifecycle
TL; DR : убедитесь, что объекты инициализированы перед тем, как получить к ним доступ, просто создайте все один раз и уничтожьте их, когда они вам не нужны, например, в конце упражнения.