Точная android Сообщение обработчика потоков отложено - PullRequest
1 голос
/ 08 мая 2020

Я пытался написать метроном на Android, однако мне очень трудно синхронизировать c удары точно с помощью метода Handler с задержкой. Мне удается добиться точного времени с помощью ScheduledThreadPoolExecutor, но проблема в том, что с помощью ScheduledThreadPoolExecutor я не могу контролировать время из метода выполнения, и поэтому я вынужден останавливать и запускать запланированное задание, что не идеально. Есть ли способ сделать задержку обработчика более точной? или способ перепланировать ScheduledThreadPoolExecutor без необходимости останавливать и запускать поток?

Мой текущий код выглядит следующим образом:

public class Metronome extends Service implements Runnable
{
    private Handler handler = new Handler();
    private SoundPool soundPool;
    private long interval;

    private void initSoundPool()
    {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            soundPool = new SoundPool.Builder()
                    .setMaxStreams(1)
                    .setAudioAttributes(new AudioAttributes.Builder()
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build())
                    .build();
        } else
        {
            soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
        }

        soundId = soundPool.load(context, R.raw.secondary_clave, 1);
    }

    @Override
    public void run()
    {
        handler.postDelayed(this, interval);
        soundPool.play(soundId, 1, 1, 0, 0, 1);
    }

    public void start()
    {
        handler.post(this);
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
}

С ScheduledThreadPoolExecutor он очень точен, однако я не У меня есть контроль с помощью флага «интервал» внутри прогона l oop, поэтому, если я изменю интервал, мне придется завершить выполнение исполнителя и начинать новый каждый раз, когда мне нужно перепланировать, что ужасно.

public class Metronome extends Service implements Runnable
{        
    private SoundPool soundPool;
    private long interval;
    private ScheduledThreadPoolExecutor beatsPerBarExec;
    private ScheduledFuture<?> futureThread;

    private void initSoundPool()
    {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            soundPool = new SoundPool.Builder()
                    .setMaxStreams(1)
                    .setAudioAttributes(new AudioAttributes.Builder()
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build())
                    .build();
        } else
        {
            soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
        }

        soundId = soundPool.load(context, R.raw.secondary_clave, 1);
    }

    @Override
    public void run()
    {            
        soundPool.play(soundId, 1, 1, 0, 0, 1);
    }

    public void start()
    {
        beatsPerBarExec = new ScheduledThreadPoolExecutor(1);
        futureThread = beatsPerBarExec.scheduleAtFixedRate(this, 0, interval, TimeUnit.MILLISECONDS);
    }

    public void pause()
    {
        futureThread.cancel(false);
        beatsPerBarExec.purge();
        beatsPerBarExec = null;            
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
}

1 Ответ

1 голос
/ 08 мая 2020

Возможно, вы видите эффект дрейфа.

Пример: вы хотите, чтобы ваш Runnable запускался каждые 200 мс c. Вы перепланировали свой Runnable в методе run(), используя postDelayed(), и передали его 200 мс c в качестве задержки. Когда в следующий раз вызывается метод run(), он может быть не точно 200 мс c по сравнению с предыдущим разом. Возможно это 210мс c. Теперь вы перенесли свой Runnable на другой 200 мс c. На этот раз метод run() может быть вызван снова через 210 мс c, что означает, что ваш звук воспроизводится на 420 мс c с момента первого, et c.

Чтобы устранить дрейф, вам необходимо определить точное время, в которое должен работать Runnable, вычесть текущее время и использовать его при вызове postDelayed(). При этом будет учтена любая потенциальная разница во времени потока.

Имейте в виду, что когда вы вызываете postDelayed(), вы отправляете Runnable для запуска в основном (UI) потоке. Это поток, который обрабатывает все обновления пользовательского интерфейса, и когда ваш Runnable будет готов к запуску, он будет поставлен в очередь в основной обработчик потока (UI) и может не запуститься немедленно.

Вы можете смягчить эту проблему, запланировав запуск вашего Runnable в фоновом потоке вместо основного (UI) потока. Однако это означает, что ваш Runnable будет вызываться в фоновом потоке, и я не знаю, должен ли ваш другой код (который воспроизводит звук) запускаться в основном (UI) потоке или нет.

...